// power.ts
// Scope: startup, boot, reality detection, and recovery fallback.

import { exec, spawn, type ChildProcess } from "node:child_process";
import net from "node:net";
import { promises as fs, existsSync, readFileSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
import path from "node:path";
import { startDeck } from "./deck";
import { updateProjectIndex } from "./mgrSNP";
import { saveSettings as saveManagedSettings } from "./mgrSNP";
import { controller } from "./controller";
import { logSystem } from "./capture";
import { ensureUserFile, setRuntimeUserTier } from "./hypauth/auth";
import { bootAuth, requestAuthorityPacket } from "./hypauth/hypauthcall";
import { runStartupSystemUpdate } from "./sysutil/auto-system-update";
import {
  recoveryShellHtml,
  getTimeline,
  listRecoverySources,
  undo,
  restoreFromScrape,
  restoreFromBackup,
  inputManifest
} from "./restore";
import {
  ROOT,
  SETTINGS_DIR,
  SETTINGS_PATH,
  USER_SETTINGS_PATH,
  TEMP_STATE_DIR,
  POWER_PID_PATH,
  REPORTS_PATH,
} from "./pathList";

const RECOVERY_PORT = 0;
const POWER_ARGS = new Set(process.argv.slice(2));
const HEADLESS_SERVICES = POWER_ARGS.has("--headless-services");
const SHOW_SERVICES = POWER_ARGS.has("--show-services") || !HEADLESS_SERVICES;
let ngrokProcess: ChildProcess | null = null;
let browserAppProcess: ChildProcess | null = null;
let browserCloseShutdownArmed = false;

function armBrowserCloseShutdown(child: ChildProcess | null): void {
  if (!HEADLESS_SERVICES || !child || browserCloseShutdownArmed) return;
  browserCloseShutdownArmed = true;
  browserAppProcess = child;
  child.once("exit", () => {
    try { logSystem("POWER", { event: "browser_app_closed_headless_shutdown" }); } catch {}
    shutdownManagedChildren();
    setTimeout(() => process.exit(0), 120);
  });
  child.once("error", err => {
    try { logSystem("POWER", { event: "browser_app_watch_error", error: err?.message || String(err) }); } catch {}
  });
}

function writePowerPid(): void {
  try {
    mkdirSync(TEMP_STATE_DIR, { recursive: true });
    writeFileSync(POWER_PID_PATH, String(process.pid), "utf8");
  } catch {}
}

function removePowerPid(): void {
  try { rmSync(POWER_PID_PATH, { force: true }); } catch {}
}

function stopVisibleNgrokFallback(): void {
  try {
    if (process.platform === "win32") exec("taskkill /IM ngrok.exe /T /F", () => {});
    else exec("pkill -f 'ngrok http'", () => {});
  } catch {}
}

function shutdownManagedChildren(): void {
  try {
    if (ngrokProcess && !ngrokProcess.killed) ngrokProcess.kill("SIGTERM");
    else stopVisibleNgrokFallback();
  } catch {}
  try {
    if (browserAppProcess && !browserAppProcess.killed) browserAppProcess.kill("SIGTERM");
  } catch {}
  removePowerPid();
}

process.on("SIGINT", () => {
  shutdownManagedChildren();
  process.exit(0);
});
process.on("SIGTERM", () => {
  shutdownManagedChildren();
  process.exit(0);
});
process.on("exit", shutdownManagedChildren);

// ── Types — AI engine config, owned by power ──────────────────────────────────

export type AITransportProfile = {
  style: "safe_json_prompt" | "response_format_json";
  timeoutMs: number;
  temperature: number;
  retries: number;
  useResponseFormat: boolean;
  jsonInstructionMode: "system" | "user" | "both";
  testedAt: number;
};

export type EQSettings = {
  lmStudioUrl: string;
  model: string;
  aiTransport: AITransportProfile;
  searchThresholds: { short: number; medium: number; long: number; };
  searchSizeCutoffs: { shortCharsMax: number; mediumCharsMax: number; mediumLinesMax: number; };
  activityHistoryLimit: number;
  upstreamImportLimit: number;
  server: { host: string; port: number; };
  bridge: {
    enabled: boolean;
    type: "ngrok" | "none" | "custom";
    url: string;
    autoStart: boolean;
    auth?: {
      type: "apiKey";
      authType: "custom";
      headerName: string;
      apiKey: string;
    };
  };
  extensions: { enabled: boolean; };
};

const DEFAULT_EQ: EQSettings = {
  lmStudioUrl: "http://127.0.0.1:1234",
  model: "",
  aiTransport: { style: "safe_json_prompt", timeoutMs: 30000, temperature: 0.1, retries: 1, useResponseFormat: false, jsonInstructionMode: "system", testedAt: 0 },
  searchThresholds: { short: 0.95, medium: 0.9, long: 0.8 },
  searchSizeCutoffs: { shortCharsMax: 120, mediumCharsMax: 400, mediumLinesMax: 6 },
  activityHistoryLimit: 40,
  upstreamImportLimit: 12,
  server: { host: "127.0.0.1", port: 0 },
  bridge: {
    enabled: false,
    type: "ngrok",
    url: "",
    autoStart: false,
    auth: {
      type: "apiKey",
      authType: "custom",
      headerName: "x-amp-token",
      apiKey: ""
    }
  },
  extensions: { enabled: false }
};

// ── Re-export json utilities from playback ────────────────────────────────────

export async function readJson<T>(filePath: string, fallback: T): Promise<T> {
  try {
    const raw = await fs.readFile(filePath, "utf8");
    return JSON.parse(raw) as T;
  } catch {
    return fallback;
  }
}

export async function writeJson(filePath: string, value: unknown): Promise<void> {
  await fs.mkdir(path.dirname(filePath), { recursive: true });
  await fs.writeFile(filePath, JSON.stringify(value, null, 2), "utf8");
}

function now(): number { return Date.now(); }
function quote(v: string): string { return `"${String(v).replace(/"/g, '\\"')}"`; }

export async function ensureJsonFile(filePath: string, fallback: unknown): Promise<void> {
  try { await fs.access(filePath); }
  catch { await fs.writeFile(filePath, JSON.stringify(fallback, null, 2), "utf8"); }
}

// ── Temp runtime state ────────────────────────────────────────────────────────

export async function ensureTempState(): Promise<void> {
  await fs.mkdir(TEMP_STATE_DIR, { recursive: true });
  await ensureJsonFile(REPORTS_PATH, []);
  await ensureSettingsFile();
}

export async function ensureEQState(): Promise<void> {
  const eqStateDir = path.join(ROOT, ".eq");
  await fs.mkdir(eqStateDir, { recursive: true });
  await ensureJsonFile(path.join(eqStateDir, "activity.json"), []);
  await ensureJsonFile(path.join(eqStateDir, "reports.json"), []);
}

// ── Settings — power owns read/write for AI engine config ─────────────────────

export async function readSettings(): Promise<any> {
  try { return JSON.parse(await fs.readFile(SETTINGS_PATH, "utf8")); } catch { return {}; }
}

export async function readUserSettings(): Promise<any> {
  try { return JSON.parse(await fs.readFile(USER_SETTINGS_PATH, "utf8")); } catch { return {}; }
}

export async function writeSettings(next: any): Promise<void> {
  await fs.writeFile(SETTINGS_PATH, JSON.stringify(next, null, 2), "utf8");
}

export async function ensureSettingsFile(): Promise<void> {
  await fs.mkdir(SETTINGS_DIR, { recursive: true });
  try { await fs.access(SETTINGS_PATH); }
  catch { await fs.writeFile(SETTINGS_PATH, JSON.stringify(DEFAULT_EQ, null, 2), "utf8"); }
}

export async function loadSettings(): Promise<EQSettings> {
  await ensureTempState();
  const c = await readJson<Record<string, any>>(SETTINGS_PATH, {});
  const user = await readUserSettings();
  return {
    lmStudioUrl: typeof c.lmStudioUrl === "string" && c.lmStudioUrl.trim() ? c.lmStudioUrl.trim() : DEFAULT_EQ.lmStudioUrl,
    model: typeof c.model === "string" ? c.model.trim() : "",
    aiTransport: {
      style: c.aiTransport?.style === "response_format_json" ? "response_format_json" : "safe_json_prompt",
      timeoutMs: Number(c.aiTransport?.timeoutMs ?? DEFAULT_EQ.aiTransport.timeoutMs),
      temperature: Number(c.aiTransport?.temperature ?? DEFAULT_EQ.aiTransport.temperature),
      retries: Number(c.aiTransport?.retries ?? DEFAULT_EQ.aiTransport.retries),
      useResponseFormat: Boolean(c.aiTransport?.useResponseFormat ?? DEFAULT_EQ.aiTransport.useResponseFormat),
      jsonInstructionMode: c.aiTransport?.jsonInstructionMode === "user" || c.aiTransport?.jsonInstructionMode === "both" ? c.aiTransport.jsonInstructionMode : "system",
      testedAt: Number(c.aiTransport?.testedAt ?? 0)
    },
    searchThresholds: { short: Number(c.searchThresholds?.short ?? DEFAULT_EQ.searchThresholds.short), medium: Number(c.searchThresholds?.medium ?? DEFAULT_EQ.searchThresholds.medium), long: Number(c.searchThresholds?.long ?? DEFAULT_EQ.searchThresholds.long) },
    searchSizeCutoffs: { shortCharsMax: Number(c.searchSizeCutoffs?.shortCharsMax ?? DEFAULT_EQ.searchSizeCutoffs.shortCharsMax), mediumCharsMax: Number(c.searchSizeCutoffs?.mediumCharsMax ?? DEFAULT_EQ.searchSizeCutoffs.mediumCharsMax), mediumLinesMax: Number(c.searchSizeCutoffs?.mediumLinesMax ?? DEFAULT_EQ.searchSizeCutoffs.mediumLinesMax) },
    activityHistoryLimit: Number(c.activityHistoryLimit ?? c.sessionHistoryLimit ?? DEFAULT_EQ.activityHistoryLimit),
    upstreamImportLimit: Number(c.upstreamImportLimit ?? DEFAULT_EQ.upstreamImportLimit),
    server: { host: typeof c.server?.host === "string" && c.server.host.trim() ? c.server.host.trim() : DEFAULT_EQ.server.host, port: Number.isFinite(Number(c.server?.port)) && Number(c.server?.port) > 0 ? Number(c.server.port) : DEFAULT_EQ.server.port },
    bridge: {
      enabled: Boolean(String(c.bridge?.url || "").trim()),
      type: ["ngrok","none","custom"].includes(c.bridge?.type) ? c.bridge.type : DEFAULT_EQ.bridge.type,
      url: typeof c.bridge?.url === "string" ? c.bridge.url : "",
      autoStart: Boolean(String(c.bridge?.url || "").trim()),
      auth: {
        type: "apiKey",
        authType: "custom",
        headerName: typeof c.bridge?.auth?.headerName === "string" && c.bridge.auth.headerName.trim()
          ? c.bridge.auth.headerName.trim()
          : "x-amp-token",
        apiKey: typeof user.bridgeAuth?.apiKey === "string"
          ? user.bridgeAuth.apiKey
          : ""
      }
    },  
    extensions: { enabled: Boolean(c.extensions?.enabled ?? false) }
  };
}

export async function saveSettings(settings: EQSettings): Promise<void> {
  await ensureTempState();
  const current = await readSettings();
  await writeJson(SETTINGS_PATH, {
    ...current,
    lmStudioUrl: settings.lmStudioUrl,
    model: settings.model,
    aiTransport: settings.aiTransport,
    searchThresholds: settings.searchThresholds,
    searchSizeCutoffs: settings.searchSizeCutoffs,
    activityHistoryLimit: settings.activityHistoryLimit,
    upstreamImportLimit: settings.upstreamImportLimit,
    extensions: settings.extensions
  });
}

type StartupAuthorityPosture = {
  tier: "limited" | "unlimited" | "backstage";
  status: "valid" | "pending_update" | "limited_pending_update";
  message: string;
};

type StartupAuthorityState = {
  bootTier: StartupAuthorityPosture["tier"];
  startupMode: "backstage" | "project_select";
  posture: StartupAuthorityPosture;
};

function normalizeStartupTier(value: unknown): StartupAuthorityPosture["tier"] {
  const clean = String(value || "").trim().toLowerCase();
  if (clean === "backstage") return "backstage";
  if (clean === "unlimited" || clean === "standard" || clean === "custom" || clean === "lifetime_custom") return "unlimited";
  return "limited";
}

function startupAuthorityProfile(tier: StartupAuthorityPosture["tier"]): Record<string, any> {
  if (tier === "backstage") return { projectLimit: -1, importRunsPerDay: -1, systemGuardrails: false, manifestScrambler: false, extensionCapable: true, systemProject: true };
  if (tier === "unlimited") return { projectLimit: -1, importRunsPerDay: -1, systemGuardrails: true, manifestScrambler: true, extensionCapable: true, systemProject: false };
  return { projectLimit: 2, importRunsPerDay: 5, systemGuardrails: true, manifestScrambler: false, extensionCapable: false, systemProject: false };
}

function recordStartupAuthority(posture: StartupAuthorityPosture): void {
  const profile = startupAuthorityProfile(posture.tier);
  const signedIn = posture.status === "valid" || posture.status === "pending_update";

  saveManagedSettings({
    authTier: posture.tier,
    authStatus: signedIn ? "signed_in" : "signed_out",
    authorityStatus: posture.status,
    authorityMessage: posture.message,
    authorityCheckedAt: Date.now()
  });

  logSystem("POWER", {
    event: "startup_authority_posture",
    tier: posture.tier,
    authorityStatus: posture.status,
    message: posture.message,
    projectLimit: profile.projectLimit,
    importRunsPerDay: profile.importRunsPerDay,
    systemGuardrails: profile.systemGuardrails,
    manifestScrambler: profile.manifestScrambler,
    extensionCapable: profile.extensionCapable
  });
}

async function runStartupAuthority(): Promise<StartupAuthorityState> {
  let posture: StartupAuthorityPosture = {
    tier: "limited",
    status: "limited_pending_update",
    message: "startup_authority_check_failed"
  };

  try {
    const authResult = await bootAuth();
    const authTier = normalizeStartupTier(authResult?.tier);
    const authMessage = String(authResult?.message || authResult?.error || "");

    logSystem("POWER", {
      event: "boot_auth_complete",
      tier: authResult?.tier,
      serverUp: authResult?.serverUp,
      message: authResult?.message
    });

    if (!authResult?.ok) {
      posture = { tier: "limited", status: "limited_pending_update", message: authMessage || "auth_validation_failed" };
    } else if (!authResult?.serverUp) {
      posture = { tier: "limited", status: "limited_pending_update", message: authMessage || "server_unavailable_pending_update" };
    } else if (authMessage === "server_error_using_cache" || authMessage.includes("server_error") || authMessage.includes("API request failed")) {
      posture = { tier: "limited", status: "limited_pending_update", message: "remote_server_error" };
    } else {
      
      // CONTRACT ONE: Strict Tier Lock. 
      // The local app MUST blindly accept the tier returned by the auth server lookup.
      const verifiedAuthTier = authTier;

      // CONTRACT TWO: Boot Integrity Sequence
      const authority = await requestAuthorityPacket();

      if (!authority?.ok) {
        // If the integrity check explicitly fails or errors out, force the state downgrade
        posture = { tier: "limited", status: "limited_pending_update", message: String(authority?.message || authority?.error || "system_integrity_check_failed") };
      } else {
        // A passing integrity check can NEVER upgrade the user tier, it only inherits the verified auth tier.
        const status = authority.updateRequired || authority.pendingUpdate ? "pending_update" : "valid";
        posture = { tier: verifiedAuthTier, status, message: String(authority.message || (status === "valid" ? "authority_valid" : "pending_update")) };
      }
    }
  } catch (err: any) {
    posture = { tier: "limited", status: "limited_pending_update", message: err?.message || "startup_authority_check_failed" };
    logSystem("POWER", { event: "boot_phase_warn", phase: "bootAuth", error: err?.message ?? String(err) });
  }

  const effectiveTier = posture.status === "valid" ? posture.tier : "limited";
  const startupMode = effectiveTier === "backstage" ? "backstage" : "project_select";

  controller.boot.tier(effectiveTier);
  if (startupMode === "backstage") controller.boot.backstage();
  setRuntimeUserTier(effectiveTier);

  recordStartupAuthority({ ...posture, tier: effectiveTier });

  logSystem("POWER", {
    event: "startup_mode_resolved",
    tier: posture.tier,
    startupMode,
    projectSerial: startupMode === "backstage" ? "system" : ""
  });

  return { bootTier: effectiveTier, startupMode, posture: { ...posture, tier: effectiveTier } };
}

function printStartupAuthorityResult(state: StartupAuthorityState): void {
  if (state.posture.status !== "valid") {
    console.log(`⚠️  Auth: ${state.posture.message || "limited_access"} — local limited mode`);
  } else if (state.startupMode === "backstage") {
    console.log("✅ Auth: backstage tier verified — Backstage available");
  } else {
    console.log(`✅ Auth: ${state.posture.tier} tier verified`);
  }
}

async function findAvailablePort(preferred = 0): Promise<number> {
  return await new Promise((resolvePort, rejectPort) => {
    const server = net.createServer();
    server.once("error", err => rejectPort(err));
    server.listen({ host: "127.0.0.1", port: preferred }, () => {
      const address = server.address();
      const port = typeof address === "object" && address ? Number(address.port) : preferred;
      server.close(() => resolvePort(port));
    });
  });
}

async function resolveRuntimeDeckPort(): Promise<number> {
  const settings = await readSettings();
  const mode = settings?.server?.portMode === "fixed" ? "fixed" : "random";
  const requested = Number(settings?.server?.port || 0);
  if (mode === "fixed" && requested > 0) {
    try { return await findAvailablePort(requested); }
    catch { logSystem("POWER", { event: "fixed_port_unavailable_using_random", requested }); }
  }
  return await findAvailablePort(0);
}

function launchOptionalNgrok(port: number): void {
  try {
    const settings: any = JSON.parse(existsSync(SETTINGS_PATH) ? readFileSync(SETTINGS_PATH, "utf8") : "{}");
    const bridgeUrl = String(settings?.bridge?.url || "").trim();
    if (!bridgeUrl) {
      logSystem("POWER", { event: "ngrok_skipped", reason: "bridge_url_empty" });
      return;
    }

    if (HEADLESS_SERVICES) {
      ngrokProcess = spawn("ngrok", ["http", String(port)], {
        cwd: ROOT,
        stdio: "ignore",
        windowsHide: true,
        detached: false
      });
      logSystem("POWER", { event: "ngrok_launch_requested", mode: "hidden", port });
      return;
    }

    if (process.platform === "win32") exec(`start "NV ngrok" cmd /c ngrok http ${port}`);
    else exec(`ngrok http ${port}`);
    logSystem("POWER", { event: "ngrok_launch_requested", mode: SHOW_SERVICES ? "visible" : "default", port });
  } catch (err: any) {
    logSystem("POWER", { event: "ngrok_launch_skipped", error: err?.message || String(err) });
  }
}
function macBrowserProfileArg(name: string): string {
  const profile = path.join(TEMP_STATE_DIR, name);
  try { mkdirSync(profile, { recursive: true }); } catch {}
  return `--user-data-dir=${profile}`;
}

function openUIInBrowserApp(url: string): void {
  if (process.platform === "win32") {
    const candidates = [
      process.env.PROGRAMFILES         ? path.join(process.env.PROGRAMFILES,         "Google","Chrome","Application","chrome.exe") : "",
      process.env["PROGRAMFILES(X86)"] ? path.join(process.env["PROGRAMFILES(X86)"], "Google","Chrome","Application","chrome.exe") : "",
      process.env.PROGRAMFILES         ? path.join(process.env.PROGRAMFILES,         "Microsoft","Edge","Application","msedge.exe") : "",
      process.env["PROGRAMFILES(X86)"] ? path.join(process.env["PROGRAMFILES(X86)"], "Microsoft","Edge","Application","msedge.exe") : ""
    ].filter(Boolean);

    const exePath = candidates.find(p => existsSync(p));
    if (HEADLESS_SERVICES && exePath) {
      const child = spawn(exePath, [`--app=${url}`, "--new-window"], {
        cwd: ROOT,
        stdio: "ignore",
        windowsHide: true,
        detached: false
      });
      armBrowserCloseShutdown(child);
      return;
    }

    if (exePath) { exec(`start "" ${quote(exePath)} --app=${quote(url)}`); return; }
    exec(`start msedge --app=${quote(url)} || start chrome --app=${quote(url)}`);
    return;
  }

  if (process.platform === "darwin") {
    const profileArg = macBrowserProfileArg("verbatim-browser-profile");
    const appArgs = ["--args", "--new-window", profileArg, `--app=${url}`];

    if (HEADLESS_SERVICES) {
      const child = spawn("open", ["-W", "-n", "-a", "Google Chrome", ...appArgs], {
        cwd: ROOT,
        stdio: "ignore",
        detached: false
      });
      armBrowserCloseShutdown(child);
      return;
    }

    const chromeCmd = `open -na "Google Chrome" --args --new-window ${quote(profileArg)} --app=${quote(url)}`;
    const edgeCmd = `open -na "Microsoft Edge" --args --new-window ${quote(profileArg)} --app=${quote(url)}`;
    exec(`${chromeCmd} || ${edgeCmd}`);
    return;
  }

  if (HEADLESS_SERVICES) {
    const child = spawn("sh", ["-c", `google-chrome --app=${quote(url)} || microsoft-edge --app=${quote(url)} || chromium-browser --app=${quote(url)} || chromium --app=${quote(url)}`], {
      cwd: ROOT,
      stdio: "ignore",
      detached: false
    });
    armBrowserCloseShutdown(child);
    return;
  }

  exec(`google-chrome --app=${quote(url)} || microsoft-edge --app=${quote(url)} || chromium-browser --app=${quote(url)} || chromium --app=${quote(url)}`);
}

function buildProbeMessages(profile: AITransportProfile) {
  const sys = "You are testing API compatibility. Return {\"summary\":\"ok\",\"confidence\":1,\"issues\":[]} as valid JSON only.";
  const inq = "Probe: power transport test";
  const jsonNote = "Return valid JSON only. No markdown. No commentary outside the JSON object.";
  const systemText = (profile.jsonInstructionMode === "system" || profile.jsonInstructionMode === "both") ? `${sys}\n${jsonNote}` : sys;
  const userText = (profile.jsonInstructionMode === "user" || profile.jsonInstructionMode === "both") ? `${inq}\n\n${jsonNote}` : inq;
  return [{ role: "system" as const, content: systemText }, { role: "user" as const, content: userText }];
}

async function postProbeRequest(settings: EQSettings, model: string, messages: { role: "system"|"user"; content: string }[], profile: AITransportProfile) {
  const ctrl = new AbortController();
  const timeout = setTimeout(() => ctrl.abort(), profile.timeoutMs);
  try {
    const body: Record<string, any> = { model, temperature: profile.temperature, messages };
    if (profile.useResponseFormat) body.response_format = { type: "json_object" };
    return await fetch(`${settings.lmStudioUrl.replace(/\/$/, "")}/v1/chat/completions`, { method: "POST", headers: { "Content-Type": "application/json" }, signal: ctrl.signal, body: JSON.stringify(body) });
  } finally { clearTimeout(timeout); }
}

async function detectLMStudioModel(settings?: EQSettings): Promise<string> {
  const cfg = settings ?? await loadSettings();
  try { const res = await fetch(`${cfg.lmStudioUrl.replace(/\/$/, "")}/v1/models`); if (!res.ok) return ""; const data = await res.json() as any; return String(data?.data?.[0]?.id ?? "").trim(); }
  catch { return ""; }
}

async function testAITransport(settings: EQSettings, model: string): Promise<AITransportProfile> {
  const base: AITransportProfile = {
    style: "safe_json_prompt",
    timeoutMs: 900,
    temperature: 0.1,
    retries: 1,
    useResponseFormat: false,
    jsonInstructionMode: "system",
    testedAt: 0
  };

  const candidates: AITransportProfile[] = [
    base,
    { ...base, style: "response_format_json", useResponseFormat: true }
  ];

  for (const candidate of candidates) {
    try {
      const res = await postProbeRequest(settings, model, buildProbeMessages(candidate), candidate);
      if (!res.ok) continue;

      const data = await res.json() as any;
      const raw = String(data?.choices?.[0]?.message?.content ?? "").trim();

      const cleaned = raw.startsWith("```")
        ? raw
            .split(/\r?\n/)
            .filter((line, index, lines) => index !== 0 && !(index === lines.length - 1 && line.trim() === "```"))
            .join("\n")
            .trim()
        : raw;

      JSON.parse(cleaned);
      return { ...candidate, testedAt: now() };
    } catch {}
  }

  return { ...base, testedAt: now() };
}

export async function syncModelFromLMStudio(): Promise<string> {
  const settings = await loadSettings();
  const detected = await detectLMStudioModel(settings);
  if (!detected) { logSystem("POWER", { event: "model_sync_skipped", reason: "lmstudio_unavailable" }); return ""; }
  const shouldRetest = detected !== settings.model || !settings.aiTransport?.testedAt;
  settings.model = detected;
  if (shouldRetest) { settings.aiTransport = await testAITransport(settings, detected); logSystem("POWER", { event: "model_synced", model: detected, transport: settings.aiTransport.style }); }
  await saveSettings(settings);
  return detected;
}

async function checkLM(): Promise<void> {
  try {
    const res = await fetch("http://127.0.0.1:1234/v1/models");
    if (res.ok) { const data = await res.json() as any; console.log("🧠 LM Studio ready:", data.data?.[0]?.id || "no model"); return; }
    console.log("⚠️  LM Studio responded but no model list was available");
  } catch { console.log("⚠️  LM Studio not running"); }
}

// ── Recovery server — independent fallback, only needs recovery.ts ────────────

async function bootRecovery(reason: string): Promise<void> {
  console.error(`\n❌ Boot failed: ${reason}`);
  console.log("⚡ Starting recovery server...\n");
  try {
    const Fastify = (await import("fastify")).default;
    const cors = (await import("@fastify/cors")).default;
    const app = Fastify({ logger: false });
    await app.register(cors, { origin: true });

    app.get("/",                        async (_req, reply) => reply.type("text/html").send(recoveryShellHtml(reason)));
    app.get("/recovery/timeline",       async (req: any) => await getTimeline(Number(req?.query?.limit || 25)));
    app.get("/recovery/sources",        async () => await listRecoverySources());
    app.get("/health",                  async () => ({ ok: true, mode: "recovery" }));

    app.post("/recovery/undo",          async (req) => { const b = (req.body && typeof req.body === "object") ? req.body as any : {}; return await undo({ count: Number(b.count || 0), dryRun: Boolean(b.dryRun) }); });
    app.post("/recovery/restore/scrape",async (req) => { const b = (req.body && typeof req.body === "object") ? req.body as any : {}; return await restoreFromScrape({ name: String(b.name || "").trim(), dryRun: Boolean(b.dryRun) }); });
    app.post("/recovery/restore/backup",async (req) => { const b = (req.body && typeof req.body === "object") ? req.body as any : {}; return await restoreFromBackup({ name: String(b.name || "").trim(), dryRun: Boolean(b.dryRun) }); });
    app.post("/recovery/manifest",      async (req) => { const b = (req.body && typeof req.body === "object") ? req.body as any : {}; return await inputManifest({ manifest: (b.manifest && typeof b.manifest === "object") ? b.manifest : {}, dryRun: Boolean(b.dryRun) }); });

    const recoveryPort = await findAvailablePort(RECOVERY_PORT);
    await app.listen({ port: recoveryPort, host: "127.0.0.1" });
    console.log(`🚑 Recovery server running at http://127.0.0.1:${recoveryPort}/`);
    console.log(`   Reason: ${reason}`);
    console.log(`   Use recovery shell to undo or restore, then restart NV.\n`);
    setTimeout(() => { try { openUIInBrowserApp(`http://127.0.0.1:${recoveryPort}/`); } catch {} }, 600);
  } catch (err: any) {
    console.error("❌ Recovery server also failed:", err?.message ?? String(err));
    console.error("   Manual recovery required. Check logs/ directory.");
    process.exit(1);
  }
}

// ── Process error handlers ────────────────────────────────────────────────────

let deckStarted = false;

process.on("unhandledRejection", (reason: any) => {
  const message = reason?.message ?? String(reason);
  try { logSystem("POWER", { event: "unhandled_rejection", error: message }); } catch {}
  console.error("❌ Unhandled rejection:", message);
  if (!deckStarted) bootRecovery(`Unhandled rejection during boot: ${message}`);
});

process.on("uncaughtException", (err: any) => {
  const message = err?.message ?? String(err);
  try { logSystem("POWER", { event: "uncaught_exception", error: message }); } catch {}
  console.error("❌ Uncaught exception:", message);
  if (!deckStarted) bootRecovery(`Uncaught exception during boot: ${message}`);
});

// ── Boot ──────────────────────────────────────────────────────────────────────

async function boot() {
  console.log("⚡ NV booting...\n");
  writePowerPid();

  // ActiveProject and AccessTier are memory-only and reset every boot.
  controller.boot.clear();

  try {
    // 1. Ensure permanent settings/user files exist.
    try {
      await ensureSettingsFile();
    } catch (err: any) {
      logSystem("POWER", {
        event: "boot_phase_warn",
        phase: "ensureSettingsFile",
        error: err?.message ?? String(err)
      });
    }

    try {
      ensureUserFile();
    } catch (err: any) {
      logSystem("POWER", {
        event: "boot_phase_warn",
        phase: "ensureUserFile",
        error: err?.message ?? String(err)
      });
    }

    // 2. Ensure runtime support files only. No project/session persistence.
    try {
      await fs.mkdir(TEMP_STATE_DIR, { recursive: true });
      await ensureJsonFile(REPORTS_PATH, []);
    } catch (err: any) {
      logSystem("POWER", {
        event: "boot_phase_warn",
        phase: "ensureRuntimeSupport",
        error: err?.message ?? String(err)
      });
    }

    // 3. Rebuild project index only. Do not select a project.
    try {
      updateProjectIndex();
    } catch (err: any) {
      logSystem("POWER", {
        event: "boot_phase_warn",
        phase: "rebuildProjectIndex",
        error: err?.message ?? String(err)
      });
    }

    // 4. Resolve and persist Deck runtime port.
    const runtimePort = await resolveRuntimeDeckPort();
    const bootSettings = await readSettings();
    const portMode = bootSettings?.server?.portMode === "fixed" ? "fixed" : "random";

    saveManagedSettings({
      server: {
        host: "127.0.0.1",
        port: portMode === "fixed" ? Number(bootSettings?.server?.port || runtimePort) : 0,
        portMode,
        runtimePort
      } as any
    });

    // 5. Start from safe local access. Remote auth must not block local UI startup.
    let bootTier: StartupAuthorityPosture["tier"] = "limited";
    let startupMode: "backstage" | "project_select" = "project_select";

    controller.boot.tier("limited");

    recordStartupAuthority({
      tier: "limited",
      status: "limited_pending_update",
      message: "local_startup_pending_auth"
    });

    // 6. Start Deck as soon as local runtime state is ready.
    await startDeck(runtimePort, async (reason) => {
      try { logSystem("POWER", { event: "deck_shutdown_request_received", reason }); } catch {}
      shutdownManagedChildren();
      setTimeout(() => process.exit(0), 150);
    });
    deckStarted = true;

    // 7. Open UI immediately after Deck is live.
    try {
      const uiUrl = existsSync(path.join(ROOT, "monitor.html"))
        ? `http://127.0.0.1:${runtimePort}/`
        : `http://127.0.0.1:${runtimePort}/?recovery=1`;

      console.log(`\n🌐 Open Verbatim: ${uiUrl}`);
      console.log("   If your browser did not open automatically, copy this link into your browser.\n");
      openUIInBrowserApp(uiUrl);
    } catch (err: any) {
      logSystem("POWER", {
        event: "boot_phase_warn",
        phase: "openUI",
        error: err?.message ?? String(err)
      });
    }

    // 8. Resolve/apply auth after local UI is already available.
    const authorityState = await runStartupAuthority();

    bootTier = authorityState.bootTier;
    startupMode = authorityState.startupMode;

    printStartupAuthorityResult(authorityState);

    // 9. Update check only. Do not block local UI startup.
    if (startupMode === "backstage") {
      logSystem("POWER", {
        event: "startup_update_skipped",
        reason: "backstage_no_automatic_updates",
        startupMode,
        tier: bootTier
      });
    } else {
      try {
        const releaseSettings = await readSettings();
        const channel = String(releaseSettings?.release?.channel || "beta");
        const updateResult = await runStartupSystemUpdate({
          channel,
          autoApply: releaseSettings?.release?.autoUpdate !== false,
          relaunch: releaseSettings?.release?.autoUpdate !== false
        });

        logSystem("POWER", {
          event: "startup_update_checked",
          channel,
          applied: Boolean(updateResult.applied),
          restartRequired: Boolean(updateResult.restartRequired),
          version: updateResult.version || ""
        });
      } catch (err: any) {
        logSystem("POWER", {
          event: "boot_phase_warn",
          phase: "startupUpdate",
          error: err?.message ?? String(err)
        });
      }
    }

        // 10. LM checks should not block UI startup.
    try {
      await checkLM();
    } catch (err: any) {
      logSystem("POWER", {
        event: "boot_phase_warn",
        phase: "checkLM",
        error: err?.message ?? String(err)
      });
    }

    try {
      await syncModelFromLMStudio();
    } catch (err: any) {
      logSystem("POWER", {
        event: "boot_phase_warn",
        phase: "syncModel",
        error: err?.message ?? String(err)
      });
    }

    // 11. Launch ngrok after Deck is live.
    launchOptionalNgrok(runtimePort);


    console.log(`\n✅ NV ready — ${bootTier} tier — ${startupMode}\n`);
  } catch (err: any) {
    const message = err?.message ?? String(err);
    try {
      logSystem("POWER", {
        event: "boot_failed",
        error: message
      });
    } catch {}

    await bootRecovery(message);
  }
}

boot();