/**
 * mailpouch — Terminal UI
 *
 * Provides interactive configuration management when a browser is not
 * available. Detects the environment at runtime and selects the best
 * rendering mode:
 *
 *   browser  — start HTTP server + auto-open system browser
 *   ansi     — full-colour interactive TUI with arrow-key navigation
 *   plain    — readline numbered-menu TUI (no escape codes)
 *   none     — print status summary and exit (piped / non-interactive)
 *
 * No external dependencies — uses only Node.js built-ins.
 */
import readline from "readline";
import { spawnSync } from "child_process";
import { Socket } from "net";
import { loadConfig, saveConfig, defaultConfig, buildPermissions, configExists, getConfigPath, } from "../config/loader.js";
import { checkConnections } from "./connection-check.js";
import { ALL_TOOLS, TOOL_CATEGORIES, } from "../config/schema.js";
import { getPendingEscalations, approveEscalation, denyEscalation, getAuditLog, } from "../permissions/escalation.js";
export function detectEnvironment() {
    const platform = process.platform;
    const isTTY = process.stdout.isTTY === true && process.stdin.isTTY === true;
    // ── ANSI colour detection ────────────────────────────────────────────────
    let hasAnsi = false;
    if (isTTY) {
        if (process.env.NO_COLOR || process.env.TERM === "dumb") {
            hasAnsi = false;
        }
        else if (process.env.FORCE_COLOR) {
            hasAnsi = true;
        }
        else if (platform === "win32") {
            // Windows Terminal, VS Code, ConEmu, JetBrains — all support ANSI
            hasAnsi = !!(process.env.WT_SESSION ||
                process.env.TERM_PROGRAM ||
                process.env.ConEmuANSI === "ON" ||
                process.env.ANSICON);
            // Windows 10 v1511+ supports ANSI natively in conhost
            if (!hasAnsi) {
                const release = process.version; // crude fallback: Node ≥20 on Win10+ is safe
                hasAnsi = parseInt(release.replace("v", "")) >= 20;
            }
        }
        else {
            // macOS / Linux: COLORTERM, non-dumb TERM, or presence of TERM at all
            hasAnsi = !!(process.env.COLORTERM || process.env.TERM);
        }
    }
    // ── Desktop / display detection ──────────────────────────────────────────
    let hasDisplay = false;
    if (platform === "darwin" || platform === "win32") {
        hasDisplay = true; // always have a browser on desktop OS
    }
    else {
        // Linux/BSD: X11 or Wayland
        hasDisplay = !!(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
    }
    // ── Mode selection ───────────────────────────────────────────────────────
    let mode;
    if (hasDisplay) {
        mode = "browser";
    }
    else if (isTTY && hasAnsi) {
        mode = "ansi";
    }
    else if (isTTY) {
        mode = "plain";
    }
    else {
        mode = "none";
    }
    return { isTTY, hasAnsi, hasDisplay, platform, mode };
}
// ─── Browser launcher ──────────────────────────────────────────────────────────
export function openBrowser(url) {
    const platform = process.platform;
    try {
        if (platform === "darwin") {
            spawnSync("open", [url], { stdio: "ignore" });
            return true;
        }
        else if (platform === "win32") {
            // `start` is a cmd.exe built-in, not an executable, so it must run through
            // a shell — shell:false here made spawn look for a `start.exe` that
            // doesn't exist and silently fail (ENOENT swallowed by stdio:"ignore").
            // The empty "" is start's title argument (required when the URL is quoted).
            spawnSync("cmd", ["/c", "start", "", url], { stdio: "ignore", shell: true });
            return true;
        }
        else {
            // Linux/BSD: try xdg-open, then fallback candidates
            for (const cmd of ["xdg-open", "sensible-browser", "x-www-browser", "gnome-open"]) {
                const r = spawnSync(cmd, [url], { stdio: "ignore" });
                if (r.status === 0 || r.error === undefined)
                    return true;
            }
        }
    }
    catch {
        // ignore — caller will print URL instead
    }
    return false;
}
// ─── Shared helpers ────────────────────────────────────────────────────────────
async function tcpCheck(host, port) {
    return new Promise((resolve) => {
        const s = new Socket();
        s.setTimeout(3000);
        s.on("connect", () => { s.destroy(); resolve(true); });
        s.on("error", () => resolve(false));
        s.on("timeout", () => { s.destroy(); resolve(false); });
        s.connect(port, host);
    });
}
function loadOrDefault() {
    return loadConfig() ?? defaultConfig();
}
/**
 * Ask a question via an existing readline interface while suppressing echoed
 * input — suitable for password fields.  Uses the caller's `rl` to avoid
 * opening a second interface on the same stdin.
 *
 * Implementation: `rl.question()` writes the prompt synchronously via
 * `_writeToOutput`, then waits for a line event.  We set `muted = true`
 * immediately after the call (prompt already written) so all subsequent
 * character output is suppressed until the user presses Enter.
 */
function mutedQuestion(rl, prompt) {
    return new Promise(resolve => {
        let muted = false;
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const orig = rl._writeToOutput?.bind(rl);
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        rl._writeToOutput = (s) => { if (!muted)
            orig?.(s); };
        rl.question(prompt, answer => {
            // Restore normal output before the next prompt
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            if (orig)
                rl._writeToOutput = orig;
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            rl.output?.write('\n'); // move to next line after hidden input
            resolve(answer);
        });
        muted = true; // suppress all further _writeToOutput calls until answer received
    });
}
/** Strip ANSI/VT escape sequences from a string (defense-in-depth for terminal output). */
function stripAnsi(s) {
    return s
        .replace(/\x1b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "")
        .replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
}
// ─── Non-interactive mode ──────────────────────────────────────────────────────
export function printNonInteractive() {
    const cfg = loadConfig();
    const path = getConfigPath();
    process.stdout.write("\nmailpouch — Settings\n");
    process.stdout.write("─".repeat(48) + "\n");
    if (cfg) {
        process.stdout.write(`Config file   : ${path}\n`);
        process.stdout.write(`Username      : ${cfg.connection.username || "(not set)"}\n`);
        process.stdout.write(`SMTP          : ${cfg.connection.smtpHost}:${cfg.connection.smtpPort}\n`);
        process.stdout.write(`IMAP          : ${cfg.connection.imapHost}:${cfg.connection.imapPort}\n`);
        process.stdout.write(`Preset        : ${cfg.permissions.preset}\n`);
        process.stdout.write(`Bridge cert   : ${cfg.connection.bridgeCertPath || "(none)"}\n`);
    }
    else {
        process.stdout.write(`Config file   : ${path} (not found — read-only defaults apply)\n`);
    }
    process.stdout.write("\nTo configure: run in an interactive terminal, or start the browser UI:\n");
    process.stdout.write("  npx mailpouch-settings\n\n");
}
// ─── Colour helpers (ANSI) ────────────────────────────────────────────────────
const C = {
    reset: "\x1b[0m",
    bold: "\x1b[1m",
    dim: "\x1b[2m",
    ul: "\x1b[4m",
    black: "\x1b[30m",
    red: "\x1b[31m",
    green: "\x1b[32m",
    yellow: "\x1b[33m",
    blue: "\x1b[34m",
    magenta: "\x1b[35m",
    cyan: "\x1b[36m",
    white: "\x1b[37m",
    gray: "\x1b[90m",
    bRed: "\x1b[91m",
    bGreen: "\x1b[92m",
    bYellow: "\x1b[93m",
    bBlue: "\x1b[94m",
    bMag: "\x1b[95m",
    bCyan: "\x1b[96m",
    bWhite: "\x1b[97m",
    bgBlue: "\x1b[44m",
    bgNav: "\x1b[48;5;17m", // deep navy background
    clrScr: "\x1b[2J\x1b[H",
    clrLine: "\x1b[2K",
    hideCur: "\x1b[?25l",
    showCur: "\x1b[?25h",
    saveCur: "\x1b[s",
    restCur: "\x1b[u",
};
function c(colour, text) {
    return C[colour] + text + C.reset;
}
/** Render a connection probe result. A port that is open but failing auth
 *  (e.g. a 454) is shown as a warning, never as a green "reachable". */
function protoLabel(pc) {
    if (!pc.reachable)
        return c("bRed", "❌ unreachable");
    if (pc.authenticated === true)
        return c("bGreen", "✅ connected");
    if (pc.authenticated === false)
        return c("yellow", "⚠ port open · auth failed" + (pc.error ? " (" + pc.error + ")" : ""));
    return C.gray + "◐ port open · auth not tested" + C.reset;
}
const MAIN_MENU = [
    { label: "View current configuration", key: "config" },
    { label: "Edit connection settings", key: "edit" },
    { label: "Set permission preset", key: "preset" },
    { label: "Escalation requests", key: "escalation" },
    { label: "Test connectivity", key: "test" },
    { label: "Open browser UI", key: "server" },
    { label: "Reset to defaults", key: "reset" },
    { label: "Quit", key: "quit" },
];
// ─── Escalation display helper ────────────────────────────────────────────────
function renderEscalationRecord(e, ansi) {
    const bold = ansi ? C.bold : "";
    const rst = ansi ? C.reset : "";
    const warn = ansi ? C.yellow : "";
    const ok = ansi ? C.bGreen : "";
    const err = ansi ? C.bRed : "";
    const gray = ansi ? C.gray : "";
    const cyan = ansi ? C.bCyan : "";
    const secsLeft = Math.max(0, Math.round((new Date(e.expiresAt).getTime() - Date.now()) / 1000));
    const minStr = `${Math.floor(secsLeft / 60)}m${secsLeft % 60}s`;
    const lines = [
        `${bold}${warn}  ⚠ Pending Escalation Request${rst}`,
        `  ${gray}ID:${rst}       ${e.id}`,
        `  ${gray}From:${rst}     ${e.currentPreset}  ${warn}→${rst}  ${cyan}${e.targetPreset}${rst}`,
        `  ${gray}Reason:${rst}   ${stripAnsi(e.reason)}`,
        `  ${gray}Expires:${rst}  in ${secsLeft < 60 ? err : warn}${minStr}${rst}`,
        "",
    ];
    if (e.newTools.length > 0) {
        lines.push(`  ${gray}New tools that will be enabled:${rst}`);
        for (const t of e.newTools)
            lines.push(`    ${warn}• ${t}${rst}`);
        lines.push("");
    }
    return lines.join("\n");
}
const PRESETS = [
    { id: "read_only", label: "Read-Only", desc: "Reading unlimited — all writes blocked" },
    { id: "supervised", label: "Supervised", desc: "All tools; sending ≤200/hr, deletion ≤20/hr, bulk ≤100/hr" },
    { id: "send_only", label: "Send-Only", desc: "Reading unlimited; send ≤50/hr — actions/deletion disabled" },
    { id: "full", label: "Full Access", desc: `All ${ALL_TOOLS.length} tools, no rate limits — full agent trust` },
];
function ansiDraw(st) {
    const out = [];
    const w = Math.min(process.stdout.columns || 80, 90);
    const pad = (s, n) => s.slice(0, n).padEnd(n);
    // Clear
    out.push(C.clrScr + C.hideCur);
    // Header
    const title = "  ✉  mailpouch — Settings";
    out.push(C.bgNav + C.bCyan + C.bold + title.padEnd(w) + C.reset + "\n");
    const cfgExists = configExists();
    const cfgPath = getConfigPath();
    const presetLabel = st.cfg.permissions.preset ?? "read_only";
    const username = st.cfg.connection.username || c("gray", "(not set)");
    out.push(C.gray + `  Config: ${cfgPath} ` +
        (cfgExists ? c("bGreen", "[exists]") : c("yellow", "[not found — defaults applied]")) +
        C.reset + "\n");
    out.push(C.gray + `  User:   ` + C.bWhite + username + C.gray +
        `   Preset: ` + C.bCyan + presetLabel + C.reset + "\n");
    out.push("\n");
    if (st.view === "main") {
        out.push(C.bold + "  Main Menu\n" + C.reset);
        out.push(C.gray + "  " + "─".repeat(w - 4) + C.reset + "\n\n");
        MAIN_MENU.forEach((item, i) => {
            const selected = i === st.menuIdx;
            const arrow = selected ? C.bCyan + "  ▶ " + C.reset : "    ";
            const text = selected
                ? C.bold + C.bWhite + item.label + C.reset
                : C.white + item.label + C.reset;
            out.push(arrow + text + "\n");
        });
        out.push("\n");
        out.push(C.gray + "  ↑↓ navigate · Enter select · Q quit" + C.reset + "\n");
    }
    else if (st.view === "config") {
        out.push(C.bold + "  Current Configuration\n" + C.reset);
        out.push(C.gray + "  " + "─".repeat(w - 4) + C.reset + "\n\n");
        const cn = st.cfg.connection;
        const rows = [
            ["Username", cn.username || "(not set)"],
            ["SMTP", `${cn.smtpHost}:${cn.smtpPort}`],
            ["IMAP", `${cn.imapHost}:${cn.imapPort}`],
            ["Bridge cert", cn.bridgeCertPath || "(none)"],
            ["SMTP token", cn.smtpToken ? "••••••••" : "(none)"],
            ["Debug", cn.debug ? "enabled" : "off"],
            ["Preset", st.cfg.permissions.preset],
        ];
        // Enabled tools per category
        out.push(C.gray + "  " + "─".repeat(w - 4) + C.reset + "\n");
        for (const [k, v] of rows) {
            out.push(`  ${C.gray}${pad(k, 14)}${C.reset}${C.bWhite}${v}${C.reset}\n`);
        }
        out.push("\n");
        out.push(C.bold + "  Tool permissions\n" + C.reset);
        for (const [catKey, cat] of Object.entries(TOOL_CATEGORIES)) {
            const tools = cat.tools;
            const perms = st.cfg.permissions.tools;
            const allEnabled = tools.every(t => perms[t]?.enabled !== false);
            const someEnabled = tools.some(t => perms[t]?.enabled !== false);
            const icon = allEnabled ? c("bGreen", "●") : someEnabled ? c("yellow", "◐") : c("red", "○");
            out.push(`  ${icon} ${C.white}${cat.label}${C.reset}` +
                `${C.gray} (${tools.length} tools)${C.reset}\n`);
        }
        out.push("\n");
        out.push(C.gray + "  Press Escape or B to go back\n" + C.reset);
    }
    else if (st.view === "preset") {
        out.push(C.bold + "  Set Permission Preset\n" + C.reset);
        out.push(C.gray + "  " + "─".repeat(w - 4) + C.reset + "\n\n");
        PRESETS.forEach((p, i) => {
            const selected = i === st.presetIdx;
            const isCurrent = p.id === st.cfg.permissions.preset;
            const arrow = selected ? C.bCyan + "  ▶ " : "    ";
            const name = selected
                ? C.bold + C.bWhite + p.label
                : C.white + p.label;
            const curr = isCurrent ? C.bGreen + " ✓ current" + C.reset : "";
            out.push(arrow + name + C.reset + curr + "\n");
            out.push(`    ${C.gray}${p.desc}${C.reset}\n\n`);
        });
        out.push(C.gray + "  ↑↓ navigate · Enter apply · Escape back\n" + C.reset);
    }
    else if (st.view === "escalation") {
        const pending = getPendingEscalations();
        out.push(C.bold + "  Escalation Requests\n" + C.reset);
        out.push(C.gray + "  " + "─".repeat(w - 4) + C.reset + "\n\n");
        if (pending.length === 0) {
            out.push(C.gray + "  No pending escalation requests.\n" + C.reset);
            const audit = getAuditLog(5);
            if (audit.length > 0) {
                out.push("\n" + C.bold + "  Recent audit events:\n" + C.reset);
                for (const e of audit) {
                    const col = e.event === "approved" ? C.bGreen : e.event === "denied" ? C.bRed : C.gray;
                    out.push(`  ${col}${e.event.padEnd(10)}${C.reset}${C.gray} ${e.fromPreset} → ${e.toPreset}${C.reset}\n`);
                }
            }
        }
        else {
            for (const e of pending) {
                out.push(renderEscalationRecord(e, true));
            }
            out.push(C.bold + "  T — approve request · D — deny request\n" + C.reset);
        }
        out.push("\n" + C.gray + "  Escape to go back\n" + C.reset);
    }
    else if (st.view === "test") {
        out.push(C.bold + "  Test Connectivity\n" + C.reset);
        out.push(C.gray + "  " + "─".repeat(w - 4) + C.reset + "\n\n");
        const cn = st.cfg.connection;
        out.push(`  SMTP  ${cn.smtpHost}:${cn.smtpPort}  `);
        if (st.testResults === null) {
            out.push(C.gray + "— press T to test" + C.reset);
        }
        else if (st.testResults.smtp === null) {
            out.push(C.yellow + "checking…" + C.reset);
        }
        else {
            out.push(protoLabel(st.testResults.smtp));
        }
        out.push("\n");
        out.push(`  IMAP  ${cn.imapHost}:${cn.imapPort}  `);
        if (st.testResults === null) {
            out.push(C.gray + "— press T to test" + C.reset);
        }
        else if (st.testResults.imap === null) {
            out.push(C.yellow + "checking…" + C.reset);
        }
        else {
            out.push(protoLabel(st.testResults.imap));
        }
        out.push("\n\n");
        if (st.testResults && st.testResults.smtp?.reachable === false && st.testResults.imap?.reachable === false) {
            out.push(c("yellow", "  ⚠  Make sure Proton Bridge is running and signed in.\n"));
            out.push(C.gray + "     Download: https://proton.me/mail/bridge\n" + C.reset);
        }
        out.push(C.gray + "  T — run test · Escape back\n" + C.reset);
    }
    else if (st.view === "server") {
        const url = `http://localhost:${st.serverPort}`;
        out.push(C.bold + "  Browser UI\n" + C.reset);
        out.push(C.gray + "  " + "─".repeat(w - 4) + C.reset + "\n\n");
        if (st.serverStarted) {
            out.push(c("bGreen", "  ✅ Server running: ") + C.bold + C.bCyan + url + C.reset + "\n\n");
            out.push(C.white + "  Open the URL above in your browser to access\n");
            out.push("  the full settings UI. Press Escape to return here.\n" + C.reset);
        }
        else {
            out.push(C.gray + "  Press S to start the browser UI server.\n" + C.reset);
        }
        out.push("\n" + C.gray + "  S — start server · Escape back\n" + C.reset);
    }
    out.push("\n");
    process.stdout.write(out.join(""));
}
export async function runAnsiTUI(serverPort, startServerFn) {
    const st = {
        view: "main",
        menuIdx: 0,
        presetIdx: 0,
        testResults: null,
        serverPort,
        serverStarted: false,
        cfg: loadOrDefault(),
        dirty: true,
    };
    // Find current preset index
    const curPresetIdx = PRESETS.findIndex(p => p.id === st.cfg.permissions.preset);
    if (curPresetIdx >= 0)
        st.presetIdx = curPresetIdx;
    function redraw() { ansiDraw(st); }
    // Put stdin in raw mode so we get key-by-key input
    process.stdin.setRawMode(true);
    process.stdin.resume();
    process.stdin.setEncoding("utf8");
    // Cleanup on exit
    function cleanup() {
        process.stdin.setRawMode(false);
        process.stdout.write(C.showCur + C.reset + "\n");
    }
    process.on("exit", cleanup);
    process.on("SIGINT", () => { cleanup(); process.exit(0); });
    process.on("SIGTERM", () => { cleanup(); process.exit(0); });
    redraw();
    process.stdin.on("data", async (key) => {
        const UP = "\x1b[A";
        const DOWN = "\x1b[B";
        const ENTER = "\r";
        const ESC = "\x1b";
        const CTRL_C = "\x03";
        if (key === CTRL_C) {
            cleanup();
            process.exit(0);
        }
        if (st.view === "main") {
            if (key === UP) {
                st.menuIdx = (st.menuIdx - 1 + MAIN_MENU.length) % MAIN_MENU.length;
                redraw();
                return;
            }
            if (key === DOWN) {
                st.menuIdx = (st.menuIdx + 1) % MAIN_MENU.length;
                redraw();
                return;
            }
            if (key === "q" || key === "Q") {
                cleanup();
                process.exit(0);
            }
            if (key === ENTER) {
                const action = MAIN_MENU[st.menuIdx].key;
                if (action === "quit") {
                    cleanup();
                    process.exit(0);
                }
                if (action === "config") {
                    st.cfg = loadOrDefault();
                    st.view = "config";
                    redraw();
                    return;
                }
                if (action === "preset") {
                    st.cfg = loadOrDefault();
                    const ci = PRESETS.findIndex(p => p.id === st.cfg.permissions.preset);
                    if (ci >= 0)
                        st.presetIdx = ci;
                    st.view = "preset";
                    redraw();
                    return;
                }
                if (action === "escalation") {
                    st.view = "escalation";
                    redraw();
                    return;
                }
                if (action === "test") {
                    st.testResults = null;
                    st.view = "test";
                    redraw();
                    return;
                }
                if (action === "server") {
                    st.view = "server";
                    redraw();
                    return;
                }
                if (action === "reset") {
                    // Temporarily exit raw mode for confirmation
                    process.stdin.setRawMode(false);
                    const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
                    rl.question(C.yellow + "\n  Reset config to defaults? All settings will be lost. (y/N): " + C.reset, (ans) => {
                        rl.close();
                        process.stdin.setRawMode(true);
                        if (ans.trim().toLowerCase() === "y") {
                            saveConfig(defaultConfig());
                            st.cfg = loadOrDefault();
                        }
                        st.view = "main";
                        redraw();
                    });
                    return;
                }
                if (action === "edit") {
                    // Edit connection via readline prompts (exit raw, gather fields, re-enter raw)
                    process.stdin.setRawMode(false);
                    process.stdout.write(C.clrScr);
                    process.stdout.write(C.bold + "\n  Edit Connection Settings\n" + C.reset);
                    process.stdout.write(C.gray + "  Leave blank to keep current value. Ctrl+C to cancel.\n\n" + C.reset);
                    const cfg = loadOrDefault();
                    const cn = cfg.connection;
                    const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
                    const ask = (q) => new Promise(res => rl.question(C.gray + "  " + q + C.reset, res));
                    try {
                        const username = await ask(`Username [${cn.username || ""}]: `);
                        const password = await mutedQuestion(rl, C.gray + "  Bridge password (leave blank to keep): " + C.reset);
                        const smtpHost = await ask(`SMTP host [${cn.smtpHost}]: `);
                        const smtpPort = await ask(`SMTP port [${cn.smtpPort}]: `);
                        const imapHost = await ask(`IMAP host [${cn.imapHost}]: `);
                        const imapPort = await ask(`IMAP port [${cn.imapPort}]: `);
                        const bridgeCert = await ask(`Bridge cert path [${cn.bridgeCertPath || "none"}]: `);
                        if (username.trim())
                            cn.username = username.trim();
                        if (password.trim())
                            cn.password = password.trim();
                        if (smtpHost.trim())
                            cn.smtpHost = smtpHost.trim();
                        if (smtpPort.trim()) {
                            const p = parseInt(smtpPort, 10);
                            if (!isNaN(p) && p > 0 && p <= 65535)
                                cn.smtpPort = p;
                        }
                        if (imapHost.trim())
                            cn.imapHost = imapHost.trim();
                        if (imapPort.trim()) {
                            const p = parseInt(imapPort, 10);
                            if (!isNaN(p) && p > 0 && p <= 65535)
                                cn.imapPort = p;
                        }
                        if (bridgeCert.trim() !== cn.bridgeCertPath) {
                            cn.bridgeCertPath = bridgeCert.trim();
                        }
                        cfg.connection = cn;
                        saveConfig(cfg);
                        st.cfg = cfg;
                        process.stdout.write(c("bGreen", "\n  ✅ Connection settings saved.\n"));
                    }
                    catch {
                        process.stdout.write(c("yellow", "\n  Cancelled.\n"));
                    }
                    finally {
                        rl.close();
                    }
                    await new Promise(r => setTimeout(r, 800));
                    process.stdin.setRawMode(true);
                    st.view = "main";
                    redraw();
                    return;
                }
            }
        }
        else if (st.view === "config") {
            if (key === ESC || key === "b" || key === "B" || key === ENTER) {
                st.view = "main";
                redraw();
            }
        }
        else if (st.view === "preset") {
            if (key === UP) {
                st.presetIdx = (st.presetIdx - 1 + PRESETS.length) % PRESETS.length;
                redraw();
                return;
            }
            if (key === DOWN) {
                st.presetIdx = (st.presetIdx + 1) % PRESETS.length;
                redraw();
                return;
            }
            if (key === ESC) {
                st.view = "main";
                redraw();
                return;
            }
            if (key === ENTER) {
                const preset = PRESETS[st.presetIdx].id;
                const cfg = loadOrDefault();
                cfg.permissions = buildPermissions(preset);
                saveConfig(cfg);
                st.cfg = cfg;
                st.view = "main";
                redraw();
            }
        }
        else if (st.view === "escalation") {
            if (key === ESC) {
                st.view = "main";
                redraw();
                return;
            }
            const pending = getPendingEscalations();
            if (pending.length > 0) {
                const e = pending[0];
                if (key === "t" || key === "T") {
                    // Approve — exit raw mode to get typed confirmation
                    process.stdin.setRawMode(false);
                    const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
                    rl2.question(C.yellow + "\n  Type APPROVE to confirm escalation (anything else cancels): " + C.reset, (ans) => {
                        rl2.close();
                        process.stdin.setRawMode(true);
                        if (ans.trim() === "APPROVE") {
                            const result = approveEscalation(e.id, "terminal_tui");
                            if (result.ok) {
                                const cfg = loadConfig() ?? defaultConfig();
                                cfg.permissions = buildPermissions(result.targetPreset);
                                saveConfig(cfg);
                                st.cfg = cfg;
                                process.stdout.write(C.bGreen + "\n  ✅ Approved. New preset: " + result.targetPreset + "\n" + C.reset);
                            }
                            else {
                                process.stdout.write(C.bRed + "\n  Error: " + result.error + "\n" + C.reset);
                            }
                        }
                        else {
                            process.stdout.write(C.gray + "\n  Cancelled.\n" + C.reset);
                        }
                        setTimeout(() => { st.view = "main"; redraw(); }, 900);
                    });
                    return;
                }
                if (key === "d" || key === "D") {
                    denyEscalation(e.id, "terminal_tui");
                    process.stdout.write(C.bRed + "\n  Escalation denied.\n" + C.reset);
                    setTimeout(() => { st.view = "main"; redraw(); }, 700);
                    return;
                }
            }
        }
        else if (st.view === "test") {
            if (key === ESC) {
                st.view = "main";
                redraw();
                return;
            }
            if (key === "t" || key === "T") {
                st.testResults = { smtp: null, imap: null };
                redraw();
                const cn = st.cfg.connection;
                // Real check: TCP reachability AND a STARTTLS+AUTH probe, so a 454 /
                // bad-password (port open, auth failing) is NOT shown as reachable.
                const checked = await checkConnections({
                    smtpHost: cn.smtpHost, smtpPort: cn.smtpPort,
                    imapHost: cn.imapHost, imapPort: cn.imapPort,
                });
                st.testResults = { smtp: checked.smtp, imap: checked.imap };
                redraw();
            }
        }
        else if (st.view === "server") {
            if (key === ESC) {
                st.view = "main";
                redraw();
                return;
            }
            if ((key === "s" || key === "S") && !st.serverStarted) {
                st.serverStarted = true;
                startServerFn(st.serverPort);
                redraw();
            }
        }
    });
    // Keep process alive
    await new Promise(() => { });
}
// ─── Plain (no-ANSI) Interactive TUI ──────────────────────────────────────────
export async function runPlainTUI(serverPort, startServerFn) {
    const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
    const ask = (q) => new Promise(res => rl.question(q, res));
    function hr() { process.stdout.write("─".repeat(48) + "\n"); }
    function printHeader() {
        process.stdout.write("\nmailpouch — Settings\n");
        hr();
        const cfg = loadConfig();
        const path = getConfigPath();
        if (cfg) {
            process.stdout.write(`Config : ${path}\n`);
            process.stdout.write(`User   : ${cfg.connection.username || "(not set)"}\n`);
            process.stdout.write(`Preset : ${cfg.permissions.preset}\n`);
        }
        else {
            process.stdout.write(`Config : ${path} (not found — defaults apply)\n`);
        }
        hr();
    }
    async function mainMenu() {
        printHeader();
        // Show pending escalation alert
        const pendingEsc = getPendingEscalations();
        if (pendingEsc.length > 0) {
            process.stdout.write("\n  *** PENDING ESCALATION REQUEST — AI agent is requesting more access ***\n");
            process.stdout.write("  Option 4 to review and approve/deny.\n");
        }
        process.stdout.write("  1. View current configuration\n");
        process.stdout.write("  2. Edit connection settings\n");
        process.stdout.write("  3. Set permission preset\n");
        process.stdout.write(`  4. Manage escalation requests${pendingEsc.length ? ` [${pendingEsc.length} PENDING]` : ""}\n`);
        process.stdout.write("  5. Test connectivity\n");
        process.stdout.write("  6. Start browser UI\n");
        process.stdout.write("  7. Reset to defaults\n");
        process.stdout.write("  0. Quit\n");
        hr();
        const choice = (await ask("Choice: ")).trim();
        switch (choice) {
            case "1":
                await viewConfig();
                break;
            case "2":
                await editConnection();
                break;
            case "3":
                await setPreset();
                break;
            case "4":
                await manageEscalations();
                break;
            case "5":
                await testConnectivity();
                break;
            case "6":
                await startBrowserUI();
                break;
            case "7":
                await resetDefaults();
                break;
            case "0": return false;
            default:
                process.stdout.write("Unknown option.\n");
                break;
        }
        return true;
    }
    async function viewConfig() {
        const cfg = loadOrDefault();
        const cn = cfg.connection;
        process.stdout.write("\n── Configuration ──\n");
        process.stdout.write(`Username    : ${cn.username || "(not set)"}\n`);
        process.stdout.write(`SMTP        : ${cn.smtpHost}:${cn.smtpPort}\n`);
        process.stdout.write(`IMAP        : ${cn.imapHost}:${cn.imapPort}\n`);
        process.stdout.write(`Bridge cert : ${cn.bridgeCertPath || "(none)"}\n`);
        process.stdout.write(`SMTP token  : ${cn.smtpToken ? "set" : "(none)"}\n`);
        process.stdout.write(`Debug       : ${cn.debug ? "yes" : "no"}\n`);
        process.stdout.write(`Preset      : ${cfg.permissions.preset}\n`);
        process.stdout.write("\n── Tool permissions ──\n");
        for (const [, cat] of Object.entries(TOOL_CATEGORIES)) {
            const perms = cfg.permissions.tools;
            const enabled = cat.tools.filter(t => perms[t]?.enabled !== false).length;
            process.stdout.write(`${cat.label.padEnd(20)}: ${enabled}/${cat.tools.length} tools enabled\n`);
        }
        await ask("\nPress Enter to continue...");
    }
    async function editConnection() {
        const cfg = loadOrDefault();
        const cn = cfg.connection;
        process.stdout.write("\n── Edit Connection (blank = keep current) ──\n");
        const username = await ask(`Username [${cn.username || ""}]: `);
        const password = await mutedQuestion(rl, `Bridge password (blank to keep): `);
        const smtpHost = await ask(`SMTP host [${cn.smtpHost}]: `);
        const smtpPort = await ask(`SMTP port [${cn.smtpPort}]: `);
        const imapHost = await ask(`IMAP host [${cn.imapHost}]: `);
        const imapPort = await ask(`IMAP port [${cn.imapPort}]: `);
        const cert = await ask(`Bridge cert path [${cn.bridgeCertPath || "none"}]: `);
        if (username.trim())
            cn.username = username.trim();
        if (password.trim())
            cn.password = password.trim();
        if (smtpHost.trim())
            cn.smtpHost = smtpHost.trim();
        if (smtpPort.trim()) {
            const p = parseInt(smtpPort, 10);
            if (!isNaN(p) && p > 0 && p <= 65535)
                cn.smtpPort = p;
        }
        if (imapHost.trim())
            cn.imapHost = imapHost.trim();
        if (imapPort.trim()) {
            const p = parseInt(imapPort, 10);
            if (!isNaN(p) && p > 0 && p <= 65535)
                cn.imapPort = p;
        }
        if (cert.trim())
            cn.bridgeCertPath = cert.trim();
        cfg.connection = cn;
        saveConfig(cfg);
        process.stdout.write("Connection settings saved.\n");
    }
    async function setPreset() {
        process.stdout.write("\n── Set Permission Preset ──\n");
        PRESETS.forEach((p, i) => {
            process.stdout.write(`  ${i + 1}. ${p.label.padEnd(14)} — ${p.desc}\n`);
        });
        const choice = (await ask("Choice (1-4, 0 to cancel): ")).trim();
        const idx = parseInt(choice, 10) - 1;
        if (idx >= 0 && idx < PRESETS.length) {
            const cfg = loadOrDefault();
            cfg.permissions = buildPermissions(PRESETS[idx].id);
            saveConfig(cfg);
            process.stdout.write(`Preset set to: ${PRESETS[idx].label}\n`);
        }
        else if (choice !== "0") {
            process.stdout.write("Invalid choice.\n");
        }
    }
    async function manageEscalations() {
        const pending = getPendingEscalations();
        process.stdout.write("\n── Escalation Requests ──\n");
        if (pending.length === 0) {
            process.stdout.write("No pending escalation requests.\n");
            const audit = getAuditLog(5);
            if (audit.length > 0) {
                process.stdout.write("\nRecent audit log:\n");
                for (const e of audit) {
                    process.stdout.write(`  ${e.time.slice(0, 19)}  ${e.event.padEnd(10)}  ${e.fromPreset} → ${e.toPreset}\n`);
                }
            }
            await ask("\nPress Enter to continue...");
            return;
        }
        for (const e of pending) {
            process.stdout.write(renderEscalationRecord(e, false));
            process.stdout.write("  Actions: A = Approve · D = Deny · S = Skip\n");
            const action = (await ask("  Choice: ")).trim().toUpperCase();
            if (action === "A") {
                const confirm = (await ask("  Type APPROVE to confirm: ")).trim();
                if (confirm === "APPROVE") {
                    const result = approveEscalation(e.id, "terminal_tui");
                    if (result.ok) {
                        const cfg = loadOrDefault();
                        cfg.permissions = buildPermissions(result.targetPreset);
                        saveConfig(cfg);
                        process.stdout.write(`Approved. New preset: ${result.targetPreset}\n`);
                    }
                    else {
                        process.stdout.write(`Error: ${result.error}\n`);
                    }
                }
                else {
                    process.stdout.write("Confirmation text did not match. Cancelled.\n");
                }
            }
            else if (action === "D") {
                denyEscalation(e.id, "terminal_tui");
                process.stdout.write("Escalation denied.\n");
            }
        }
        await ask("\nPress Enter to continue...");
    }
    async function testConnectivity() {
        const cfg = loadOrDefault();
        const cn = cfg.connection;
        process.stdout.write(`\nTesting SMTP ${cn.smtpHost}:${cn.smtpPort}... `);
        const smtp = await tcpCheck(cn.smtpHost, cn.smtpPort);
        process.stdout.write(smtp ? "OK\n" : "UNREACHABLE\n");
        process.stdout.write(`Testing IMAP ${cn.imapHost}:${cn.imapPort}... `);
        const imap = await tcpCheck(cn.imapHost, cn.imapPort);
        process.stdout.write(imap ? "OK\n" : "UNREACHABLE\n");
        if (!smtp || !imap) {
            process.stdout.write("\nMake sure Proton Bridge is running and signed in.\n");
            process.stdout.write("Download: https://proton.me/mail/bridge\n");
        }
        await ask("\nPress Enter to continue...");
    }
    async function startBrowserUI() {
        const url = `http://localhost:${serverPort}`;
        startServerFn(serverPort);
        process.stdout.write(`\nBrowser UI running at: ${url}\n`);
        process.stdout.write("Open the URL in your browser.\n");
        process.stdout.write("Press Ctrl+C to stop.\n");
        await new Promise(() => { }); // wait forever (user Ctrl+C)
    }
    async function resetDefaults() {
        const confirm = (await ask("Reset config to defaults? All settings will be lost. (y/N): ")).trim();
        if (confirm.toLowerCase() === "y") {
            saveConfig(defaultConfig());
            process.stdout.write("Config reset to defaults.\n");
        }
        else {
            process.stdout.write("Cancelled.\n");
        }
    }
    // Main loop
    let running = true;
    while (running) {
        running = await mainMenu();
    }
    rl.close();
    process.stdout.write("\nGoodbye.\n");
}
//# sourceMappingURL=tui.js.map