import app from "ags/gtk4/app";
import { Astal, Gtk } from "ags/gtk4";
import { execAsync } from "ags/process";
import css from "./homelab.css";
import GLib from "gi://GLib";

function loadConfig() {
    const configPath = GLib.getenv("HOMELAB_CONFIG") || `${GLib.getenv("HOME")}/.config/homelab/config.yaml`;
    const defaults = { host: "10.0.0.15", user: "root", port: 22 };

    try {
        const [ok, contents] = GLib.file_get_contents(configPath);
        if (!ok || !contents) return defaults;

        const text = new TextDecoder().decode(contents);
        const lines = text.split("\n");
        let host = defaults.host;
        let user = defaults.user;
        let inServer = false;

        for (const line of lines) {
            const trimmed = line.trim();
            if (trimmed.startsWith("#") || trimmed.length === 0) continue;
            if (trimmed === "server:") { inServer = true; continue; }
            if (inServer && /^\w+:/.test(trimmed) && !trimmed.startsWith("  ")) inServer = false;
            if (!inServer) continue;
            const addrMatch = trimmed.match(/address:\s*["']?(.+?)["']?$/);
            if (addrMatch) host = addrMatch[1];
            const userMatch = trimmed.match(/username:\s*["']?(.+?)["']?$/);
            if (userMatch) user = userMatch[1];
        }
        return { host, user, port: defaults.port };
    } catch {
        return defaults;
    }
}

const CONFIG = loadConfig();
const UNRAID_HOST = CONFIG.host;
const UNRAID_USER = CONFIG.user;
const SSH_OPTS = [
    "-o", "StrictHostKeyChecking=accept-new",
    "-o", "ConnectTimeout=5",
    "-o", "ServerAliveInterval=5",
    "-o", "ServerAliveCountMax=1",
];
const REFRESH_INTERVAL_SECONDS = 30;
const HISTORY_LIMIT = 24;
const WINDOW_MARGIN_TOP = 48;
const WINDOW_EDGE_GAP = 48;
const ESC_KEYVAL = 65307;

type PageId = "dashboard" | "docker" | "services" | "storage" | "network" | "system" | "logs" | "automations" | "auth" | "integrations" | "ai" | "monitoring";

type DockerContainer = {
    id: string;
    name: string;
    image: string;
    status: string;
    state: string;
    health: string;
    ports: string;
    restart: string;
    cpu: number;
    memory: string;
};

type DiskInfo = {
    filesystem: string;
    size: string;
    used: string;
    available: string;
    percent: number;
    mount: string;
};

type ServerStatus = {
    host: string;
    uptime: string;
    kernel: string;
    load: string;
    load1: number;
    cpuPercent: number;
    memory: string;
    memoryPercent: number;
    rootDisk: string;
    rootDiskPercent: number;
    userDisk: string;
    userDiskPercent: number;
    array: string;
    parity: string;
    temps: string;
    docker: string;
    dockerRunning: number;
    dockerStopped: number;
    dockerUnhealthy: number;
    dockerContainers: DockerContainer[];
    disks: DiskInfo[];
    smart: string;
    network: string;
    networkRxBytes: number;
    networkTxBytes: number;
    dockerNetworks: string;
    ports: string;
    updates: string;
    serviceHealth: Record<string, string>;
    alerts: string[];
    updated: string;
};

type MetricSample = {
    time: string;
    load1: number;
    cpuPercent: number;
    memoryPercent: number;
    rootDiskPercent: number;
    userDiskPercent: number;
    dockerRunning: number;
    networkRxRate: number;
    networkTxRate: number;
    rxBytes: number;
    txBytes: number;
    timestamp: number;
};

const navItems: { id: PageId; icon: string; label: string }[] = [
    { id: "dashboard", icon: "🏠", label: "Dashboard" },
    { id: "docker", icon: "🐳", label: "Docker" },
    { id: "services", icon: "📦", label: "Apps / Services" },
    { id: "storage", icon: "💾", label: "Storage / Disks" },
    { id: "network", icon: "🌐", label: "Netzwerk" },
    { id: "system", icon: "⚙️", label: "System" },
    { id: "logs", icon: "📜", label: "Logs" },
    { id: "automations", icon: "🤖", label: "Automationen" },
    { id: "auth", icon: "🔐", label: "Auth / User" },
    { id: "integrations", icon: "🔌", label: "Integrationen" },
    { id: "ai", icon: "🧠", label: "AI / Agent" },
    { id: "monitoring", icon: "📊", label: "Monitoring Advanced" },
];

const serviceChecks = [
    { name: "Unraid", url: `http://${UNRAID_HOST}`, localUrl: "http://127.0.0.1", hint: "Core Web UI", query: "" },
    { name: "Navidrome", url: `http://${UNRAID_HOST}:4533`, localUrl: "http://127.0.0.1:4533", hint: "Music", query: "navidrome" },
    { name: "Paperless", url: `http://${UNRAID_HOST}:8000`, localUrl: "http://127.0.0.1:8000", hint: "Docs", query: "paperless" },
    { name: "Nextcloud", url: `http://${UNRAID_HOST}:8080`, localUrl: "http://127.0.0.1:8080", hint: "Cloud", query: "nextcloud" },
    { name: "Ollama", url: `http://${UNRAID_HOST}:11434`, localUrl: "http://127.0.0.1:11434", hint: "AI API", query: "ollama" },
    { name: "n8n", url: `http://${UNRAID_HOST}:5678`, localUrl: "http://127.0.0.1:5678", hint: "Automation", query: "n8n" },
    { name: "Home Assistant", url: `http://${UNRAID_HOST}:8123`, localUrl: "http://127.0.0.1:8123", hint: "Smart Home", query: "homeassistant" },
    { name: "Authentik", url: `http://${UNRAID_HOST}:9000`, localUrl: "http://127.0.0.1:9000", hint: "SSO", query: "authentik" },
];

let password = "";
let authed = false;
let busy = false;
let status: ServerStatus | null = null;
let errorMessage = "";
let history: MetricSample[] = [];
let nextRefreshAt = 0;
let timerStarted = false;
let armedAction = "";
let activePage: PageId = "dashboard";
let logOutput = "Noch keine Logs geladen.";
let logTitle = "Logs";

function shQuote(value: string) {
    return `'${value.replace(/'/g, `'\\''`)}'`;
}

function notify(message: string) {
    execAsync(["notify-send", "Homelab", message]).catch(console.error);
}

function sshCommand(remoteCommand: string) {
    const opts = SSH_OPTS.map(shQuote).join(" ");
    return `SSHPASS=${shQuote(password)} sshpass -e ssh ${opts} ${shQuote(`${UNRAID_USER}@${UNRAID_HOST}`)} ${shQuote(remoteCommand)}`;
}

function runRemote(remoteCommand: string) {
    return execAsync(["bash", "-lc", sshCommand(remoteCommand)]);
}

function parsePercent(value: string) {
    return clampNumber(Number.parseFloat(value.replace("%", "").trim() || "0"), 0, 100);
}

function parseSections(output: string) {
    const sections: Record<string, string> = {};
    let current = "";

    for (const line of output.split("\n")) {
        const match = line.match(/^===([A-Z_]+)===$/);
        if (match) {
            current = match[1];
            sections[current] = "";
            continue;
        }

        if (current) {
            sections[current] += `${line}\n`;
        }
    }

    return sections;
}

function parseDocker(sections: Record<string, string>) {
    const stats: Record<string, { cpu: number; memory: string }> = {};

    for (const line of (sections.DOCKER_STATS || "").trim().split("\n").filter(Boolean)) {
        const [name, cpu, memory] = line.split("||");
        stats[name] = { cpu: parsePercent(cpu || "0"), memory: memory || "n/a" };
    }

    return (sections.DOCKER_ALL || "")
        .trim()
        .split("\n")
        .filter(Boolean)
        .map(line => {
            const [id, name, image, statusText, state, health, ports, restart] = line.split("||");
            return {
                id: id || "",
                name: name || "unknown",
                image: image || "n/a",
                status: statusText || "n/a",
                state: state || "unknown",
                health: health || "n/a",
                ports: ports || "n/a",
                restart: restart || "n/a",
                cpu: stats[name]?.cpu || 0,
                memory: stats[name]?.memory || "n/a",
            };
        });
}

function parseDisks(output: string) {
    return output
        .trim()
        .split("\n")
        .filter(Boolean)
        .map(line => {
            const [filesystem, size, used, available, percent, mount] = line.split("||");
            return {
                filesystem: filesystem || "n/a",
                size: size || "n/a",
                used: used || "n/a",
                available: available || "n/a",
                percent: parsePercent(percent || "0"),
                mount: mount || "n/a",
            };
        });
}

function parseServiceHealth(output: string) {
    const health: Record<string, string> = {};

    for (const line of output.trim().split("\n").filter(Boolean)) {
        const [name, code] = line.split("||");
        health[name] = code || "n/a";
    }

    return health;
}

function buildAlerts(nextStatus: ServerStatus) {
    const alerts: string[] = [];

    if (nextStatus.dockerUnhealthy > 0) {
        alerts.push(`${nextStatus.dockerUnhealthy} unhealthy container`);
    }
    if (nextStatus.dockerContainers.some(container => container.state !== "running")) {
        alerts.push(`${nextStatus.dockerStopped} stopped container`);
    }
    if (nextStatus.userDiskPercent >= 90 || nextStatus.rootDiskPercent >= 90) {
        alerts.push("Disk usage high");
    }
    if (nextStatus.load1 >= 4) {
        alerts.push("High load");
    }
    if (nextStatus.smart.toLowerCase().includes("prefail") || nextStatus.smart.toLowerCase().includes("failed")) {
        alerts.push("SMART warning");
    }
    if (nextStatus.array.toLowerCase().includes("disabled") || nextStatus.array.toLowerCase().includes("invalid")) {
        alerts.push("Array warning");
    }

    return alerts.length ? alerts : ["Alles ruhig"];
}

function parseStatus(output: string): ServerStatus {
    const sections = parseSections(output);
    const updated = GLib.DateTime.new_now_local().format("%H:%M:%S") || "";
    const load = sections.LOAD?.trim() || "n/a";
    const dockerContainers = parseDocker(sections);
    const dockerRunning = dockerContainers.filter(container => container.state === "running").length;
    const dockerStopped = dockerContainers.filter(container => container.state !== "running").length;
    const dockerUnhealthy = dockerContainers.filter(container => container.health.toLowerCase().includes("unhealthy")).length;

    const nextStatus: ServerStatus = {
        host: sections.HOST?.trim() || UNRAID_HOST,
        uptime: sections.UPTIME?.trim() || "n/a",
        kernel: sections.KERNEL?.trim() || "n/a",
        load,
        load1: Math.max(0, Number.parseFloat(load.split(/\s+/)[0] || "0") || 0),
        cpuPercent: parsePercent(sections.CPU_PERCENT || "0"),
        memory: sections.MEMORY?.trim() || "n/a",
        memoryPercent: parsePercent(sections.MEMORY_PERCENT || "0"),
        rootDisk: sections.ROOT_DISK?.trim() || "n/a",
        rootDiskPercent: parsePercent(sections.ROOT_DISK_PERCENT || "0"),
        userDisk: sections.USER_DISK?.trim() || "n/a",
        userDiskPercent: parsePercent(sections.USER_DISK_PERCENT || "0"),
        array: sections.ARRAY?.trim() || "n/a",
        parity: sections.PARITY?.trim() || "n/a",
        temps: sections.TEMPS?.trim() || "n/a",
        docker: sections.DOCKER?.trim() || "n/a",
        dockerRunning,
        dockerStopped,
        dockerUnhealthy,
        dockerContainers,
        disks: parseDisks(sections.DISKS || ""),
        smart: sections.SMART?.trim() || "n/a",
        network: sections.NETWORK?.trim() || "n/a",
        networkRxBytes: Math.max(0, Number.parseInt(sections.NETWORK_RX_BYTES?.trim() || "0", 10) || 0),
        networkTxBytes: Math.max(0, Number.parseInt(sections.NETWORK_TX_BYTES?.trim() || "0", 10) || 0),
        dockerNetworks: sections.DOCKER_NETWORKS?.trim() || "n/a",
        ports: sections.PORTS?.trim() || "n/a",
        updates: sections.UPDATES?.trim() || "n/a",
        serviceHealth: parseServiceHealth(sections.SERVICE_HEALTH || ""),
        alerts: [],
        updated,
    };

    nextStatus.alerts = buildAlerts(nextStatus);
    return nextStatus;
}

const statusCommand = String.raw`
printf '===HOST===\n'
hostname 2>/dev/null || uname -n
printf '===UPTIME===\n'
uptime -p 2>/dev/null || uptime
printf '===KERNEL===\n'
uname -r
printf '===LOAD===\n'
awk '{print $1"  "$2"  "$3}' /proc/loadavg 2>/dev/null
printf '===CPU_PERCENT===\n'
read _ u1 n1 s1 i1 iw1 irq1 si1 st1 _ < /proc/stat
idle1=$((i1 + iw1))
total1=$((u1 + n1 + s1 + i1 + iw1 + irq1 + si1 + st1))
sleep 1
read _ u2 n2 s2 i2 iw2 irq2 si2 st2 _ < /proc/stat
idle2=$((i2 + iw2))
total2=$((u2 + n2 + s2 + i2 + iw2 + irq2 + si2 + st2))
awk -v idle="$((idle2 - idle1))" -v total="$((total2 - total1))" 'BEGIN { if (total > 0) printf "%.0f\n", (1 - idle / total) * 100; else print 0 }'
printf '===MEMORY===\n'
free -h | awk '/Mem:/ {print $3 " / " $2 " used"}'
printf '===MEMORY_PERCENT===\n'
free | awk '/Mem:/ { if ($2 > 0) printf "%.0f\n", ($3 / $2) * 100; else print 0 }'
printf '===ROOT_DISK===\n'
df -h / | awk 'NR==2 {print $3 " / " $2 " used (" $5 ")"}'
printf '===ROOT_DISK_PERCENT===\n'
df -P / | awk 'NR==2 {gsub("%", "", $5); print $5}'
printf '===USER_DISK===\n'
df -h /mnt/user 2>/dev/null | awk 'NR==2 {print $3 " / " $2 " used (" $5 ")"}'
printf '===USER_DISK_PERCENT===\n'
df -P /mnt/user 2>/dev/null | awk 'NR==2 {gsub("%", "", $5); print $5}'
printf '===ARRAY===\n'
if command -v mdcmd >/dev/null 2>&1; then
    mdcmd status | grep -E '^(mdState|mdNumDisabled|mdNumInvalid|sbSynced|mdResync|mdResyncPos|mdResyncSize)=' | head -30
elif [ -r /proc/mdcmd ]; then
    grep -E '^(mdState|mdNumDisabled|mdNumInvalid|sbSynced|mdResync|mdResyncPos|mdResyncSize)=' /proc/mdcmd | head -30
else
    echo 'Array status unavailable'
fi
printf '===PARITY===\n'
if command -v mdcmd >/dev/null 2>&1; then
    mdcmd status | grep -E '^(sbSynced|mdResync|mdResyncPos|mdResyncSize|mdResyncDt|mdResyncDb)=' | head -20
else
    echo 'Parity details unavailable'
fi
printf '===TEMPS===\n'
if command -v sensors >/dev/null 2>&1; then
    sensors | grep -E 'Package id|Tctl|Tdie|Core [0-9]|temp[0-9]' | head -8
fi
if command -v smartctl >/dev/null 2>&1; then
    for disk in /dev/sd? /dev/nvme?n1; do
        [ -b "$disk" ] || continue
        smartctl -A "$disk" 2>/dev/null | awk -v disk="$disk" '/Temperature_Celsius|Current Drive Temperature|Composite Temperature/ {print disk ": " $0; exit}'
    done | head -8
fi
printf '===DOCKER===\n'
if command -v docker >/dev/null 2>&1; then
    docker ps --format '{{.Names}} | {{.Status}}' | head -12
else
    echo 'Docker unavailable'
fi
printf '===DOCKER_ALL===\n'
if command -v docker >/dev/null 2>&1; then
    docker ps -aq | while read id; do
        [ -n "$id" ] || continue
        name=$(docker inspect -f '{{.Name}}' "$id" 2>/dev/null | sed 's#^/##')
        image=$(docker inspect -f '{{.Config.Image}}' "$id" 2>/dev/null)
        status=$(docker inspect -f '{{.State.Status}}' "$id" 2>/dev/null)
        health=$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}n/a{{end}}' "$id" 2>/dev/null)
        restart=$(docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' "$id" 2>/dev/null)
        ports=$(docker port "$id" 2>/dev/null | paste -sd ', ' -)
        shown=$(docker ps -a --filter id="$id" --format '{{.Status}}' | head -1)
        [ -n "$ports" ] || ports=n/a
        [ -n "$restart" ] || restart=n/a
        echo "$id||$name||$image||$shown||$status||$health||$ports||$restart"
    done
fi
printf '===DOCKER_STATS===\n'
if command -v docker >/dev/null 2>&1; then
    docker stats --no-stream --format '{{.Name}}||{{.CPUPerc}}||{{.MemUsage}}' 2>/dev/null
fi
printf '===DISKS===\n'
df -hP | awk 'NR>1 && ($6 ~ /^\/mnt/ || $6 == "/") {gsub("%", "", $5); print $1 "||" $2 "||" $3 "||" $4 "||" $5 "||" $6}'
printf '===SMART===\n'
if command -v smartctl >/dev/null 2>&1; then
    for disk in /dev/sd? /dev/nvme?n1; do
        [ -b "$disk" ] || continue
        health=$(smartctl -H "$disk" 2>/dev/null | awk -F: '/overall-health|SMART Health Status/ {gsub(/^ +/, "", $2); print $2; exit}')
        temp=$(smartctl -A "$disk" 2>/dev/null | awk '/Temperature_Celsius|Current Drive Temperature|Composite Temperature/ {print $NF; exit}')
        [ -n "$health" ] || health=n/a
        [ -n "$temp" ] || temp=n/a
        echo "$disk | health=$health | temp=$temp"
    done | head -16
else
    echo 'smartctl unavailable'
fi
printf '===NETWORK===\n'
ip -br addr 2>/dev/null | head -12
printf '===NETWORK_RX_BYTES===\n'
awk -F'[: ]+' 'NR>2 && $2 !~ /^lo$/ {rx += $3} END {print rx+0}' /proc/net/dev
printf '===NETWORK_TX_BYTES===\n'
awk -F'[: ]+' 'NR>2 && $2 !~ /^lo$/ {tx += $11} END {print tx+0}' /proc/net/dev
printf '===DOCKER_NETWORKS===\n'
if command -v docker >/dev/null 2>&1; then
    docker network ls --format '{{.Name}} | {{.Driver}} | {{.Scope}}'
else
    echo 'Docker networks unavailable'
fi
printf '===PORTS===\n'
(ss -tulpen 2>/dev/null || netstat -tulpen 2>/dev/null) | head -24
printf '===SERVICE_HEALTH===\n'
if command -v curl >/dev/null 2>&1; then
    while IFS='|' read name url; do
        [ -n "$name" ] || continue
        code=$(curl -skL --max-time 3 -o /dev/null -w '%{http_code}' "$url" 2>/dev/null)
        [ -n "$code" ] || code=000
        echo "$name||$code"
    done <<'SERVICES'
Unraid|http://127.0.0.1
Navidrome|http://127.0.0.1:4533
Paperless|http://127.0.0.1:8000
Nextcloud|http://127.0.0.1:8080
Ollama|http://127.0.0.1:11434
n8n|http://127.0.0.1:5678
Home Assistant|http://127.0.0.1:8123
Authentik|http://127.0.0.1:9000
SERVICES
else
    echo 'curl||unavailable'
fi
printf '===UPDATES===\n'
if command -v plugin >/dev/null 2>&1; then
    echo 'Plugin tool available'
else
    echo 'Update check unavailable from shell'
fi
`;

const quickStatusCommand = String.raw`
printf '===HOST===\n'
hostname 2>/dev/null || uname -n
printf '===UPTIME===\n'
uptime -p 2>/dev/null || uptime
printf '===KERNEL===\n'
uname -r
printf '===LOAD===\n'
awk '{print $1"  "$2"  "$3}' /proc/loadavg 2>/dev/null
printf '===CPU_PERCENT===\n'
awk '{print int($1 * 100)}' /proc/loadavg 2>/dev/null
printf '===MEMORY===\n'
free -h | awk '/Mem:/ {print $3 " / " $2 " used"}'
printf '===MEMORY_PERCENT===\n'
free | awk '/Mem:/ { if ($2 > 0) printf "%.0f\n", ($3 / $2) * 100; else print 0 }'
printf '===ROOT_DISK===\n'
df -h / | awk 'NR==2 {print $3 " / " $2 " used (" $5 ")"}'
printf '===ROOT_DISK_PERCENT===\n'
df -P / | awk 'NR==2 {gsub("%", "", $5); print $5}'
printf '===USER_DISK===\n'
df -h /mnt/user 2>/dev/null | awk 'NR==2 {print $3 " / " $2 " used (" $5 ")"}'
printf '===USER_DISK_PERCENT===\n'
df -P /mnt/user 2>/dev/null | awk 'NR==2 {gsub("%", "", $5); print $5}'
printf '===ARRAY===\n'
echo 'Detail refresh laeuft'
printf '===PARITY===\n'
echo 'Detail refresh laeuft'
printf '===TEMPS===\n'
echo 'Detail refresh laeuft'
printf '===DOCKER===\n'
if command -v docker >/dev/null 2>&1; then
    docker ps --format '{{.Names}} | {{.Status}}' | head -12
else
    echo 'Docker unavailable'
fi
printf '===DOCKER_ALL===\n'
if command -v docker >/dev/null 2>&1; then
    docker ps -a --format '{{.ID}}||{{.Names}}||{{.Image}}||{{.Status}}||{{.State}}||n/a||n/a||n/a'
fi
printf '===DISKS===\n'
df -hP | awk 'NR>1 && ($6 ~ /^\/mnt/ || $6 == "/") {gsub("%", "", $5); print $1 "||" $2 "||" $3 "||" $4 "||" $5 "||" $6}'
printf '===SMART===\n'
echo 'Detail refresh laeuft'
printf '===NETWORK===\n'
ip -br addr 2>/dev/null | head -12
printf '===NETWORK_RX_BYTES===\n'
awk -F'[: ]+' 'NR>2 && $2 !~ /^lo$/ {rx += $3} END {print rx+0}' /proc/net/dev
printf '===NETWORK_TX_BYTES===\n'
awk -F'[: ]+' 'NR>2 && $2 !~ /^lo$/ {tx += $11} END {print tx+0}' /proc/net/dev
printf '===DOCKER_NETWORKS===\n'
echo 'Detail refresh laeuft'
printf '===PORTS===\n'
echo 'Detail refresh laeuft'
printf '===UPDATES===\n'
echo 'Detail refresh laeuft'
`;

function clampNumber(value: number, min: number, max: number) {
    if (!Number.isFinite(value)) {
        return min;
    }
    return Math.max(min, Math.min(max, value));
}

function formatRate(bytesPerSecond: number) {
    if (bytesPerSecond >= 1024 * 1024) {
        return `${(bytesPerSecond / 1024 / 1024).toFixed(1)} MiB/s`;
    }
    if (bytesPerSecond >= 1024) {
        return `${(bytesPerSecond / 1024).toFixed(1)} KiB/s`;
    }
    return `${Math.round(bytesPerSecond)} B/s`;
}

function addSample(nextStatus: ServerStatus) {
    const timestamp = Date.now();
    const previous = history[history.length - 1];
    const seconds = previous ? Math.max(1, (timestamp - previous.timestamp) / 1000) : REFRESH_INTERVAL_SECONDS;
    const networkRxRate = previous ? Math.max(0, (nextStatus.networkRxBytes - previous.rxBytes) / seconds) : 0;
    const networkTxRate = previous ? Math.max(0, (nextStatus.networkTxBytes - previous.txBytes) / seconds) : 0;

    history = [
        ...history,
        {
            time: nextStatus.updated,
            load1: nextStatus.load1,
            cpuPercent: nextStatus.cpuPercent,
            memoryPercent: nextStatus.memoryPercent,
            rootDiskPercent: nextStatus.rootDiskPercent,
            userDiskPercent: nextStatus.userDiskPercent,
            dockerRunning: nextStatus.dockerRunning,
            networkRxRate,
            networkTxRate,
            rxBytes: nextStatus.networkRxBytes,
            txBytes: nextStatus.networkTxBytes,
            timestamp,
        },
    ].slice(-HISTORY_LIMIT);
}

function sparkline(values: number[], maxValue = 100) {
    const blocks = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
    if (!values.length) {
        return "·".repeat(HISTORY_LIMIT);
    }

    return values
        .map(value => {
            const ratio = clampNumber(value / Math.max(maxValue, 1), 0, 1);
            return blocks[Math.round(ratio * (blocks.length - 1))];
        })
        .join("");
}

function secondsUntilRefresh() {
    if (!authed || !nextRefreshAt) {
        return REFRESH_INTERVAL_SECONDS;
    }
    return Math.max(0, Math.ceil((nextRefreshAt - Date.now()) / 1000));
}

function scheduleNextRefresh() {
    nextRefreshAt = Date.now() + REFRESH_INTERVAL_SECONDS * 1000;
}

function windowLayout() {
    const geometry = app.monitors[0]?.get_geometry();
    const screenWidth = geometry?.width || 1280;
    const screenHeight = geometry?.height || 720;
    const width = Math.round(clampNumber(screenWidth - WINDOW_EDGE_GAP * 2, 760, 1180));
    const height = Math.round(clampNumber(screenHeight - WINDOW_MARGIN_TOP - WINDOW_EDGE_GAP, 420, 720));

    return {
        width,
        height,
        pageWidth: Math.max(420, width - 290),
        pageHeight: Math.max(260, height - 92),
    };
}

function setBusy(value: boolean) {
    busy = value;
    app.get_window("homelab-control")?.queue_draw();
}

function applyStatus(output: string) {
    const nextStatus = parseStatus(output);
    status = nextStatus;
    addSample(nextStatus);
    authed = true;
}

function refresh(command = statusCommand, showBusy = true) {
    if (!password) {
        errorMessage = "Passwort fehlt.";
        return;
    }

    if (showBusy) {
        setBusy(true);
    }
    errorMessage = "";
    scheduleNextRefresh();

    runRemote(command)
        .then(output => {
            applyStatus(output);
        })
        .catch(error => {
            console.error(error);
            errorMessage = "Verbindung fehlgeschlagen. Passwort, Netzwerk oder SSH pruefen.";
        })
        .finally(() => {
            if (showBusy) {
                setBusy(false);
            }
            rebuild();
        });
}

function login() {
    if (!password) {
        errorMessage = "Passwort fehlt.";
        rebuild();
        return;
    }

    setBusy(true);
    errorMessage = "";

    runRemote(quickStatusCommand)
        .then(output => {
            applyStatus(output);
            scheduleNextRefresh();
            setBusy(false);
            rebuild();
            GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => {
                refresh(statusCommand, false);
                return GLib.SOURCE_REMOVE;
            });
        })
        .catch(error => {
            console.error(error);
            errorMessage = "Verbindung fehlgeschlagen. Passwort, Netzwerk oder SSH pruefen.";
            setBusy(false);
            rebuild();
        });
}

function runAction(label: string, command: string) {
    armedAction = "";
    setBusy(true);
    runRemote(command)
        .then(() => {
            notify(`${label} ausgefuehrt.`);
            setBusy(false);
            refresh();
        })
        .catch(error => {
            console.error(error);
            errorMessage = `${label} fehlgeschlagen.`;
            setBusy(false);
            rebuild();
        });
}

function loadContainerLogs(container: string) {
    setBusy(true);
    runRemote(`docker logs --tail 160 ${shQuote(container)} 2>&1`)
        .then(output => {
            logTitle = `Logs: ${container}`;
            logOutput = output.trim() || "Keine Logs.";
            activePage = "logs";
        })
        .catch(error => {
            console.error(error);
            logTitle = `Logs: ${container}`;
            logOutput = "Logs konnten nicht geladen werden.";
            activePage = "logs";
        })
        .finally(() => {
            setBusy(false);
            rebuild();
        });
}

function openContainerShell(container: string) {
    const remote = `docker exec -it ${shQuote(container)} sh`;
    const ssh = sshCommand(remote);
    const launch = `kitty bash -lc ${shQuote(ssh)} || alacritty -e bash -lc ${shQuote(ssh)} || foot bash -lc ${shQuote(ssh)} || xterm -e bash -lc ${shQuote(ssh)}`;
    execAsync(["bash", "-lc", launch]).catch(error => {
        console.error(error);
        notify("Kein Terminal fuer Exec Shell gefunden.");
    });
}

function openUrl(url: string) {
    execAsync(["xdg-open", url]).catch(console.error);
}

function restartServiceCommand(query: string) {
    if (!query) {
        return "echo 'No container mapping configured for this service'";
    }

    return `id=$(docker ps -aq --filter name=${shQuote(query)} | head -1); [ -n "$id" ] && docker restart "$id" || echo 'Container not found'`;
}

function Card({ title, value, className = "" }: { title: string; value: string; className?: string }) {
    return (
        <box class={`card ${className}`} orientation={Gtk.Orientation.VERTICAL} spacing={6}>
            <label class="card-title" xalign={0} label={title} />
            <label class="card-value" xalign={0} wrap label={value || "n/a"} />
        </box>
    );
}

function StatCard({ title, value, percent }: { title: string; value: string; percent: number }) {
    const safePercent = clampNumber(percent, 0, 100);

    return (
        <box class="card stat-card" orientation={Gtk.Orientation.VERTICAL} spacing={7}>
            <box>
                <label class="card-title" xalign={0} hexpand label={title} />
                <label class="percent-label" xalign={1} label={`${Math.round(safePercent)}%`} />
            </box>
            <box class="meter">
                <box class="meter-fill" widthRequest={Math.max(4, Math.round(safePercent * 1.9))} />
            </box>
            <label class="card-value compact" xalign={0} wrap label={value || "n/a"} />
        </box>
    );
}

function ChartCard({ title, value, values, maxValue = 100, accent = "" }: { title: string; value: string; values: number[]; maxValue?: number; accent?: string }) {
    const current = values.length ? values[values.length - 1] : 0;

    return (
        <box class={`chart-card ${accent}`} orientation={Gtk.Orientation.VERTICAL} spacing={8}>
            <box>
                <label class="card-title" xalign={0} hexpand label={title} />
                <label class="chart-value" xalign={1} label={value} />
            </box>
            <label class="sparkline" xalign={0} label={sparkline(values, Math.max(maxValue, current, 1))} />
            <box class="meter">
                <box class="meter-fill" widthRequest={Math.max(4, Math.round(clampNumber(current / Math.max(maxValue, 1), 0, 1) * 260))} />
            </box>
        </box>
    );
}

function ActionButton({ label, command, destructive = false }: { label: string; command: string; destructive?: boolean }) {
    return (
        <button
            class={destructive ? "button danger" : "button"}
            sensitive={!busy && authed}
            onClicked={() => {
                if (destructive && armedAction !== label) {
                    armedAction = label;
                    rebuild();
                    return;
                }
                runAction(label, command);
            }}
            label={destructive && armedAction === label ? "Nochmal bestaetigen" : label}
        />
    );
}

function NavButton({ id, icon, label }: { id: PageId; icon: string; label: string }) {
    return (
        <button
            class={activePage === id ? "nav-button active" : "nav-button"}
            onClicked={() => {
                activePage = id;
                rebuild();
            }}
        >
            <box spacing={8}>
                <label class="nav-icon" label={icon} />
                <label class="nav-label" xalign={0} label={label} />
            </box>
        </button>
    );
}

function LoginView() {
    return (
        <box class="login-panel" orientation={Gtk.Orientation.VERTICAL} spacing={14}>
            <box orientation={Gtk.Orientation.VERTICAL} spacing={4}>
                <label class="title" xalign={0} label="Homelab Controlcenter" />
                <label class="subtitle" xalign={0} label={`${UNRAID_USER}@${UNRAID_HOST}`} />
            </box>
            <entry
                visibility={false}
                placeholderText="Unraid Passwort"
                hexpand
                onChanged={entry => {
                    password = entry.get_text();
                }}
                onActivate={entry => {
                    password = entry.get_text();
                    login();
                }}
            />
            <button class="button primary" sensitive={!busy} onClicked={login} label={busy ? "Verbinde..." : "Verbinden"} />
            {errorMessage ? <label class="error" xalign={0} wrap label={errorMessage} /> : <box />}
        </box>
    );
}

function AlertsStrip() {
    return (
        <box class="alerts" spacing={8}>
            {(status?.alerts || ["Noch keine Daten"]).map(alert => (
                <label class={alert === "Alles ruhig" ? "alert ok" : "alert warn"} label={alert} />
            ))}
        </box>
    );
}

function DashboardPage() {
    const loadMax = Math.max(2, ...history.map(sample => sample.load1), status?.load1 || 0);
    const networkMax = Math.max(1024, ...history.map(sample => Math.max(sample.networkRxRate, sample.networkTxRate)));
    const latest = history[history.length - 1];

    return (
        <box orientation={Gtk.Orientation.VERTICAL} spacing={12}>
            <AlertsStrip />
            <box class="live-strip" spacing={10}>
                <ChartCard title="CPU" value={`${status?.cpuPercent ?? 0}%`} values={history.map(sample => sample.cpuPercent)} accent="cpu" />
                <ChartCard title="RAM" value={`${status?.memoryPercent ?? 0}%`} values={history.map(sample => sample.memoryPercent)} accent="memory" />
                <ChartCard title="Load 1m" value={`${status?.load1?.toFixed(2) || "0.00"}`} values={history.map(sample => sample.load1)} maxValue={loadMax} accent="load" />
            </box>
            <box class="live-strip" spacing={10}>
                <ChartCard title="Network RX" value={formatRate(latest?.networkRxRate || 0)} values={history.map(sample => sample.networkRxRate)} maxValue={networkMax} accent="network" />
                <ChartCard title="Network TX" value={formatRate(latest?.networkTxRate || 0)} values={history.map(sample => sample.networkTxRate)} maxValue={networkMax} accent="network" />
                <ChartCard title="Docker" value={`${status?.dockerRunning || 0} running`} values={history.map(sample => sample.dockerRunning)} maxValue={Math.max(5, ...history.map(sample => sample.dockerRunning))} accent="docker" />
            </box>
            <box class="grid" spacing={10}>
                <Card title="Temps" value={status?.temps || "n/a"} />
                <Card title="Parity" value={status?.parity || "n/a"} />
                <Card title="Power Usage" value="Nicht verfuegbar ohne Sensor / UPS Integration" />
            </box>
            <box class="grid" spacing={10}>
                <StatCard title="Root Disk" value={status?.rootDisk || ""} percent={status?.rootDiskPercent || 0} />
                <StatCard title="/mnt/user" value={status?.userDisk || ""} percent={status?.userDiskPercent || 0} />
                <Card title="SMART / Health" value={status?.smart || "n/a"} />
            </box>
        </box>
    );
}

function ContainerRow({ container }: { container: DockerContainer }) {
    const running = container.state === "running";
    return (
        <box class="table-row" spacing={8}>
            <box class="container-main" orientation={Gtk.Orientation.VERTICAL} spacing={2} hexpand>
                <label class="row-title" xalign={0} label={container.name} />
                <label class="row-subtitle" xalign={0} wrap label={`${container.image} · ${container.status}`} />
                <label class="row-subtitle" xalign={0} wrap label={`Ports: ${container.ports || "n/a"}`} />
            </box>
            <box class="container-stats" orientation={Gtk.Orientation.VERTICAL} spacing={2}>
                <label class={running ? "pill ok" : "pill warn"} label={container.state} />
                <label class="row-subtitle" xalign={1} label={`CPU ${container.cpu.toFixed(1)}%`} />
                <label class="row-subtitle" xalign={1} label={container.memory} />
            </box>
            <box class="row-actions" spacing={6}>
                <ActionButton label="Start" command={`docker start ${shQuote(container.name)}`} />
                <ActionButton label="Stop" command={`docker stop ${shQuote(container.name)}`} destructive />
                <ActionButton label="Restart" command={`docker restart ${shQuote(container.name)}`} />
                <button class="mini-button" label="Logs" onClicked={() => loadContainerLogs(container.name)} />
                <button class="mini-button" label="Shell" onClicked={() => openContainerShell(container.name)} />
            </box>
        </box>
    );
}

function DockerPage() {
    return (
        <box orientation={Gtk.Orientation.VERTICAL} spacing={12}>
            <box class="grid" spacing={10}>
                <Card title="Running" value={`${status?.dockerRunning || 0}`} />
                <Card title="Stopped" value={`${status?.dockerStopped || 0}`} />
                <Card title="Unhealthy" value={`${status?.dockerUnhealthy || 0}`} />
                <Card title="Auto Update" value="Button vorbereitet" />
            </box>
            <box class="actions" spacing={8}>
                <ActionButton label="Docker Service restart" command="/etc/rc.d/rc.docker restart" destructive />
                <ActionButton label="Auto-Update Container" command="docker ps -q | xargs -r docker update --restart unless-stopped" />
            </box>
            <box orientation={Gtk.Orientation.VERTICAL} spacing={8}>
                {(status?.dockerContainers || []).map(container => <ContainerRow container={container} />)}
            </box>
        </box>
    );
}

function ServicesPage() {
    const firstRow = serviceChecks.slice(0, 4);
    const secondRow = serviceChecks.slice(4);

    return (
        <box orientation={Gtk.Orientation.VERTICAL} spacing={12}>
            {[firstRow, secondRow].map(row => (
                <box class="service-grid" spacing={10}>
                    {row.map(service => (
                    <box class="service-card" orientation={Gtk.Orientation.VERTICAL} spacing={8}>
                        <box>
                            <label class="row-title" xalign={0} hexpand label={service.name} />
                            <label class={status?.serviceHealth[service.name]?.startsWith("2") || status?.serviceHealth[service.name]?.startsWith("3") ? "pill ok" : "pill warn"} label={status?.serviceHealth[service.name] || service.hint} />
                        </box>
                        <label class="row-subtitle" xalign={0} label={service.url} />
                        <box spacing={8}>
                            <button class="button" label="Open UI" onClicked={() => openUrl(service.url)} />
                            <ActionButton label="Restart Service" command={restartServiceCommand(service.query)} />
                        </box>
                    </box>
                    ))}
                </box>
            ))}
        </box>
    );
}

function StoragePage() {
    return (
        <box orientation={Gtk.Orientation.VERTICAL} spacing={12}>
            <box class="grid" spacing={10}>
                <Card title="Array Status" value={status?.array || "n/a"} />
                <Card title="Parity Status" value={status?.parity || "n/a"} />
                <Card title="SMART" value={status?.smart || "n/a"} />
            </box>
            <box orientation={Gtk.Orientation.VERTICAL} spacing={8}>
                {(status?.disks || []).map(disk => (
                    <box class="table-row" spacing={10}>
                        <box orientation={Gtk.Orientation.VERTICAL} spacing={3} hexpand>
                            <label class="row-title" xalign={0} label={disk.mount} />
                            <label class="row-subtitle" xalign={0} label={`${disk.filesystem} · ${disk.used} / ${disk.size} · ${disk.available} free`} />
                            <box class="meter"><box class="meter-fill" widthRequest={Math.max(4, Math.round(disk.percent * 2.8))} /></box>
                        </box>
                        <label class="percent-label" label={`${disk.percent}%`} />
                        <box class="row-actions" spacing={6}>
                            <ActionButton label="Spin Down" command={`hdparm -y ${shQuote(disk.filesystem)}`} destructive />
                            <ActionButton label="SMART Test" command={`smartctl -t short ${shQuote(disk.filesystem)}`} />
                        </box>
                    </box>
                ))}
            </box>
        </box>
    );
}

function NetworkPage() {
    const networkMax = Math.max(1024, ...history.map(sample => Math.max(sample.networkRxRate, sample.networkTxRate)));
    const latest = history[history.length - 1];

    return (
        <box orientation={Gtk.Orientation.VERTICAL} spacing={12}>
            <box class="live-strip" spacing={10}>
                <ChartCard title="Traffic RX" value={formatRate(latest?.networkRxRate || 0)} values={history.map(sample => sample.networkRxRate)} maxValue={networkMax} accent="network" />
                <ChartCard title="Traffic TX" value={formatRate(latest?.networkTxRate || 0)} values={history.map(sample => sample.networkTxRate)} maxValue={networkMax} accent="network" />
                <Card title="Host" value={`${status?.host || UNRAID_HOST}\n${UNRAID_HOST}`} />
            </box>
            <box class="grid" spacing={10}>
                <Card title="Interfaces" value={status?.network || "n/a"} />
                <Card title="Docker Networks" value={status?.dockerNetworks || "n/a"} />
                <Card title="Ports / Listening" value={status?.ports || "n/a"} />
            </box>
        </box>
    );
}

function SystemPage() {
    return (
        <box orientation={Gtk.Orientation.VERTICAL} spacing={12}>
            <box class="grid" spacing={10}>
                <Card title="Uptime" value={status?.uptime || "n/a"} />
                <Card title="Kernel" value={status?.kernel || "n/a"} />
                <Card title="Updates" value={status?.updates || "n/a"} />
            </box>
            <box class="actions" spacing={8}>
                <ActionButton label="Reboot" command="reboot" destructive />
                <ActionButton label="Shutdown" command="poweroff" destructive />
                <ActionButton label="Docker Service restart" command="/etc/rc.d/rc.docker restart" destructive />
                <ActionButton label="Update Unraid" command="echo 'Unraid update must be started from Web UI'" />
                <button class="button" label="Plugin Manager" onClicked={() => openUrl(`http://${UNRAID_HOST}/Plugins`)} />
            </box>
        </box>
    );
}

function PlaceholderPage({ title, body }: { title: string; body: string }) {
    return (
        <box orientation={Gtk.Orientation.VERTICAL} spacing={12}>
            <Card title={title} value={body} className="wide-card" />
        </box>
    );
}

function LogsPage() {
    return (
        <box orientation={Gtk.Orientation.VERTICAL} spacing={12}>
            <Card title={logTitle} value={logOutput} className="log-card" />
        </box>
    );
}

function CurrentPage() {
    if (activePage === "dashboard") return <DashboardPage />;
    if (activePage === "docker") return <DockerPage />;
    if (activePage === "services") return <ServicesPage />;
    if (activePage === "storage") return <StoragePage />;
    if (activePage === "network") return <NetworkPage />;
    if (activePage === "system") return <SystemPage />;
    if (activePage === "logs") return <LogsPage />;
    if (activePage === "automations") return <PlaceholderPage title="Automationen" body="n8n, Cronjobs und Event-Flows bekommen hier spaeter Restart, Health und Run-History." />;
    if (activePage === "auth") return <PlaceholderPage title="Auth / User" body="User, Tokens, SSO und Authentik-Status. Struktur ist vorbereitet, sichere Mutationen kommen separat." />;
    if (activePage === "integrations") return <PlaceholderPage title="Integrationen" body="Home Assistant, Authentik, Reverse Proxy und externe APIs als Health-Layer." />;
    if (activePage === "ai") return <PlaceholderPage title="AI / Agent" body="AI Diagnose fuer Container, Logs und Alerts. Erst Daten sammeln, dann richtig schlau machen." />;
    return <PlaceholderPage title="Monitoring Advanced" body="Langzeit-History, Prometheus/Grafana Bridge und feinere Sensoren passen hier rein." />;
}

function DashboardView() {
    const active = navItems.find(item => item.id === activePage);
    const layout = windowLayout();

    return (
        <box class="shell" widthRequest={layout.width} heightRequest={layout.height}>
            <box class="sidebar" orientation={Gtk.Orientation.VERTICAL} spacing={6}>
                <box class="brand" orientation={Gtk.Orientation.VERTICAL} spacing={2}>
                    <label class="brand-title" xalign={0} label="Homelab" />
                    <label class="brand-subtitle" xalign={0} label={status?.host || UNRAID_HOST} />
                </box>
                {navItems.map(item => <NavButton id={item.id} icon={item.icon} label={item.label} />)}
            </box>
            <box class="content" orientation={Gtk.Orientation.VERTICAL} spacing={14} hexpand vexpand>
                <box class="header">
                    <box orientation={Gtk.Orientation.VERTICAL} hexpand>
                        <label class="title" xalign={0} label={active?.label || "Dashboard"} />
                        <label class="subtitle" xalign={0} label={`${status?.updated || "nie"} · live in ${secondsUntilRefresh()}s · ${UNRAID_USER}@${UNRAID_HOST}`} />
                    </box>
                    <button class="icon-button" tooltipText="Aktualisieren" sensitive={!busy} onClicked={() => refresh()}>
                        <label label="" />
                    </button>
                    <button class="icon-button close" tooltipText="Schliessen" onClicked={() => app.quit()}>
                        <label label="" />
                    </button>
                </box>
                {errorMessage ? <label class="error" xalign={0} wrap label={errorMessage} /> : <box />}
                <scrolledwindow
                    class="page-scroll"
                    hexpand
                    vexpand
                    widthRequest={layout.pageWidth}
                    heightRequest={layout.pageHeight}
                    hscrollbarPolicy={Gtk.PolicyType.AUTOMATIC}
                    vscrollbarPolicy={Gtk.PolicyType.AUTOMATIC}
                >
                    <CurrentPage />
                </scrolledwindow>
            </box>
        </box>
    );
}

function startRefreshTimer() {
    if (timerStarted) {
        return;
    }

    timerStarted = true;
    GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => {
        if (authed && !busy && Date.now() >= nextRefreshAt) {
            refresh();
        } else if (authed) {
            rebuild();
        }

        return GLib.SOURCE_CONTINUE;
    });
}

function HomelabWindow() {
    return (
        <window
            name="homelab-control"
            namespace="homelab-control"
            class="homelab-window"
            visible
            keymode={Astal.Keymode.EXCLUSIVE}
            anchor={Astal.WindowAnchor.TOP}
            application={app}
        >
            <Gtk.EventControllerKey onKeyPressed={(_, keyval) => {
                if (keyval === ESC_KEYVAL) {
                    app.quit();
                    return true;
                }
                return false;
            }} />
            <box marginTop={WINDOW_MARGIN_TOP}>
                {authed ? <DashboardView /> : <LoginView />}
            </box>
        </window>
    );
}

function rebuild() {
    const win = app.get_window("homelab-control");
    if (!win) {
        return;
    }
    win.set_child(<box marginTop={WINDOW_MARGIN_TOP}>{authed ? <DashboardView /> : <LoginView />}</box> as Gtk.Widget);
}

app.start({
    css,
    instanceName: "homelab-control",
    main() {
        startRefreshTimer();
        HomelabWindow();
    },
});
