import app from "ags/gtk4/app";
import { Astal, Gtk } from "ags/gtk4";
import { execAsync } from "ags/process";
import css from "./package-manager.css";

const WINDOW_MARGIN_TOP = 48;
const ESC_KEYVAL = 65307;

type Helper = "pacman" | "paru";

type PackageResult = {
    repo: string;
    name: string;
    version: string;
    installed: boolean;
    description: string;
};

let helper: Helper = "paru";
let query = "";
let results: PackageResult[] = [];
let busy = false;
let statusMessage = "Suchbegriff eingeben und Enter druecken.";
let errorMessage = "";

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

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

function runShell(command: string) {
    return execAsync(["bash", "-lc", command]);
}

function setBusy(value: boolean) {
    busy = value;
    rebuild();
}

function helperAvailable(nextHelper: Helper) {
    return runShell(`command -v ${nextHelper} >/dev/null 2>&1`);
}

function parseSearch(output: string) {
    const parsed: PackageResult[] = [];
    const lines = output.split("\n");

    for (let index = 0; index < lines.length; index += 1) {
        const header = lines[index];
        const match = header.match(/^([^/\s]+)\/([^\s]+)\s+([^\s]+)(?:\s+\[(installed[^\]]*)\])?/);

        if (!match) {
            continue;
        }

        const descriptionLines: string[] = [];
        let next = index + 1;
        while (next < lines.length && /^\s+/.test(lines[next])) {
            descriptionLines.push(lines[next].trim());
            next += 1;
        }

        parsed.push({
            repo: match[1],
            name: match[2],
            version: match[3],
            installed: Boolean(match[4]),
            description: descriptionLines.join(" ") || "Keine Beschreibung.",
        });

        index = next - 1;
    }

    return parsed.slice(0, 80);
}

function searchPackages() {
    const term = query.trim();
    if (!term) {
        results = [];
        statusMessage = "Suchbegriff fehlt.";
        rebuild();
        return;
    }

    setBusy(true);
    errorMessage = "";
    statusMessage = `Suche mit ${helper} nach "${term}"...`;

    helperAvailable(helper)
        .then(() => runShell(`${helper} -Ss ${shQuote(term)} 2>/dev/null || true`))
        .then(output => {
            results = parseSearch(output);
            statusMessage = results.length
                ? `${results.length} Treffer fuer "${term}" mit ${helper}.`
                : `Keine Treffer fuer "${term}".`;
        })
        .catch(error => {
            console.error(error);
            results = [];
            errorMessage = `${helper} ist nicht verfuegbar oder die Suche ist fehlgeschlagen.`;
            statusMessage = "Suche fehlgeschlagen.";
        })
        .finally(() => {
            setBusy(false);
        });
}

function terminalCommand(command: string) {
    const hold = `${command}; printf '\\n'; read -r -p 'Enter zum Schliessen... ' _`;
    return `kitty --title ${shQuote("Paketmanager")} sh -lc ${shQuote(hold)} || alacritty -e sh -lc ${shQuote(hold)} || foot sh -lc ${shQuote(hold)} || xterm -e sh -lc ${shQuote(hold)}`;
}

function installPackage(pkg: PackageResult) {
    const command = helper === "pacman"
        ? `sudo pacman -S ${shQuote(pkg.name)}`
        : `paru -S ${shQuote(pkg.name)}`;

    notify(`Installation gestartet: ${pkg.name}`);
    runShell(terminalCommand(command)).catch(error => {
        console.error(error);
        notify("Kein Terminal fuer Paketinstallation gefunden.");
    });
}

function updateSystem() {
    const command = helper === "pacman"
        ? "sudo pacman -Syu"
        : "paru -Syu";

    notify(`Update gestartet mit ${helper}.`);
    runShell(terminalCommand(command)).catch(error => {
        console.error(error);
        notify("Kein Terminal fuer Updates gefunden.");
    });
}

function HelperButton({ id, label }: { id: Helper; label: string }) {
    return (
        <button
            class={helper === id ? "tool-button active" : "tool-button"}
            onClicked={() => {
                helper = id;
                statusMessage = `Aktiver Helper: ${helper}`;
                rebuild();
            }}
            label={label}
        />
    );
}

function PackageRow({ pkg }: { pkg: PackageResult }) {
    return (
        <box class="package-row" spacing={10}>
            <box orientation={Gtk.Orientation.VERTICAL} spacing={4} hexpand>
                <box spacing={8}>
                    <label class="package-name" xalign={0} label={pkg.name} />
                    <label class="repo-pill" label={pkg.repo} />
                    {pkg.installed ? <label class="installed-pill" label="installed" /> : <box />}
                </box>
                <label class="package-meta" xalign={0} label={pkg.version} />
                <label class="package-desc" xalign={0} wrap label={pkg.description} />
            </box>
            <button class="button primary" sensitive={!busy} onClicked={() => installPackage(pkg)} label="Installieren" />
        </box>
    );
}

