/**
 * VS Code editor (code-server) route handlers.
 *
 * Registers Hono routes for launching a browser-based VS Code editor
 * (code-server) for a session's working directory. Supports two modes:
 * - **Container mode**: Starts code-server inside the session's Docker container
 *   with port forwarding.
 * - **Host mode**: Falls back to a host-installed code-server binary when
 *   containers are unavailable or lack code-server.
 *
 * Routes:
 * - `POST /sessions/:id/editor/start` — Start code-server and return the editor URL.
 */

import type { Hono } from "hono";
import { join } from "node:path";
import { homedir } from "node:os";
import { z } from "zod";
import type { CliLauncher } from "../cli-launcher.js";
import { resolveBinary } from "../path-resolver.js";
import { containerManager } from "../container-manager.js";
import { shellEscapeArg } from "../shell-utils.js";
import { config } from "../config.js";
import { parseParam } from "../validation.js";

const VSCODE_EDITOR_CONTAINER_PORT = 13337;
const VSCODE_EDITOR_HOST_PORT = config.editorPort;

const sessionIdSchema = z.string().min(1, "Session ID is required");

// ─── Routes ───────────────────────────────────────────────────────

export function registerEditorRoutes(api: Hono, launcher: CliLauncher): void {
  api.post("/sessions/:id/editor/start", async (c) => {
    const parsed = parseParam(c, "id", sessionIdSchema);
    if (!parsed.success) return parsed.response;
    const id = parsed.data;
    const session = launcher.getSession(id);
    if (!session) return c.json({ error: "Session not found" }, 404);

    // For container sessions, try code-server inside the container first.
    // If unavailable, fall through to host code-server with the host-mapped cwd.
    let hostFallbackCwd = session.cwd;

    if (session.containerId) {
      const container = containerManager.getContainer(id);
      const hasContainerCodeServer = container
        && await containerManager.hasBinaryInContainer(container.containerId, "code-server");

      if (container && hasContainerCodeServer) {
        const editorPathSuffix = `?folder=${encodeURIComponent("/workspace")}`;
        const portMapping = container.portMappings.find(
          (p) => p.containerPort === VSCODE_EDITOR_CONTAINER_PORT,
        );
        if (!portMapping) {
          return c.json({
            available: false,
            installed: true,
            mode: "container",
            message: "Container editor port is missing. Start a new session to enable the VS Code editor.",
          });
        }

        try {
          const alive = await containerManager.isContainerAlive(container.containerId);
          if (alive === "stopped") {
            await containerManager.startContainer(container.containerId);
          } else if (alive === "missing") {
            return c.json({
              available: false,
              installed: true,
              mode: "container",
              message: "Session container no longer exists. Start a new session to use the editor.",
            });
          }

          const startCmd = [
            `if ! pgrep -f ${shellEscapeArg(`code-server.*--bind-addr 0.0.0.0:${VSCODE_EDITOR_CONTAINER_PORT}`)} >/dev/null 2>&1; then`,
            `nohup code-server --auth none --disable-telemetry --bind-addr 0.0.0.0:${VSCODE_EDITOR_CONTAINER_PORT} /workspace >/tmp/zencode-code-server.log 2>&1 &`,
            "fi",
          ].join(" ");
          await containerManager.execInContainer(container.containerId, ["sh", "-lc", startCmd], 10_000);

          // Wait for code-server to be ready (up to 5s)
          const containerEditorUrl = `http://localhost:${portMapping.hostPort}${editorPathSuffix}`;
          for (let i = 0; i < 25; i++) {
            try {
              const res = await fetch(`http://127.0.0.1:${portMapping.hostPort}/healthz`);
              if (res.ok || res.status === 302 || res.status === 200) break;
            } catch {
              // not ready yet
            }
            await new Promise((r) => setTimeout(r, 200));
          }

          return c.json({
            available: true,
            installed: true,
            mode: "container",
            url: containerEditorUrl,
          });
        } catch (e) {
          const message = e instanceof Error ? e.message : String(e);
          return c.json({
            available: false,
            installed: true,
            mode: "container",
            message: `Failed to start VS Code editor in container: ${message}`,
          });
        }
      }

      // Container doesn't have code-server — fall through to host code-server
      // using the host-mapped workspace path
      if (container) {
        hostFallbackCwd = container.hostCwd;
      }
    }

    const hostCodeServer = resolveBinary("code-server");
    if (!hostCodeServer) {
      return c.json({
        available: false,
        installed: false,
        mode: "host",
        message: "VS Code editor is not installed. Install it with: brew install code-server",
      });
    }

    const editorPathSuffix = `?folder=${encodeURIComponent(hostFallbackCwd)}`;

    try {
      const zencodeDir = join(homedir(), ".zencode");
      const logFile = join(zencodeDir, "code-server-host.log");
      const startCmd = [
        `if ! pgrep -f ${shellEscapeArg(`code-server.*--bind-addr 127.0.0.1:${VSCODE_EDITOR_HOST_PORT}`)} >/dev/null 2>&1; then`,
        `nohup ${shellEscapeArg(hostCodeServer)} --auth none --disable-telemetry --bind-addr 127.0.0.1:${VSCODE_EDITOR_HOST_PORT} ${shellEscapeArg(hostFallbackCwd)} >> ${shellEscapeArg(logFile)} 2>&1 &`,
        "fi",
      ].join(" ");
      const startHostCmd = `mkdir -p ${shellEscapeArg(zencodeDir)} && ${startCmd}`;
      const proc = Bun.spawn(["sh", "-c", startHostCmd], {
        stdout: "pipe",
        stderr: "pipe",
      });
      const spawnTimeout = setTimeout(() => proc.kill(), 10_000);
      await proc.exited;
      clearTimeout(spawnTimeout);

      // Wait for code-server to be ready (up to 5s)
      const editorUrl = `http://localhost:${VSCODE_EDITOR_HOST_PORT}${editorPathSuffix}`;
      for (let i = 0; i < 25; i++) {
        try {
          const res = await fetch(`http://127.0.0.1:${VSCODE_EDITOR_HOST_PORT}/healthz`);
          if (res.ok || res.status === 302 || res.status === 200) break;
        } catch {
          // not ready yet
        }
        await new Promise((r) => setTimeout(r, 200));
      }

      return c.json({
        available: true,
        installed: true,
        mode: "host",
        url: editorUrl,
      });
    } catch (e) {
      const message = e instanceof Error ? e.message : String(e);
      return c.json({
        available: false,
        installed: true,
        mode: "host",
        message: `Failed to start VS Code editor: ${message}`,
      });
    }
  });
}
