import St from "gi://St";

import Clutter from "gi://Clutter";

import GLib from "gi://GLib";

import Gio from "gi://Gio";

import { SystemMetricsService } from "../../lib/systemMetricsApi.js";

import { readTextFileAsync } from "../../lib/fsUtils.js";

import { SHADOW_DEFAULTS, shadowBoxShadowCss as _shadowBoxShadowCss, toCssColor as _toCssColor, TEXT_SHADOW_DEFAULTS, textShadowCss as _textShadowCss, cardStyleCss as _cardStyleCss, BORDER_DEFAULTS, OPACITY_DEFAULTS } from "../../lib/widgetVisualKit.js";

import { createLayeredCard, applyLayeredCardStyle } from "../../lib/shell/cardLayers.js";

import {configJsonDefaults} from '../../lib/widgetConfigDefaults.js';
const REFRESH_INTERVAL_SECONDS = 30;

const CARD_PADDING = 16;

const DISTRO_KEYS = [ "alpine", "android", "arch", "armbian", "buildroot", "bunsenlabs", "centos", "crunchbang", "darwin", "debian", "devuan", "elementary", "endeavouros", "enso", "fedora", "freebsd", "gentoo", "guix", "kali", "linux", "linuxmint", "manjaro", "moevalent", "netbsd", "nixos", "nobara", "openbsd", "opensuse", "parabola", "pop", "quirinux", "raspbian", "rhel", "rocky", "siduction", "slackware", "ubuntu", "univalent", "windows" ];

const DISTRO_ID_ALIASES = {
    "opensuse-leap": "opensuse",
    "opensuse-tumbleweed": "opensuse",
    suse: "opensuse",
    sles: "opensuse",
    ol: "rhel",
    rhel: "rhel",
    almalinux: "rocky",
    mint: "linuxmint",
    artix: "arch"
};

const ROW_DEFS = [ [ "os", "OS", "#61afef" ], [ "host", "Host", "#c678dd" ], [ "kernel", "Kernel", "#98c379" ], [ "uptime", "Uptime", "#e5c07b" ], [ "packages", "Packages", "#56b6c2" ], [ "shell", "Shell", "#d19a66" ], [ "resolution", "Resolution", "#e06c75" ], [ "de", "DE", "#bb9af7" ], [ "cpu", "CPU", "#f7768e" ], [ "gpu", "GPU", "#9ece6a" ], [ "memory", "Memory", "#7aa2f7" ] ];

const GENERIC_ART = {
    color: "#8be9fd"
};

