/**
 * Calvana Ship Log Extension
 *
 * Automatically tracks what you're shipping and updates the live Calvana site.
 *
 * Tools (LLM-callable):
 *   - calvana_ship: Add/update/complete shipping log entries
 *   - calvana_oops: Log mistakes and fixes
 *   - calvana_deploy: Push changes to the live site
 *
 * Commands (user):
 *   /ships       — View current shipping log
 *   /ship-deploy — Force deploy to calvana.quikcue.com
 *
 * How it works:
 *   1. When you work on tasks, the LLM uses calvana_ship to track progress
 *   2. If something breaks, calvana_oops logs it
 *   3. calvana_deploy rebuilds the /live page HTML and pushes it to the server
 *   4. The extension auto-injects context so the LLM knows to track ships
 *
 * Edit the SSH/deploy config in the DEPLOY_CONFIG section below.
 */

import { StringEnum } from "@mariozechner/pi-ai";
import type { ExtensionAPI, ExtensionContext, Theme } from "@mariozechner/pi-coding-agent";
import { Text, truncateToWidth, matchesKey } from "@mariozechner/pi-tui";
import { Type } from "@sinclair/typebox";

// ════════════════════════════════════════════════════════════════════
// CONFIGURATION — Edit these to change deploy target, copy, links
// ════════════════════════════════════════════════════════════════════

const DEPLOY_CONFIG = {
  sshHost: "root@159.195.60.33",
  sshPort: "22",
  container: "qc-server-new",
  sitePath: "/opt/calvana/html",
  domain: "calvana.quikcue.com",
};

const SITE_CONFIG = {
  title: "Calvana",
  tagline: "I break rules. Not production.",
  email: "omair@quikcue.com",
  referralLine: "PS — Umar pointed me here. If this turns into a hire, I want him to get paid.",
};

// ════════════════════════════════════════════════════════════════════
// TYPES
// ════════════════════════════════════════════════════════════════════

type ShipStatus = "planned" | "shipping" | "shipped";

interface ShipEntry {
  id: number;
  title: string;
  status: ShipStatus;
  timestamp: string;
  metric: string;
  prLink: string;
  deployLink: string;
  loomLink: string;
}

interface OopsEntry {
  id: number;
  description: string;
  fixTime: string;
  commitLink: string;
  timestamp: string;
}

interface ShipLogState {
  ships: ShipEntry[];
  oops: OopsEntry[];
  nextShipId: number;
  nextOopsId: number;
  lastDeployed: string | null;
}

// ════════════════════════════════════════════════════════════════════
// TOOL SCHEMAS
// ════════════════════════════════════════════════════════════════════

const ShipParams = Type.Object({
  action: StringEnum(["add", "update", "list"] as const),
  title: Type.Optional(Type.String({ description: "Ship title (for add)" })),
  id: Type.Optional(Type.Number({ description: "Ship ID (for update)" })),
  status: Type.Optional(StringEnum(["planned", "shipping", "shipped"] as const)),
  metric: Type.Optional(Type.String({ description: "What moved — metric line" })),
  prLink: Type.Optional(Type.String({ description: "PR link" })),
  deployLink: Type.Optional(Type.String({ description: "Deploy link" })),
  loomLink: Type.Optional(Type.String({ description: "Loom clip link" })),
});

const OopsParams = Type.Object({
  action: StringEnum(["add", "list"] as const),
  description: Type.Optional(Type.String({ description: "What broke and how it was fixed" })),
  fixTime: Type.Optional(Type.String({ description: "Time to fix, e.g. '3 min'" })),
  commitLink: Type.Optional(Type.String({ description: "Link to the fix commit" })),
});

const DeployParams = Type.Object({
  dryRun: Type.Optional(Type.Boolean({ description: "If true, generate HTML but don't deploy" })),
});

// ════════════════════════════════════════════════════════════════════
// EXTENSION
// ════════════════════════════════════════════════════════════════════

