/**
 * Cross-platform tray-icon facade shared by the MCP server (src/index.ts)
 * and the standalone settings daemon (src/settings-main.ts).
 *
 * Two backends with automatic selection:
 *
 *   1. **Native** — a tauri-apps/tray-icon Rust crate wrapped via
 *      napi-rs (`native/tray/`). Renders correctly on modern GNOME
 *      (proper IconThemePath + .png file extension), macOS NSStatusBar,
 *      and Win32 Shell_NotifyIcon. Preferred when a prebuilt for the
 *      current platform is present.
 *   2. **systray2 (legacy)** — Go-binary fallback. Has a known GNOME
 *      rendering bug (3-dot ellipsis instead of the icon) but works on
 *      macOS / Windows when the native prebuilt isn't available.
 *
 * Selection is automatic in `createTray()`: try native, fall back to
 * systray2 on `require()` failure or on a `loadError`. systray2's
 * boot hygiene (chmod the binary, skip headless hosts) lives in
 * `preflightTrayBinary()` / `trayPreconditionSkip()` below — those
 * helpers are still exported for direct callers that want to gate
 * tray init themselves.
 */
import { existsSync, chmodSync, readFileSync } from "fs";
import { homedir } from "os";
import nodePath from "path";
import { createRequire } from "module";
import { fileURLToPath } from "url";
const _require = createRequire(import.meta.url);
/**
 * Try to load the native tray addon. Returns null if unavailable —
 * happens when no `.node` prebuilt for the current platform/arch is
 * present in `native/tray/`.
 *
 * Current packaging: the five committed prebuilts (linux-x64-gnu,
 * linux-arm64-gnu, darwin-arm64, win32-x64-msvc, win32-arm64-msvc)
 * ship inside the main mailpouch npm package via `package.json`'s
 * `files` field. The darwin-x64 target is not yet built and falls
 * through to the systray2 backend. A future PR may switch to
 * the napi-rs-standard pattern of one `@mailpouch/tray-native-*`
 * subpackage per target wired via `optionalDependencies` — keeping
 * the install size small for end users — but for now everything
 * lives in-tree for simpler CI plumbing.
 *
 * Module location resolution:
 *   1. `@mailpouch/tray-native` — present only if a future PR splits
 *      prebuilts into subpackages
 *   2. `../../native/tray` relative to compiled `dist/utils/tray.js`
 *      — the in-repo build (cargo + napi build produce index.js +
 *      the per-platform .node sibling)
 */
function _loadNativeTray() {
    const candidates = [
        "@mailpouch/tray-native",
        // dist/utils/tray.js — go up two to reach the project root, then
        // into native/tray. Three `..` would over-shoot to /mnt/data/Code/.
        nodePath.resolve(nodePath.dirname(fileURLToPath(import.meta.url)), "..", "..", "native", "tray"),
    ];
    for (const id of candidates) {
        try {
            const mod = _require(id);
            if (mod && typeof mod.Tray === "function")
                return mod;
        }
        catch { /* try the next candidate */ }
    }
    return null;
}
// ─── systray2 (legacy backend) preflight helpers ────────────────────────
function _binaryBasename(platform) {
    return platform === "win32" ? "tray_windows_release.exe"
        : platform === "darwin" ? "tray_darwin_release"
            : "tray_linux_release";
}
/**
 * Resolve + chmod 0755 the systray2 native binary in both possible
 * locations (package source, runtime cache). Idempotent; no-op on
 * Windows. Returns the source path inside node_modules, or null when
 * systray2 isn't installed / isn't resolvable.
 */