export default class GeekArcheySystechSqureWidget {
    constructor(api) {
        this._api = api;
        this._settings = api.settings;
        this._metrics = new SystemMetricsService;
        this._info = {};
        this._timerId = null;
        this._cancellable = null;
    }
    buildActor() {
        this._layers = createLayeredCard({
            contentStyleClass: "geek-archey-systech-squre-root"
        });
        this._actor = this._layers.root;
        this._actor.x_align = Clutter.ActorAlign.CENTER;
        this._actor.y_align = Clutter.ActorAlign.CENTER;
        const infoBox = new St.BoxLayout({
            vertical: true,
            style_class: "geek-archey-systech-squre-info"
        });
        infoBox.set_style(`padding: ${CARD_PADDING}px; spacing: 2px;`);
        this._headerLabel = new St.Label({
            style_class: "geek-archey-systech-squre-header"
        });
        infoBox.add_child(this._headerLabel);
        this._sepLabel = new St.Label({
            style_class: "geek-archey-systech-squre-sep"
        });
        infoBox.add_child(this._sepLabel);
        this._rows = {};
        for (const [key, label, color] of ROW_DEFS) {
            const row = new St.BoxLayout({
                vertical: false,
                style_class: "geek-archey-systech-squre-row"
            });
            const bullet = new St.Label({
                text: "■",
                style_class: "geek-archey-systech-squre-bullet"
            });
            const value = new St.Label({
                style_class: "geek-archey-systech-squre-value"
            });
            row.add_child(bullet);
            row.add_child(value);
            infoBox.add_child(row);
            this._rows[key] = {
                bullet: bullet,
                value: value,
                label: label,
                color: color
            };
        }
        this._layers.content.add_child(infoBox);
        this._render();
        return this._actor;
    }
    async enable() {
        this._cancellable = new Gio.Cancellable;
        await this._ensureDistroDetected();
        this._fetchStaticInfo();
        this._updateDynamicInfo();
        this._timerId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, REFRESH_INTERVAL_SECONDS, () => {
            this._updateDynamicInfo();
            return GLib.SOURCE_CONTINUE;
        });
    }
    disable() {
        if (this._timerId !== null) {
            GLib.source_remove(this._timerId);
            this._timerId = null;
        }
        this._cancellable?.cancel();
        this._cancellable = null;
    }
    getDefaultSettings() {
        return {
            ...configJsonDefaults(import.meta.url),
            ...SHADOW_DEFAULTS,
            ...BORDER_DEFAULTS,
            ...OPACITY_DEFAULTS,
            ...TEXT_SHADOW_DEFAULTS,
            textShadowEnabled: true,
            distroDetected: false,
        };
    }
    onSettingsChanged() {
        this._render();
        this._fetchStaticInfo();
    }
    async _ensureDistroDetected() {
        if (this._settings.distroDetected) return;
        this._settings.distro = await this._detectLinuxDistro();
        this._settings.distroDetected = true;
        this._api.saveSettings?.(this._settings);
    }
    async _detectLinuxDistro() {
        try {
            const text = await readTextFileAsync("/etc/os-release");
            if (!text) return "linux";
            const clean = v => (v ?? "").trim().replace(/^"|"$/g, "");
            const id = clean(text.match(/^ID=(.*)$/m)?.[1]);
            const idLike = clean(text.match(/^ID_LIKE=(.*)$/m)?.[1]);
            const candidates = [ id, ...idLike.split(/\s+/) ].filter(Boolean);
            for (const candidate of candidates) {
                const key = DISTRO_ID_ALIASES[candidate] ?? candidate;
                if (DISTRO_KEYS.includes(key)) return key;
            }
            return "linux";
        } catch (e) {
            return "linux";
        }
    }
    _render() {
        applyLayeredCardStyle(this._layers, this._settings, {
            cornerRadiusFallback: 18
        }, false);
        const accent = GENERIC_ART.color;
        const user = GLib.get_user_name();
        const host = GLib.get_host_name();
        const textShadow = _textShadowCss(this._settings);
        this._headerLabel.set_style(`font-family: monospace; font-size: 10px; font-weight: bold; color: #ffffff;${textShadow}`);
        this._headerLabel.text = `${user}@${host}`;
        this._sepLabel.set_style(`font-family: monospace; font-size: 8px; color: ${accent}; margin-bottom: 2px;`);
        this._sepLabel.text = "─".repeat(Math.max(user.length + host.length + 1, 10));
        for (const [key, {bullet: bullet, value: value, label: label, color: color}] of Object.entries(this._rows)) {
            bullet.set_style(`font-family: monospace; font-size: 8px; color: ${color}; margin-right: 5px;`);
            value.set_style(`font-family: monospace; font-size: 8px; color: #e5e5e5;${textShadow}`);
            value.text = `${label}: ${this._info[key] ?? "…"}`;
        }
    }
    async _fetchStaticInfo() {
        this._info.os = await this._readOsPrettyName();
        this._info.kernel = await this._readKernelVersion();
        this._info.host = await this._readHostProduct() ?? GLib.get_host_name();
        this._info.cpu = await this._readCpuModel();
        this._info.shell = this._readShellName();
        this._info.resolution = this._readResolution();
        this._info.de = GLib.getenv("XDG_CURRENT_DESKTOP") ?? "GNOME";
        this._render();
        const distroKey = this._settings.distro ?? "linux";
        this._runCommandAsync(this._packageCommand(distroKey)).then(out => {
            const count = parseInt(out, 10);
            this._info.packages = Number.isFinite(count) ? String(count) : "Unknown";
            this._render();
        });
        this._runCommandAsync([ "bash", "-lc", "lspci 2>/dev/null | grep -Ei 'vga|3d|display' | head -n1 | sed -E 's/^[^:]+: //'" ]).then(out => {
            this._info.gpu = out || "Unknown";
            this._render();
        });
        const shellPath = GLib.getenv("SHELL") ?? "/bin/bash";
        this._runCommandAsync([ shellPath, "--version" ]).then(out => {
            const match = out.match(/(\d+\.\d+(?:\.\d+)?)/);
            if (match) this._info.shell = `${this._readShellName()} ${match[1]}`;
            this._render();
        });
    }
    async _updateDynamicInfo() {
        this._info.uptime = this._formatUptime(await this._readUptimeSeconds());
        const {totalKb: totalKb, usedKb: usedKb} = await this._metrics.getMemoryUsage();
        this._info.memory = `${this._formatGb(usedKb)}/${this._formatGb(totalKb)}`;
        this._render();
    }
    _packageCommand(distroKey) {
        const PACMAN = [ "bash", "-lc", "pacman -Qq 2>/dev/null | wc -l" ];
        const DPKG = [ "bash", "-lc", "dpkg -l 2>/dev/null | grep -c '^ii'" ];
        const RPM = [ "bash", "-lc", "rpm -qa 2>/dev/null | wc -l" ];
        const APK = [ "bash", "-lc", "apk info 2>/dev/null | wc -l" ];
        const ZYPPER = [ "bash", "-lc", "rpm -qa 2>/dev/null | wc -l" ];
        const PORTAGE = [ "bash", "-lc", "qlist -I 2>/dev/null | wc -l" ];
        const NIX = [ "bash", "-lc", "nix-store -q --requisites /run/current-system/sw 2>/dev/null | wc -l" ];
        const GUIX = [ "bash", "-lc", "guix package --list-installed 2>/dev/null | wc -l" ];
        const PKG_BSD = [ "bash", "-lc", "pkg info 2>/dev/null | wc -l" ];
        const SLACKPKG = [ "bash", "-lc", "ls /var/log/packages 2>/dev/null | wc -l" ];
        const GENERIC = [ "bash", "-lc", "if command -v pacman >/dev/null; then pacman -Qq | wc -l; " + "elif command -v dpkg >/dev/null; then dpkg -l | grep -c '^ii'; " + "elif command -v rpm >/dev/null; then rpm -qa | wc -l; " + "elif command -v apk >/dev/null; then apk info | wc -l; " + "elif command -v guix >/dev/null; then guix package --list-installed | wc -l; " + "elif command -v nix-store >/dev/null; then nix-store -q --requisites /run/current-system/sw | wc -l; " + "elif command -v pkg >/dev/null; then pkg info | wc -l; " + "else echo 0; fi" ];
        const ARCH_BASED = [ "arch", "manjaro", "endeavouros" ];
        const DEBIAN_BASED = [ "debian", "ubuntu", "linuxmint", "pop", "devuan", "kali", "raspbian", "bunsenlabs", "crunchbang", "siduction", "quirinux", "elementary", "armbian" ];
        const RPM_BASED = [ "fedora", "centos", "rhel", "rocky", "nobara" ];
        if (ARCH_BASED.includes(distroKey)) return PACMAN;
        if (DEBIAN_BASED.includes(distroKey)) return DPKG;
        if (RPM_BASED.includes(distroKey)) return RPM;
        switch (distroKey) {
          case "alpine":
            return APK;

          case "opensuse":
            return ZYPPER;

          case "gentoo":
            return PORTAGE;

          case "nixos":
            return NIX;

          case "guix":
            return GUIX;

          case "freebsd":
          case "netbsd":
          case "openbsd":
            return PKG_BSD;

          case "slackware":
            return SLACKPKG;

          case "darwin":
          case "windows":
          case "android":
          case "linux":
          case "buildroot":
          case "enso":
          case "moevalent":
          case "univalent":
          case "parabola":
          default:
            return GENERIC;
        }
    }
    _runCommandAsync(argv) {
        return new Promise(resolve => {
            try {
                const proc = Gio.Subprocess.new(argv, Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_SILENCE);
                proc.communicate_utf8_async(null, this._cancellable, (source, res) => {
                    try {
                        const [, stdout] = source.communicate_utf8_finish(res);
                        resolve((stdout ?? "").trim());
                    } catch (e) {
                        resolve("");
                    }
                });
            } catch (e) {
                resolve("");
            }
        });
    }
    async _readOsPrettyName() {
        try {
            const text = await readTextFileAsync("/etc/os-release");
            if (!text) return "Linux";
            const match = text.match(/^PRETTY_NAME="?([^"\n]+)"?/m);
            return match ? match[1] : "Linux";
        } catch (e) {
            return "Linux";
        }
    }
    async _readKernelVersion() {
        try {
            const text = await readTextFileAsync("/proc/sys/kernel/osrelease");
            return text ? text.trim() : "Unknown";
        } catch (e) {
            return "Unknown";
        }
    }
    async _readHostProduct() {
        try {
            const text = await readTextFileAsync("/sys/class/dmi/id/product_name");
            const trimmed = text ? text.trim() : "";
            return trimmed || null;
        } catch (e) {
            return null;
        }
    }
    async _readCpuModel() {
        try {
            const text = await readTextFileAsync("/proc/cpuinfo");
            if (!text) return "Unknown";
            const match = text.match(/^model name\s*:\s*(.+)$/m);
            return match ? match[1].replace(/\s+/g, " ").trim() : "Unknown";
        } catch (e) {
            return "Unknown";
        }
    }
    _readShellName() {
        const shellPath = GLib.getenv("SHELL");
        if (!shellPath) return "Unknown";
        return shellPath.split("/").pop();
    }
    _readResolution() {
        try {
            const monitorIndex = global.display.get_primary_monitor();
            const geometry = global.display.get_monitor_geometry(monitorIndex);
            return `${geometry.width}x${geometry.height}`;
        } catch (e) {
            return "Unknown";
        }
    }
    async _readUptimeSeconds() {
        try {
            const text = await readTextFileAsync("/proc/uptime");
            if (!text) return 0;
            return parseFloat(text.split(" ")[0]) || 0;
        } catch (e) {
            return 0;
        }
    }
    _formatUptime(seconds) {
        const days = Math.floor(seconds / 86400);
        const hours = Math.floor(seconds % 86400 / 3600);
        const minutes = Math.floor(seconds % 3600 / 60);
        const parts = [];
        if (days > 0) parts.push(`${days}d`);
        if (days > 0 || hours > 0) parts.push(`${hours}h`);
        parts.push(`${minutes}m`);
        return parts.join(" ");
    }
    _formatGb(kb) {
        const gb = kb / 1024 / 1024;
        return `${gb < 10 ? gb.toFixed(1) : Math.round(gb)}GB`;
    }
}