export default function (pi: ExtensionAPI) {
  // ── State ──
  let state: ShipLogState = {
    ships: [],
    oops: [],
    nextShipId: 1,
    nextOopsId: 1,
    lastDeployed: null,
  };

  // ── State reconstruction: DB first, session fallback ──
  const reconstructFromDb = async () => {
    try {
      const sshBase = `ssh -o ConnectTimeout=5 -p ${DEPLOY_CONFIG.sshPort} ${DEPLOY_CONFIG.sshHost}`;
      const pgContainer = "$(docker ps --format '{{.Names}}' | grep dokploy-postgres)";
      
      const shipsResult = await pi.exec("bash", ["-c",
        `${sshBase} "incus exec ${DEPLOY_CONFIG.container} -- bash -c 'docker exec ${pgContainer} psql -U dokploy -d calvana -t -A -F \\\"|||\\\" -c \\\"SELECT id, title, status, COALESCE(metric, chr(45)), created_at::text FROM ships ORDER BY id\\\"'" 2>/dev/null`
      ], { timeout: 15000 });

      if (shipsResult.code === 0 && shipsResult.stdout.trim()) {
        state.ships = [];
        let maxId = 0;
        for (const line of shipsResult.stdout.trim().split("\n")) {
          if (!line.trim()) continue;
          const parts = line.split("|||");
          if (parts.length >= 5) {
            const id = parseInt(parts[0]);
            if (id > maxId) maxId = id;
            state.ships.push({
              id,
              title: parts[1],
              status: parts[2] as ShipStatus,
              timestamp: parts[4],
              metric: parts[3],
              prLink: "#pr",
              deployLink: "#deploy",
              loomLink: "#loomclip",
            });
          }
        }
        state.nextShipId = maxId + 1;
      }

      const oopsResult = await pi.exec("bash", ["-c",
        `${sshBase} "incus exec ${DEPLOY_CONFIG.container} -- bash -c 'docker exec ${pgContainer} psql -U dokploy -d calvana -t -A -F \\\"|||\\\" -c \\\"SELECT id, description, COALESCE(fix_time, chr(45)), COALESCE(commit_link, chr(35)), created_at::text FROM oops ORDER BY id\\\"'" 2>/dev/null`
      ], { timeout: 15000 });

      if (oopsResult.code === 0 && oopsResult.stdout.trim()) {
        state.oops = [];
        let maxOopsId = 0;
        for (const line of oopsResult.stdout.trim().split("\n")) {
          if (!line.trim()) continue;
          const parts = line.split("|||");
          if (parts.length >= 4) {
            const id = parseInt(parts[0]);
            if (id > maxOopsId) maxOopsId = id;
            state.oops.push({
              id,
              description: parts[1],
              fixTime: parts[2],
              commitLink: parts[3],
              timestamp: parts[4] || "",
            });
          }
        }
        state.nextOopsId = maxOopsId + 1;
      }
    } catch {
      // DB unavailable — fall through to session reconstruction
    }
  };

  const reconstructState = async (ctx: ExtensionContext) => {
    state = { ships: [], oops: [], nextShipId: 1, nextOopsId: 1, lastDeployed: null };

    // Always try DB first — this is the source of truth
    await reconstructFromDb();

    // If DB returned data, we're done
    if (state.ships.length > 0) return;

    // Fallback: reconstruct from session history (when DB is unreachable)
    for (const entry of ctx.sessionManager.getBranch()) {
      if (entry.type !== "message") continue;
      const msg = entry.message;
      if (msg.role !== "toolResult") continue;
      if (msg.toolName === "calvana_ship" || msg.toolName === "calvana_oops" || msg.toolName === "calvana_deploy") {
        const details = msg.details as { state?: ShipLogState } | undefined;
        if (details?.state) {
          state = details.state;
        }
      }
    }
  };

  pi.on("session_start", async (_event, ctx) => {
    await reconstructState(ctx);
    if (ctx.hasUI) {
      const theme = ctx.ui.theme;
      const shipCount = state.ships.length;
      const shipped = state.ships.filter(s => s.status === "shipped").length;
      ctx.ui.setStatus("calvana", theme.fg("dim", `🚀 ${shipped}/${shipCount} shipped (DB)`));
    }
  });
  pi.on("session_switch", async (_event, ctx) => await reconstructState(ctx));
  pi.on("session_fork", async (_event, ctx) => await reconstructState(ctx));
  pi.on("session_tree", async (_event, ctx) => await reconstructState(ctx));

  // ── Inject context so LLM knows about ship tracking ──
  pi.on("before_agent_start", async (event, _ctx) => {
    const shipContext = `
[Calvana Ship Log Extension Active — DB-backed]
Ship log is persisted in PostgreSQL (calvana DB on dokploy-postgres).
State survives across sessions — the DB is the source of truth, not session history.

Tools:
- calvana_ship: Track shipping progress (add/update/list). Writes to DB.
- calvana_oops: Log mistakes and fixes. Writes to DB.
- calvana_deploy: Queries DB for ALL historical entries, generates HTML, deploys to https://${DEPLOY_CONFIG.domain}/live

Rules:
- When you START working on a task, use calvana_ship to add or update it to "shipping".
- When you COMPLETE a task, update it to "shipped" with a metric.
- If something BREAKS, log it with calvana_oops.
- After significant changes, use calvana_deploy to push updates live.
- calvana_deploy reads from the DATABASE — it shows ALL ships ever, not just this session.

Current state from DB: ${state.ships.length} ships (${state.ships.filter(s => s.status === "shipped").length} shipped), ${state.oops.length} oops
`;
    return {
      systemPrompt: event.systemPrompt + shipContext,
    };
  });

  // ── Update status bar on turn end ──
  pi.on("turn_end", async (_event, ctx) => {
    if (ctx.hasUI) {
      const theme = ctx.ui.theme;
      const shipped = state.ships.filter(s => s.status === "shipped").length;
      const shipping = state.ships.filter(s => s.status === "shipping").length;
      const total = state.ships.length;
      let statusText = `🚀 ${shipped}/${total} shipped`;
      if (shipping > 0) statusText += ` · ${shipping} in flight`;
      if (state.lastDeployed) statusText += ` · last deploy ${state.lastDeployed}`;
      ctx.ui.setStatus("calvana", theme.fg("dim", statusText));
    }
  });

  // ════════════════════════════════════════════════════════════════
  // TOOL: calvana_ship
  // ════════════════════════════════════════════════════════════════

  pi.registerTool({
    name: "calvana_ship",
    label: "Ship Log",
    description: "Track shipping progress. Actions: add (new entry), update (change status/links), list (show all). Use this whenever you start, progress, or finish a task.",
    parameters: ShipParams,

    async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
      const now = new Date().toISOString().replace("T", " ").slice(0, 19) + " GMT+8";

      switch (params.action) {
        case "add": {
          if (!params.title) {
            return {
              content: [{ type: "text", text: "Error: title required" }],
              details: { state: { ...state }, error: "title required" },
            };
          }
          const entry: ShipEntry = {
            id: state.nextShipId++,
            title: params.title,
            status: (params.status as ShipStatus) || "planned",
            timestamp: now,
            metric: params.metric || "—",
            prLink: params.prLink || "#pr",
            deployLink: params.deployLink || "#deploy",
            loomLink: params.loomLink || "#loomclip",
          };
          state.ships.push(entry);

          // Persist to PostgreSQL
          const addTitle = entry.title.replace(/'/g, "''");
          const addMetric = (entry.metric || "—").replace(/'/g, "''");
          const addStatus = entry.status;
          try {
            const dbResult = await pi.exec("bash", ["-c",
              `ssh -o ConnectTimeout=10 -p ${DEPLOY_CONFIG.sshPort} ${DEPLOY_CONFIG.sshHost} "incus exec ${DEPLOY_CONFIG.container} -- bash -c 'docker exec \$(docker ps --format {{.Names}} | grep dokploy-postgres) psql -U dokploy -d calvana -t -c \\\"INSERT INTO ships (title, status, metric) VALUES (\\x27${addTitle}\\x27, \\x27${addStatus}\\x27, \\x27${addMetric}\\x27) RETURNING id\\\"'"`
            ], { timeout: 15000 });
            const dbId = parseInt((dbResult.stdout || "").trim());
            if (dbId > 0) entry.id = dbId;
          } catch { /* DB write failed, local state still updated */ }

          return {
            content: [{ type: "text", text: `Ship #${entry.id} added: "${entry.title}" [${entry.status}]` }],
            details: { state: { ...state, ships: [...state.ships] } },
          };
        }

        case "update": {
          if (params.id === undefined) {
            return {
              content: [{ type: "text", text: "Error: id required for update" }],
              details: { state: { ...state }, error: "id required" },
            };
          }
          const ship = state.ships.find(s => s.id === params.id);
          if (!ship) {
            return {
              content: [{ type: "text", text: `Ship #${params.id} not found` }],
              details: { state: { ...state }, error: `#${params.id} not found` },
            };
          }
          if (params.status) ship.status = params.status as ShipStatus;
          if (params.metric) ship.metric = params.metric;
          if (params.prLink) ship.prLink = params.prLink;
          if (params.deployLink) ship.deployLink = params.deployLink;
          if (params.loomLink) ship.loomLink = params.loomLink;
          ship.timestamp = now;

          // Persist update to PostgreSQL
          const setClauses: string[] = [];
          if (params.status) setClauses.push(`status='${params.status}'`);
          if (params.metric) setClauses.push(`metric='${(params.metric || "").replace(/'/g, "''")}'`);
          setClauses.push("updated_at=now()");
          try {
            await pi.exec("bash", ["-c",
              `ssh -o ConnectTimeout=10 -p ${DEPLOY_CONFIG.sshPort} ${DEPLOY_CONFIG.sshHost} "incus exec ${DEPLOY_CONFIG.container} -- bash -c 'docker exec \$(docker ps --format {{.Names}} | grep dokploy-postgres) psql -U dokploy -d calvana -c \\\"UPDATE ships SET ${setClauses.join(", ")} WHERE id=${params.id}\\\"'"`
            ], { timeout: 15000 });
          } catch { /* DB write failed, local state still updated */ }

          return {
            content: [{ type: "text", text: `Ship #${ship.id} updated: "${ship.title}" [${ship.status}]` }],
            details: { state: { ...state, ships: [...state.ships] } },
          };
        }

        case "list": {
          if (state.ships.length === 0) {
            return {
              content: [{ type: "text", text: "No ships logged yet." }],
              details: { state: { ...state } },
            };
          }
          const lines = state.ships.map(s =>
            `#${s.id} [${s.status.toUpperCase()}] ${s.title} (${s.timestamp}) — ${s.metric}`
          );
          return {
            content: [{ type: "text", text: lines.join("\n") }],
            details: { state: { ...state } },
          };
        }

        default:
          return {
            content: [{ type: "text", text: `Unknown action: ${params.action}` }],
            details: { state: { ...state } },
          };
      }
    },

    renderCall(args, theme) {
      let text = theme.fg("toolTitle", theme.bold("🚀 ship "));
      text += theme.fg("muted", args.action || "");
      if (args.title) text += " " + theme.fg("dim", `"${args.title}"`);
      if (args.id !== undefined) text += " " + theme.fg("accent", `#${args.id}`);
      if (args.status) text += " → " + theme.fg("accent", args.status);
      return new Text(text, 0, 0);
    },

    renderResult(result, { expanded }, theme) {
      const details = result.details as { state?: ShipLogState; error?: string } | undefined;
      if (details?.error) return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);

      const st = details?.state;
      if (!st || st.ships.length === 0) return new Text(theme.fg("dim", "No ships"), 0, 0);

      const shipped = st.ships.filter(s => s.status === "shipped").length;
      const total = st.ships.length;
      let text = theme.fg("success", `${shipped}/${total} shipped`);

      if (expanded) {
        for (const s of st.ships) {
          const badge = s.status === "shipped" ? theme.fg("success", "✓")
            : s.status === "shipping" ? theme.fg("warning", "●")
            : theme.fg("dim", "○");
          text += `\n  ${badge} ${theme.fg("accent", `#${s.id}`)} ${theme.fg("muted", s.title)}`;
        }
      }
      return new Text(text, 0, 0);
    },
  });

  // ════════════════════════════════════════════════════════════════
  // TOOL: calvana_oops
  // ════════════════════════════════════════════════════════════════

  pi.registerTool({
    name: "calvana_oops",
    label: "Oops Log",
    description: "Log mistakes and fixes. Actions: add (new oops entry), list (show all). Use when something breaks during a task.",
    parameters: OopsParams,

    async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
      const now = new Date().toISOString().replace("T", " ").slice(0, 19) + " GMT+8";

      switch (params.action) {
        case "add": {
          if (!params.description) {
            return {
              content: [{ type: "text", text: "Error: description required" }],
              details: { state: { ...state }, error: "description required" },
            };
          }
          const entry: OopsEntry = {
            id: state.nextOopsId++,
            description: params.description,
            fixTime: params.fixTime || "—",
            commitLink: params.commitLink || "#commit",
            timestamp: now,
          };
          state.oops.push(entry);

          // Persist to PostgreSQL
          const oopsDesc = entry.description.replace(/'/g, "''");
          const oopsTime = (entry.fixTime || "-").replace(/'/g, "''");
          const oopsCommit = (entry.commitLink || "#commit").replace(/'/g, "''");
          try {
            const dbResult = await pi.exec("bash", ["-c",
              `ssh -o ConnectTimeout=10 -p ${DEPLOY_CONFIG.sshPort} ${DEPLOY_CONFIG.sshHost} "incus exec ${DEPLOY_CONFIG.container} -- bash -c 'docker exec \$(docker ps --format {{.Names}} | grep dokploy-postgres) psql -U dokploy -d calvana -t -c \\\"INSERT INTO oops (description, fix_time, commit_link) VALUES (\\x27${oopsDesc}\\x27, \\x27${oopsTime}\\x27, \\x27${oopsCommit}\\x27) RETURNING id\\\"'"`
            ], { timeout: 15000 });
            const dbId = parseInt((dbResult.stdout || "").trim());
            if (dbId > 0) entry.id = dbId;
          } catch { /* DB write failed, local state still updated */ }

          return {
            content: [{ type: "text", text: `Oops #${entry.id}: "${entry.description}" (fixed in ${entry.fixTime})` }],
            details: { state: { ...state, oops: [...state.oops] } },
          };
        }

        case "list": {
          if (state.oops.length === 0) {
            return {
              content: [{ type: "text", text: "No oops entries. Clean run so far." }],
              details: { state: { ...state } },
            };
          }
          const lines = state.oops.map(o =>
            `#${o.id} ${o.description} — fixed in ${o.fixTime}`
          );
          return {
            content: [{ type: "text", text: lines.join("\n") }],
            details: { state: { ...state } },
          };
        }

        default:
          return {
            content: [{ type: "text", text: `Unknown action: ${params.action}` }],
            details: { state: { ...state } },
          };
      }
    },

    renderCall(args, theme) {
      let text = theme.fg("toolTitle", theme.bold("💥 oops "));
      text += theme.fg("muted", args.action || "");
      if (args.description) text += " " + theme.fg("dim", `"${args.description}"`);
      return new Text(text, 0, 0);
    },

    renderResult(result, _options, theme) {
      const details = result.details as { state?: ShipLogState; error?: string } | undefined;
      if (details?.error) return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
      const text = result.content[0];
      return new Text(theme.fg("warning", text?.type === "text" ? text.text : ""), 0, 0);
    },
  });

  // ════════════════════════════════════════════════════════════════
  // TOOL: calvana_deploy
  // ════════════════════════════════════════════════════════════════

  pi.registerTool({
    name: "calvana_deploy",
    label: "Deploy Calvana",
    description: `Regenerate the /live page with current ship log and deploy to https://${DEPLOY_CONFIG.domain}. Call this after adding/updating ships or oops entries to push changes live.`,
    parameters: DeployParams,

    async execute(_toolCallId, params, signal, onUpdate, _ctx) {
      onUpdate?.({ content: [{ type: "text", text: "Querying database for full ship log..." }] });

      // ── Build a helper script on the remote server to avoid quoting hell ──
      // This is the ONLY way to reliably run psql inside docker inside incus inside ssh.
      // Previous approach with nested escaping silently returned empty results.
      const sshBase = `ssh -o ConnectTimeout=10 -p ${DEPLOY_CONFIG.sshPort} ${DEPLOY_CONFIG.sshHost}`;
      const HELPER_SCRIPT = `
PG_CONTAINER=$(incus exec ${DEPLOY_CONFIG.container} -- bash -c "docker ps --format '{{.Names}}' | grep dokploy-postgres")
if [ -z "$PG_CONTAINER" ]; then echo "ERR:NO_PG_CONTAINER"; exit 1; fi
SHIPS=$(incus exec ${DEPLOY_CONFIG.container} -- bash -c "docker exec $PG_CONTAINER psql -U dokploy -d calvana -t -A -F '|||' -c \\"SELECT id, title, status, COALESCE(metric,'-'), COALESCE(details,' '), created_at::text, COALESCE(updated_at::text, created_at::text) FROM ships ORDER BY id\\"")
OOPS=$(incus exec ${DEPLOY_CONFIG.container} -- bash -c "docker exec $PG_CONTAINER psql -U dokploy -d calvana -t -A -F '|||' -c \\"SELECT id, description, COALESCE(fix_time,'-'), COALESCE(commit_link,'#commit'), created_at::text FROM oops ORDER BY id\\"")
echo "===SHIPS==="
echo "$SHIPS"
echo "===OOPS==="
echo "$OOPS"
echo "===END==="
`.trim();

      try {
        // 1. Run the helper script via SSH to get ALL ships + oops from DB
        const dbResult = await pi.exec("bash", ["-c",
          `${sshBase} 'bash -s' << 'DBEOF'\n${HELPER_SCRIPT}\nDBEOF`
        ], { signal, timeout: 30000 });

        if (dbResult.code !== 0) {
          return {
            content: [{ type: "text", text: `DB query failed (code ${dbResult.code}): ${dbResult.stderr}\nstdout: ${dbResult.stdout?.slice(0, 200)}` }],
            details: { state: { ...state }, error: dbResult.stderr },
            isError: true,
          };
        }

        const output = dbResult.stdout || "";
        if (output.includes("ERR:NO_PG_CONTAINER")) {
          return {
            content: [{ type: "text", text: `ABORT: PostgreSQL container not found. Refusing to deploy.` }],
            details: { state: { ...state }, error: "PG container not found" },
            isError: true,
          };
        }

        // 2. Parse the structured output
        const shipsSection = output.split("===SHIPS===")[1]?.split("===OOPS===")[0]?.trim() || "";
        const oopsSection = output.split("===OOPS===")[1]?.split("===END===")[0]?.trim() || "";

        const dbShips: Array<{ id: number; title: string; status: string; metric: string; details: string; created: string; updated: string }> = [];
        for (const line of shipsSection.split("\n")) {
          if (!line.trim()) continue;
          const parts = line.split("|||");
          if (parts.length >= 6) {
            dbShips.push({
              id: parseInt(parts[0]),
              title: parts[1],
              status: parts[2],
              metric: parts[3],
              details: parts[4],
              created: parts[5],
              updated: parts[6] || parts[5],
            });
          }
        }

        const dbOops: Array<{ id: number; description: string; fixTime: string; commitLink: string; created: string }> = [];
        for (const line of oopsSection.split("\n")) {
          if (!line.trim()) continue;
          const parts = line.split("|||");
          if (parts.length >= 4) {
            dbOops.push({
              id: parseInt(parts[0]),
              description: parts[1],
              fixTime: parts[2],
              commitLink: parts[3],
              created: parts[4] || "",
            });
          }
        }

        // ── SAFETY GATE: refuse to deploy if DB returned 0 ships ──
        // The DB has 48+ entries. If we get 0, the query broke silently.
        if (dbShips.length === 0) {
          return {
            content: [{ type: "text", text: `ABORT: DB query returned 0 ships. This would wipe the live site.\nRaw output (first 500 chars): ${output.slice(0, 500)}\n\nRefusing to deploy. Fix the DB query first.` }],
            details: { state: { ...state }, error: "0 ships from DB — refusing to deploy" },
            isError: true,
          };
        }

        onUpdate?.({ content: [{ type: "text", text: `Found ${dbShips.length} ships + ${dbOops.length} oops from DB. Generating HTML...` }] });

        // 3. Generate HTML from DB data
        const liveHtml = generateLivePageFromDb(dbShips, dbOops);

        if (params.dryRun) {
          return {
            content: [{ type: "text", text: `Dry run — ${dbShips.length} ships, ${dbOops.length} oops, ${liveHtml.length} bytes HTML.\n\n${liveHtml.slice(0, 500)}...` }],
            details: { state: { ...state }, dryRun: true },
          };
        }

        onUpdate?.({ content: [{ type: "text", text: `Deploying ${dbShips.length} ships to server...` }] });

        // 4. Deploy via base64
        const b64Html = Buffer.from(liveHtml).toString("base64");
        const deployResult = await pi.exec("bash", ["-c",
          `${sshBase} "incus exec ${DEPLOY_CONFIG.container} -- bash -c 'echo ${b64Html} | base64 -d > ${DEPLOY_CONFIG.sitePath}/live/index.html'"`
        ], { signal, timeout: 30000 });

        if (deployResult.code !== 0) {
          return {
            content: [{ type: "text", text: `Deploy failed: ${deployResult.stderr}` }],
            details: { state: { ...state }, error: deployResult.stderr },
            isError: true,
          };
        }

        // 5. Rebuild and update docker service
        const rebuildResult = await pi.exec("bash", ["-c",
          `${sshBase} "incus exec ${DEPLOY_CONFIG.container} -- bash -c 'cd /opt/calvana && docker build -t calvana:latest . 2>&1 | tail -2 && docker service update --force calvana 2>&1 | tail -2'"`
        ], { signal, timeout: 60000 });

        const now = new Date().toISOString().replace("T", " ").slice(0, 19);
        state.lastDeployed = now;

        return {
          content: [{ type: "text", text: `✓ Deployed to https://${DEPLOY_CONFIG.domain}/live\n${dbShips.length} ships + ${dbOops.length} oops from database\n${rebuildResult.stdout}` }],
          details: { state: { ...state, lastDeployed: now } },
        };
      } catch (err: any) {
        return {
          content: [{ type: "text", text: `Deploy error: ${err.message}` }],
          details: { state: { ...state }, error: err.message },
          isError: true,
        };
      }
    },

    renderCall(_args, theme) {
      return new Text(theme.fg("toolTitle", theme.bold("🌐 deploy calvana")), 0, 0);
    },

    renderResult(result, _options, theme) {
      const details = result.details as { error?: string } | undefined;
      if (details?.error) return new Text(theme.fg("error", `✗ ${details.error}`), 0, 0);
      return new Text(theme.fg("success", `✓ Live at https://${DEPLOY_CONFIG.domain}/live`), 0, 0);
    },
  });

  // ════════════════════════════════════════════════════════════════
  // COMMAND: /ships
  // ════════════════════════════════════════════════════════════════

  pi.registerCommand("ships", {
    description: "View current Calvana shipping log",
    handler: async (_args, ctx) => {
      if (!ctx.hasUI) {
        ctx.ui.notify("Requires interactive mode", "error");
        return;
      }

      await ctx.ui.custom<void>((_tui, theme, _kb, done) => {
        return new ShipLogComponent(state, theme, () => done());
      });
    },
  });

  // ════════════════════════════════════════════════════════════════
  // COMMAND: /ship-deploy
  // ════════════════════════════════════════════════════════════════

  pi.registerCommand("ship-deploy", {
    description: "Force deploy the Calvana site with current ship log",
    handler: async (_args, ctx) => {
      const ok = await ctx.ui.confirm("Deploy?", `Push ship log to https://${DEPLOY_CONFIG.domain}/live?`);
      if (!ok) return;

      // Queue a deploy via the LLM
      pi.sendUserMessage("Use calvana_deploy to push the current ship log to the live site.", { deliverAs: "followUp" });
    },
  });
}

// ════════════════════════════════════════════════════════════════════
// UI COMPONENT: /ships viewer
// ════════════════════════════════════════════════════════════════════

class ShipLogComponent {
  private state: ShipLogState;
  private theme: Theme;
  private onClose: () => void;
  private cachedWidth?: number;
  private cachedLines?: string[];

  constructor(state: ShipLogState, theme: Theme, onClose: () => void) {
    this.state = state;
    this.theme = theme;
    this.onClose = onClose;
  }

  handleInput(data: string): void {
    if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
      this.onClose();
    }
  }

  render(width: number): string[] {
    if (this.cachedLines && this.cachedWidth === width) return this.cachedLines;

    const lines: string[] = [];
    const th = this.theme;

    lines.push("");
    lines.push(truncateToWidth(
      th.fg("borderMuted", "─".repeat(3)) +
      th.fg("accent", " 🚀 Calvana Ship Log ") +
      th.fg("borderMuted", "─".repeat(Math.max(0, width - 26))),
      width
    ));
    lines.push("");

    // Ships
    if (this.state.ships.length === 0) {
      lines.push(truncateToWidth(`  ${th.fg("dim", "No ships yet.")}`, width));
    } else {
      const shipped = this.state.ships.filter(s => s.status === "shipped").length;
      lines.push(truncateToWidth(
        `  ${th.fg("muted", `${shipped}/${this.state.ships.length} shipped`)}`,
        width
      ));
      lines.push("");

      for (const s of this.state.ships) {
        const badge = s.status === "shipped" ? th.fg("success", "✓ SHIPPED ")
          : s.status === "shipping" ? th.fg("warning", "● SHIPPING")
          : th.fg("dim", "○ PLANNED ");
        lines.push(truncateToWidth(
          `  ${badge} ${th.fg("accent", `#${s.id}`)} ${th.fg("text", s.title)}`,
          width
        ));
        lines.push(truncateToWidth(
          `           ${th.fg("dim", s.timestamp)} · ${th.fg("dim", s.metric)}`,
          width
        ));
      }
    }

    // Oops
    if (this.state.oops.length > 0) {
      lines.push("");
      lines.push(truncateToWidth(`  ${th.fg("warning", "💥 Oops Log")}`, width));
      for (const o of this.state.oops) {
        lines.push(truncateToWidth(
          `  ${th.fg("error", "─")} ${th.fg("muted", o.description)} ${th.fg("dim", `(${o.fixTime})`)}`,
          width
        ));
      }
    }

    lines.push("");
    if (this.state.lastDeployed) {
      lines.push(truncateToWidth(`  ${th.fg("dim", `Last deployed: ${this.state.lastDeployed}`)}`, width));
    }
    lines.push(truncateToWidth(`  ${th.fg("dim", "Press Escape to close")}`, width));
    lines.push("");

    this.cachedWidth = width;
    this.cachedLines = lines;
    return lines;
  }

  invalidate(): void {
    this.cachedWidth = undefined;
    this.cachedLines = undefined;
  }
}

// ════════════════════════════════════════════════════════════════════
// HTML GENERATOR — Builds the /live page from current state
// ════════════════════════════════════════════════════════════════════

// Keep the old function signature for backward compat but it's no longer called by deploy
function generateLivePageHtml(state: ShipLogState): string {
  return generateLivePageFromDb(
    state.ships.map(s => ({ id: s.id, title: s.title, status: s.status, metric: s.metric, details: "", created: s.timestamp, updated: s.timestamp })),
    state.oops.map(o => ({ id: o.id, description: o.description, fixTime: o.fixTime, commitLink: o.commitLink, created: o.timestamp }))
  );
}

function generateLivePageFromDb(
  ships: Array<{ id: number; title: string; status: string; metric: string; details: string; created: string; updated: string }>,
  oops: Array<{ id: number; description: string; fixTime: string; commitLink: string; created: string }>
): string {
  const now = new Date().toISOString();
  const shipped = ships.filter(s => s.status === "shipped").length;
  const shipping = ships.filter(s => s.status === "shipping").length;

  // Group ships by date (newest first)
  const shipsByDate = new Map<string, typeof ships>();
  for (const s of [...ships].reverse()) {
    const date = s.created.split(" ")[0] || s.created.split("T")[0] || "Unknown";
    if (!shipsByDate.has(date)) shipsByDate.set(date, []);
    shipsByDate.get(date)!.push(s);
  }

  const formatDate = (dateStr: string) => {
    try {
      const d = new Date(dateStr);
      return d.toLocaleDateString("en-GB", { weekday: "long", day: "numeric", month: "long", year: "numeric" });
    } catch { return dateStr; }
  };

  let shipSections = "";
  for (const [date, dateShips] of shipsByDate) {
    const cards = dateShips.map(s => {
      const badgeClass = s.status === "shipped" ? "badge-shipped"
        : s.status === "shipping" ? "badge-shipping"
        : "badge-planned";
      const badgeLabel = s.status.charAt(0).toUpperCase() + s.status.slice(1);

      // If details has HTML (from DB), use it; otherwise use metric
      const hasDetails = s.details && s.details.trim().length > 10 && s.details.includes("<");
      const detailsBlock = hasDetails
        ? `\n          <div class="card-details">${s.details}</div>`
        : "";

      return `        <div class="card">
          <div class="card-header">
            <span class="card-id">#${s.id}</span>
            <span class="card-title">${escapeHtml(s.title)}</span>
            <span class="badge ${badgeClass}">${badgeLabel}</span>
          </div>
          <p class="metric">${escapeHtml(s.metric)}</p>${detailsBlock}
        </div>`;
    }).join("\n");

    shipSections += `
      <section class="section day-section">
        <h2 class="day-header">${formatDate(date)} <span class="day-count">${dateShips.length} ship${dateShips.length !== 1 ? "s" : ""}</span></h2>
        <div class="card-grid">
${cards}
        </div>
      </section>`;
  }

  const oopsEntries = oops.length > 0
    ? oops.map(o => `        <div class="oops-entry">
          <span class="oops-id">#${o.id}</span>
          <span>${escapeHtml(o.description)}${o.fixTime !== "—" ? ` <em>Fixed in ${escapeHtml(o.fixTime)}.</em>` : ""}</span>
          ${o.commitLink && o.commitLink !== "#commit" ? `<a href="${escapeHtml(o.commitLink)}">→ commit</a>` : ""}
        </div>`).join("\n")
    : `        <div class="oops-entry"><span>Nothing broken yet. Give it time.</span></div>`;

  return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Calvana — Live Shipping Log</title>
  <meta name="description" content="Intentional chaos. Full receipts. Watch the build happen in real time.">
  <meta property="og:title" content="Calvana — Live Shipping Log">
  <meta property="og:description" content="Intentional chaos. Full receipts.">
  <meta property="og:type" content="website">
  <meta property="og:url" content="https://${DEPLOY_CONFIG.domain}/live">
  <meta name="twitter:card" content="summary">
  <link rel="canonical" href="https://${DEPLOY_CONFIG.domain}/live">
  <link rel="stylesheet" href="/css/style.css">
  <style>
    .stats-bar { display:flex; gap:2rem; margin:1.5rem 0 2rem; flex-wrap:wrap; }
    .stat { text-align:center; }
    .stat-num { font-size:2rem; font-weight:800; line-height:1; }
    .stat-label { font-size:.75rem; text-transform:uppercase; letter-spacing:.08em; opacity:.5; margin-top:.25rem; }
    .stat-num.green { color:#22c55e; }
    .stat-num.amber { color:#f59e0b; }
    .stat-num.red { color:#ef4444; }
    .day-header { font-size:1.1rem; font-weight:700; margin-bottom:.75rem; display:flex; align-items:center; gap:.75rem; }
    .day-count { font-size:.75rem; font-weight:500; opacity:.4; }
    .day-section { margin-bottom:1.5rem; }
    .card-id { font-size:.7rem; font-weight:700; opacity:.3; margin-right:.5rem; }
    .card-details { margin-top:.5rem; font-size:.8rem; opacity:.65; line-height:1.5; }
    .card-details ul { margin:.25rem 0; padding-left:1.25rem; }
    .card-details li { margin-bottom:.15rem; }
    .oops-id { font-size:.7rem; font-weight:700; opacity:.3; margin-right:.5rem; }
    .card { position:relative; }
    .card-header { display:flex; align-items:flex-start; gap:.5rem; flex-wrap:wrap; }
    .card-title { flex:1; min-width:0; }
  </style>
</head>
<body>
  <nav>
    <div class="nav-inner">
      <a href="/" class="logo">calvana<span>.exe</span></a>
      <div class="nav-links">
        <a href="/manifesto">/manifesto</a>
        <a href="/live" class="active">/live</a>
        <a href="/hire">/hire</a>
      </div>
    </div>
  </nav>
  <main class="page">
    <h1 class="hero-title">Live Shipping Log</h1>
    <p class="subtitle">Intentional chaos. Full receipts. Every ship ever.</p>

    <div class="stats-bar">
      <div class="stat"><div class="stat-num green">${shipped}</div><div class="stat-label">Shipped</div></div>
      <div class="stat"><div class="stat-num amber">${shipping}</div><div class="stat-label">In Flight</div></div>
      <div class="stat"><div class="stat-num">${ships.length}</div><div class="stat-label">Total</div></div>
      <div class="stat"><div class="stat-num red">${oops.length}</div><div class="stat-label">Oops</div></div>
    </div>
${shipSections}

    <section class="section">
      <div class="two-col">
        <div class="col col-broke">
          <h3>Rules I broke today</h3>
          <ul>
            <li>Didn't ask permission</li>
            <li>Didn't wait for alignment</li>
            <li>Didn't write a PRD</li>
            <li>Didn't submit a normal application</li>
          </ul>
        </div>
        <div class="col col-kept">
          <h3>Rules I refuse to break</h3>
          <ul>
            <li>No silent failures</li>
            <li>No unbounded AI spend</li>
            <li>No hallucinations shipped to users</li>
            <li>No deploy without rollback path</li>
          </ul>
        </div>
      </div>
    </section>

    <section class="section">
      <h2>Oops Log</h2>
      <p class="subtitle" style="margin-bottom:1rem">If it's not here, I haven't broken it yet.</p>
      <div class="oops-log">
${oopsEntries}
      </div>
    </section>

    <footer>
      <p class="footer-tagline">${SITE_CONFIG.tagline}</p>
      <p style="margin-top:.4rem">Last updated: ${now} · ${ships.length} ships from PostgreSQL</p>
    </footer>
  </main>
</body>
</html>`;
}

function escapeHtml(str: string): string {
  return str
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;")
    .replace(/\u2014/g, "&mdash;")   // em dash
    .replace(/\u2013/g, "&ndash;")   // en dash
    .replace(/\u2019/g, "&rsquo;")   // right single quote
    .replace(/\u2018/g, "&lsquo;")   // left single quote
    .replace(/\u201c/g, "&ldquo;")   // left double quote
    .replace(/\u201d/g, "&rdquo;")   // right double quote
    .replace(/\u2026/g, "&hellip;")  // ellipsis
    .replace(/\u2192/g, "&rarr;")    // right arrow
    .replace(/\u00a3/g, "&pound;")   // pound sign
    .replace(/\u00d7/g, "&times;");  // multiplication sign
}