export function preflightTrayBinary(platform = process.platform) {
    const basename = _binaryBasename(platform);
    let resolvedSource = null;
    try {
        const moduleDir = nodePath.dirname(_require.resolve("systray2/package.json"));
        const srcPath = nodePath.join(moduleDir, "traybin", basename);
        if (existsSync(srcPath)) {
            resolvedSource = srcPath;
            if (platform !== "win32") {
                try {
                    chmodSync(srcPath, 0o755);
                }
                catch { /* non-fatal */ }
            }
        }
    }
    catch { /* systray2 not installed */ }
    if (platform !== "win32") {
        try {
            const pkg = _require("systray2/package.json");
            const version = pkg.version ?? "0";
            const cachePath = nodePath.join(homedir(), ".cache", "node-systray", version, basename);
            if (existsSync(cachePath)) {
                try {
                    chmodSync(cachePath, 0o755);
                }
                catch { /* non-fatal */ }
            }
        }
        catch { /* non-fatal */ }
    }
    return resolvedSource;
}
/**
 * MCPs are commonly spawned by GUI clients (Claude Desktop, VS Code)
 * that strip `DISPLAY` / `WAYLAND_DISPLAY` from the child env, even
 * when the user running the client is on a graphical session. Without
 * those variables GTK can't connect to the display server and the
 * tray precondition check bails.
 *
 * This reads the parent's `/proc/<ppid>/environ` on Linux and copies
 * any missing display-related vars into `process.env` so downstream
 * tray initialization can see them. No-op on non-Linux platforms and
 * when the current process already has them.
 *
 * Mutates `process.env` on purpose — GTK reads the vars via getenv()
 * at window-creation time, not via a copy we control. Returns true
 * if anything was inherited.
 */
export function inheritDisplayFromParent(platform = process.platform) {
    if (platform !== "linux")
        return false;
    if (process.env.DISPLAY || process.env.WAYLAND_DISPLAY)
        return false;
    const ppid = process.ppid;
    if (!ppid || ppid === 1)
        return false;
    try {
        const raw = readFileSync(`/proc/${ppid}/environ`, "utf8");
        const pairs = raw.split("\0");
        let inherited = false;
        for (const line of pairs) {
            const eq = line.indexOf("=");
            if (eq <= 0)
                continue;
            const key = line.slice(0, eq);
            const val = line.slice(eq + 1);
            if ((key === "DISPLAY" || key === "WAYLAND_DISPLAY" ||
                key === "XDG_RUNTIME_DIR" || key === "XAUTHORITY" ||
                key === "XDG_SESSION_TYPE" || key === "DBUS_SESSION_BUS_ADDRESS") &&
                !process.env[key]) {
                process.env[key] = val;
                inherited = true;
            }
        }
        return inherited;
    }
    catch {
        return false;
    }
}
/**
 * Generic, backend-agnostic skip check — returns a one-line DEBUG
 * reason when the host can't show a system tray at all (headless
 * Linux with no X11/Wayland), or null when a tray is feasible.
 *
 * Use this BEFORE calling `createTray()` since it applies regardless
 * of which backend wins. The `systray2`-specific arm64 / x86_64-only
 * concerns live in `systray2PreconditionSkip` below.
 */
export function trayPreconditionSkip(platform = process.platform, _arch = process.arch, env = process.env) {
    if (platform === "linux" && !env.DISPLAY && !env.WAYLAND_DISPLAY) {
        return "no display environment (DISPLAY / WAYLAND_DISPLAY unset) — tray skipped";
    }
    return null;
}
/**
 * systray2-specific skip check. Used by the createTray fallback path
 * to decide whether the systray2 backend is even worth attempting on
 * this host — the native backend may have already covered an arm64
 * platform that systray2 can't.
 */
export function systray2PreconditionSkip(platform = process.platform, arch = process.arch) {
    if ((platform === "darwin" || platform === "linux") && arch === "arm64") {
        const bin = preflightTrayBinary(platform);
        if (!bin) {
            return `arm64 ${platform} host — systray2 ships x86_64 binaries only; tray disabled (run via Rosetta, or see systray2 upstream for arm64 builds)`;
        }
    }
    return null;
}
// ─── Unified tray facade ────────────────────────────────────────────────
/**
 * Create a system-tray icon. Picks the best available backend:
 *   1. Native (tauri-apps/tray-icon via napi-rs) when the prebuilt is
 *      installed for this platform — proper rendering on modern GNOME,
 *      retina-aware on macOS, hi-DPI on Windows.
 *   2. systray2 fallback when the native prebuilt isn't available
 *      (happens during development before CI publishes prebuilts, or
 *      on niche platforms we haven't built for).
 *
 * Throws when neither backend can be used (headless host with no
 * display, ARM Mac without Rosetta, etc.). Callers should treat the
 * tray as a nice-to-have and catch the error to log + continue
 * without it.
 */