function PackageManagerWindow() {
    return (
        <window
            name="package-manager"
            namespace="package-manager"
            class="package-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 class="package-panel" orientation={Gtk.Orientation.VERTICAL} spacing={14} marginTop={WINDOW_MARGIN_TOP}>
                <box class="header">
                    <box orientation={Gtk.Orientation.VERTICAL} hexpand>
                        <label class="title" xalign={0} label="Paket Installation / Updates" />
                        <label class="subtitle" xalign={0} label="Pacman und AUR Suche, Installation im Terminal." />
                    </box>
                    <button class="icon-button close" tooltipText="Schliessen" onClicked={() => app.quit()}>
                        <label label="" />
                    </button>
                </box>
                <box class="toolbar" spacing={8}>
                    <entry
                        hexpand
                        placeholderText="Paket suchen, z.B. firefox, docker, obsidian..."
                        onChanged={entry => {
                            query = entry.get_text();
                        }}
                        onActivate={entry => {
                            query = entry.get_text();
                            searchPackages();
                        }}
                    />
                    <button class="button primary" sensitive={!busy} onClicked={searchPackages} label={busy ? "Suche..." : "Suchen"} />
                </box>
                <box class="toolbar" spacing={8}>
                    <HelperButton id="paru" label="paru" />
                    <HelperButton id="pacman" label="pacman" />
                    <button class="button" sensitive={!busy} onClicked={updateSystem} label="System updaten" />
                </box>
                <box class="status-strip">
                    <label class={errorMessage ? "error" : "muted"} xalign={0} hexpand label={errorMessage || statusMessage} />
                </box>
                <scrolledwindow
                    class="results-scroll"
                    hexpand
                    vexpand
                    hscrollbarPolicy={Gtk.PolicyType.NEVER}
                    vscrollbarPolicy={Gtk.PolicyType.AUTOMATIC}
                >
                    {results.length
                        ? (
                            <box orientation={Gtk.Orientation.VERTICAL} spacing={8}>
                                {results.map(pkg => <PackageRow pkg={pkg} />)}
                            </box>
                        )
                        : (
                            <box class="empty">
                                <label hexpand xalign={0.5} label="Noch keine Suchergebnisse." />
                            </box>
                        )}
                </scrolledwindow>
            </box>
        </window>
    );
}

function rebuild() {
    const win = app.get_window("package-manager");
    if (!win) {
        return;
    }
    win.set_child(
        <box class="package-panel" orientation={Gtk.Orientation.VERTICAL} spacing={14} marginTop={WINDOW_MARGIN_TOP}>
            <box class="header">
                <box orientation={Gtk.Orientation.VERTICAL} hexpand>
                    <label class="title" xalign={0} label="Paket Installation / Updates" />
                    <label class="subtitle" xalign={0} label="Pacman und AUR Suche, Installation im Terminal." />
                </box>
                <button class="icon-button close" tooltipText="Schliessen" onClicked={() => app.quit()}>
                    <label label="" />
                </button>
            </box>
            <box class="toolbar" spacing={8}>
                <entry
                    hexpand
                    text={query}
                    placeholderText="Paket suchen, z.B. firefox, docker, obsidian..."
                    onChanged={entry => {
                        query = entry.get_text();
                    }}
                    onActivate={entry => {
                        query = entry.get_text();
                        searchPackages();
                    }}
                />
                <button class="button primary" sensitive={!busy} onClicked={searchPackages} label={busy ? "Suche..." : "Suchen"} />
            </box>
            <box class="toolbar" spacing={8}>
                <HelperButton id="paru" label="paru" />
                <HelperButton id="pacman" label="pacman" />
                <button class="button" sensitive={!busy} onClicked={updateSystem} label="System updaten" />
            </box>
            <box class="status-strip">
                <label class={errorMessage ? "error" : "muted"} xalign={0} hexpand label={errorMessage || statusMessage} />
            </box>
            <scrolledwindow
                class="results-scroll"
                hexpand
                vexpand
                hscrollbarPolicy={Gtk.PolicyType.NEVER}
                vscrollbarPolicy={Gtk.PolicyType.AUTOMATIC}
            >
                {results.length
                    ? (
                        <box orientation={Gtk.Orientation.VERTICAL} spacing={8}>
                            {results.map(pkg => <PackageRow pkg={pkg} />)}
                        </box>
                    )
                    : (
                        <box class="empty">
                            <label hexpand xalign={0.5} label="Noch keine Suchergebnisse." />
                        </box>
                    )}
            </scrolledwindow>
        </box> as Gtk.Widget
    );
}

app.start({
    css,
    instanceName: "package-manager",
    main() {
        PackageManagerWindow();
    },
});