export function createTray(opts) {
    const native = _loadNativeTray();
    if (native) {
        const inst = new native.Tray(opts.iconPng, opts.tooltip, opts.items, opts.onClick);
        return {
            backend: "native",
            setMenu: (items) => inst.setMenu(items),
            setIcon: (bytes) => inst.setIcon(bytes),
            setTooltip: (s) => inst.setTooltip(s),
            destroy: () => inst.destroy(),
        };
    }
    return _createSystray2Fallback(opts);
}
/**
 * Adapter that wraps the legacy systray2 module behind the unified
 * TrayHandle interface. Kept here (not exposed) so all the
 * platform-quirky systray2 nuances stay isolated to one file.
 */
function _createSystray2Fallback(opts) {
    // Apply systray2's boot hygiene (chmod the binary, etc.) before
    // construction. The systray2-specific arm64 / x86_64-only check
    // lives in `systray2PreconditionSkip` — if it returns a reason,
    // the systray2 backend can't start on this host.
    const skip = systray2PreconditionSkip();
    if (skip) {
        throw new Error(`Tray cannot start: ${skip}`);
    }
    preflightTrayBinary();
    // systray2 uses platform-format bytes (multi-res ICO on Windows for
    // crisp DPI scaling, PNG elsewhere). Caller can override; otherwise
    // we accept the PNG and let Windows downsample.
    const initialBytes = opts.iconLegacyOverride ?? opts.iconPng;
    let SysTray;
    try {
        SysTray = _require("systray2").default;
    }
    catch (e) {
        throw new Error(`No tray backend available: native prebuilt not installed AND systray2 not loadable (${e.message}). ` +
            `Either ship a @mailpouch/tray-native-<platform> prebuilt or install systray2 to enable the tray.`);
    }
    // Map our generic items to systray2's shape. Track id↔title for the
    // click callback — systray2 dispatches by visible title, not by id.
    const titleToId = new Map();
    const sysItems = opts.items.map((i) => {
        if (i.separator)
            return SysTray.separator;
        titleToId.set(i.label, i.id);
        return { title: i.label, tooltip: "", checked: false, enabled: i.enabled !== false };
    });
    // Track current icon + tooltip so partial updates (setIcon without
    // touching tooltip, or vice-versa) don't clobber the other field.
    // systray2's `update-menu` action replaces the WHOLE menu config, so
    // every send has to include both values; without local state we'd
    // revert to the original `opts.iconPng` / `opts.tooltip` on each
    // update call.
    let currentIconB64 = initialBytes.toString("base64");
    let currentTooltip = opts.tooltip;
    const tray = new SysTray({
        menu: {
            icon: currentIconB64,
            title: "",
            tooltip: currentTooltip,
            items: sysItems,
        },
        debug: false,
        copyDir: true,
    });
    tray.ready().then(() => tray.onClick((action) => {
        const id = titleToId.get(action.item.title);
        if (id)
            opts.onClick(id);
    })).catch(() => { });
    const sendUpdate = () => {
        void tray.sendAction({
            type: "update-menu",
            menu: { icon: currentIconB64, title: "", tooltip: currentTooltip, items: sysItems },
        });
    };
    return {
        backend: "systray2",
        setMenu: (_items) => {
            // Documented on the TrayHandle interface — silent no-op on
            // this backend. Real menu updates require a native restart of
            // the systray2 binary which the wrapper isn't set up for.
        },
        setIcon: (bytes) => {
            currentIconB64 = bytes.toString("base64");
            sendUpdate();
        },
        setTooltip: (s) => {
            currentTooltip = s;
            sendUpdate();
        },
        destroy: () => { try {
            tray.kill(false);
        }
        catch { /* already gone */ } },
    };
}
//# sourceMappingURL=tray.js.map