import { resolve, join } from "path";
import { mkdir, writeFile } from "fs/promises";
import { runShell, type ShellResult } from "./shell.js";
import { BRAND_HEX, setBrandPaletteTheme } from "../brand/palette.js";
import { getActiveBrandChromeLabel } from "../brand/theme-compiled.js";
import { getProjectRoot, getRunPath, pathExists } from "./fs.js";
import { writeSessionMeta } from "./session.js";
import { writeTodos } from "./todo-sync.js";

export async function detectTmux(): Promise<boolean> {
  const result: ShellResult = await runShell("tmux", ["-V"], { timeout: 3000 });
  return result.exitCode === 0;
}

export function isCockpitChild(): boolean {
  return process.env.OMK_CHAT_COCKPIT_CHILD === "1";
}

export interface LaunchChatCockpitOptions {
  runId?: string;
  brand?: string;
  cwd?: string;
  agentFile?: string;
  workers?: string;
  maxStepsPerTurn?: string;
  mcpScope?: string;
  provider?: string;
  model?: string;
  execution?: string;
  ui?: string;
  cockpitRefresh?: string;
  cockpitRedraw?: "diff" | "full" | "append";
  cockpitHistory?: "off" | "static" | "watch";
  cockpitSideWidth?: string;
  cockpitHeight?: string;
}

export async function ensureChatRunState(root: string, runId: string): Promise<void> {
  const runDir = getRunPath(runId, undefined, root);
  await mkdir(runDir, { recursive: true });
  const statePath = join(runDir, "state.json");
  if (!(await pathExists(statePath))) {
    const state = {
      schemaVersion: 1,
      runId,
      status: "running",
      nodes: [
        {
          id: "chat",
          name: "Chat Session",
          role: "chat",
          dependsOn: [],
          status: "running",
          retries: 0,
          maxRetries: 0,
          startedAt: new Date().toISOString(),
        },
      ],
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
    };
    await writeFile(statePath, JSON.stringify(state, null, 2));
    // Write initial session metadata and empty todos
    try {
      const now = new Date().toISOString();
      await writeSessionMeta(runId, { runId, type: "chat", status: "active", startedAt: now, updatedAt: now, todoCount: 0, todoDoneCount: 0 });
      await writeTodos(runId, []);
    } catch {
      // ignore initialization failures
    }
  }
}

function parseCockpitRefreshMs(value?: string): number {
  const defaultMs = 2000;
  if (value === undefined) return defaultMs;
  const parsed = Number.parseInt(value, 10);
  if (!Number.isFinite(parsed)) return defaultMs;
  return Math.min(60_000, Math.max(750, parsed));
}

function parseCockpitSideWidth(value?: string): number {
  const defaultPct = defaultCockpitSideWidth();
  if (value === undefined) return defaultPct;
  const parsed = Number.parseInt(value, 10);
  if (!Number.isFinite(parsed)) return defaultPct;
  return Math.min(80, Math.max(20, parsed));
}

function defaultCockpitSideWidth(): number {
  const columns = process.stdout.columns ?? 0;
  if (columns >= 180) return 50;
  if (columns >= 120) return 45;
  return 40;
}

const MIN_CHAT_COCKPIT_HEIGHT = 14;
const MAX_CHAT_COCKPIT_HEIGHT = 96;

function parseCockpitHeight(value?: string): number | undefined {
  if (value === undefined) return undefined;
  const parsed = Number.parseInt(value, 10);
  if (!Number.isFinite(parsed)) return undefined;
  return Math.min(MAX_CHAT_COCKPIT_HEIGHT, Math.max(MIN_CHAT_COCKPIT_HEIGHT, parsed));
}

function normalizeBrandChromeTheme(value?: string): "night-city" | "rust-forge" | undefined {
  const normalized = value?.trim().toLowerCase();
  if (!normalized) return undefined;
  if (
    normalized === "rust-forge" ||
    normalized === "rust" ||
    normalized === "cargo" ||
    normalized === "oxide" ||
    normalized === "forge" ||
    normalized === "oxidized-forge" ||
    normalized === "oxidized" ||
    normalized === "rust-native"
  ) {
    return "rust-forge";
  }
  if (
    normalized === "night-city" ||
    normalized === "night-city-ops" ||
    normalized === "neon-grid" ||
    normalized === "neon" ||
    normalized === "grid" ||
    normalized === "control" ||
    normalized === "omk-control" ||
    normalized === "metrics" ||
    normalized === "metrics-control" ||
    normalized === "cyberpunk"
  ) {
    return "night-city";
  }
  return undefined;
}

export function resolveCockpitChromeTheme(options: {
  brand?: string;
  env?: NodeJS.ProcessEnv;
}): "night-city" | "rust-forge" | undefined {
  // OMK_THEME is the user-selected control chrome theme. Chat `--brand omk`
  // is only the left-pane skin and must not overwrite an env/project theme.
  return normalizeBrandChromeTheme(options.env?.OMK_THEME)
    ?? normalizeBrandChromeTheme(options.brand);
}

export function buildRightPaneCommand(options: {
  nodeCmd: string;
  cliCmd: string;
  runId: string;
  refreshMs: number;
  redraw?: "diff" | "full" | "append";
  height?: number;
  theme?: string;
}): string {
  let cmd = `${options.nodeCmd} ${options.cliCmd} cockpit --run-id ${shellQuote(options.runId)} --watch --refresh ${options.refreshMs}`;
  const theme = normalizeBrandChromeTheme(options.theme);
  if (theme) {
    cmd = `OMK_THEME=${shellQuote(theme)} ${cmd}`;
  }
  if (options.redraw && options.redraw !== "diff") {
    cmd += ` --redraw ${shellQuote(options.redraw)}`;
  }
  if (options.height) {
    cmd += ` --height ${options.height}`;
  }
  return cmd;
}

// Active brand tmux chrome. Values are built at launch time so P/BRAND_HEX
// mutations from setBrandPaletteTheme() are reflected without changing imports.
export function buildTmuxBrandChromeOptions(brand?: string): {
  sessionOptions: Array<readonly [string, string]>;
  windowOptions: Array<readonly [string, string]>;
} {
  setBrandPaletteTheme(brand);
  const statusLeft = `#[fg=${BRAND_HEX.mint},bold] OMK//CONTROL #[fg=${BRAND_HEX.gray}]${getActiveBrandChromeLabel()}`;
  const statusRight = `#[fg=${BRAND_HEX.cyan}]#S #[fg=${BRAND_HEX.gray}]%H:%M`;
  return {
    sessionOptions: [
      ["status", "on"],
      ["status-style", `bg=${BRAND_HEX.dark},fg=${BRAND_HEX.cream}`],
      ["message-style", `bg=${BRAND_HEX.purple},fg=${BRAND_HEX.cream},bold`],
      ["window-status-style", `fg=${BRAND_HEX.gray},bg=${BRAND_HEX.dark}`],
      ["window-status-current-style", `fg=${BRAND_HEX.dark},bg=${BRAND_HEX.mint},bold`],
      ["status-left", statusLeft],
      ["status-right", statusRight],
    ],
    windowOptions: [
      ["pane-border-style", `fg=${BRAND_HEX.gridLine}`],
      ["pane-active-border-style", `fg=${BRAND_HEX.magenta}`],
    ],
  };
}

async function applyTmuxBrandTheme(session: string, cwd: string, brand?: string): Promise<void> {
  const chrome = buildTmuxBrandChromeOptions(brand);
  for (const [option, value] of chrome.sessionOptions) {
    const result = await runShell("tmux", ["set-option", "-t", session, option, value], { cwd, timeout: 5000 });
    if (result.failed) {
      console.warn(`Failed to set tmux ${option}: ${result.stderr || result.stdout}`);
    }
  }

  for (const [option, value] of chrome.windowOptions) {
    const result = await runShell("tmux", ["set-window-option", "-t", `${session}:chat`, option, value], { cwd, timeout: 5000 });
    if (result.failed) {
      console.warn(`Failed to set tmux ${option}: ${result.stderr || result.stdout}`);
    }
  }
}

function computeMainPaneWidth(terminalWidth: number, sideWidthPercent: number): number | undefined {
  if (!Number.isFinite(terminalWidth) || terminalWidth <= 0) return undefined;
  const rightColumns = Math.max(24, Math.round((terminalWidth * sideWidthPercent) / 100));
  return Math.max(20, terminalWidth - rightColumns);
}

async function pinTmuxRightDashboard(options: {
  session: string;
  cwd: string;
  leftPaneId: string;
  terminalWidth: number;
  sideWidthPercent: number;
}): Promise<void> {
  const mainPaneWidth = computeMainPaneWidth(options.terminalWidth, options.sideWidthPercent);
  if (mainPaneWidth !== undefined) {
    const widthResult = await runShell(
      "tmux",
      ["set-window-option", "-t", `${options.session}:chat`, "main-pane-width", String(mainPaneWidth)],
      { cwd: options.cwd, timeout: 5000 }
    );
    if (widthResult.failed) {
      console.warn(`Failed to pin tmux main pane width: ${widthResult.stderr || widthResult.stdout}`);
    }
  }

  const layoutResult = await runShell(
    "tmux",
    ["select-layout", "-t", `${options.session}:chat`, "main-vertical"],
    { cwd: options.cwd, timeout: 5000 }
  );
  if (layoutResult.failed) {
    console.warn(`Failed to pin tmux right dashboard layout: ${layoutResult.stderr || layoutResult.stdout}`);
  }

  const selectLeftResult = await runShell(
    "tmux",
    ["select-pane", "-t", options.leftPaneId],
    { cwd: options.cwd, timeout: 5000 }
  );
  if (selectLeftResult.failed) {
    console.warn(`Failed to reselect tmux chat pane after layout pin: ${selectLeftResult.stderr || selectLeftResult.stdout}`);
  }
}

export async function launchChatCockpit(options: LaunchChatCockpitOptions = {}): Promise<void> {
  if (isCockpitChild()) {
    throw new Error("Recursive cockpit launch detected");
  }

  const runId = options.runId ?? `chat-${Date.now()}`;
  const cwd = options.cwd ?? getProjectRoot();

  // Ensure run state exists before launching tmux so the right pane can read it
  await ensureChatRunState(cwd, runId);
  const sanitized = runId.replace(/[^a-zA-Z0-9]/g, "-");
  const session = `omk-chat-${sanitized}`;
  const brand = options.brand ?? "omk";
  const omkCli = process.argv[1] ? resolve(process.argv[1]) : "omk";
  const nodeCmd = process.execPath ? shellQuote(process.execPath) : "node";
  const cliCmd = shellQuote(omkCli);

  // 1. Reuse existing session if it already exists
  const hasSessionResult = await runShell(
    "tmux",
    ["has-session", "-t", session],
    { cwd, timeout: 5000 }
  );
  if (hasSessionResult.exitCode === 0) {
    // Session exists — attach to it instead of destroying active work.
    if (process.env.TMUX) {
      await runShell("tmux", ["switch-client", "-t", session], { cwd, timeout: 0, stdio: "inherit" });
    } else {
      await runShell("tmux", ["attach", "-t", session], { cwd, timeout: 0, stdio: "inherit" });
    }
    return;
  }

  // 2. Build commands
  const refreshMs = parseCockpitRefreshMs(options.cockpitRefresh);
  const sideWidth = parseCockpitSideWidth(options.cockpitSideWidth);
  const redraw = options.cockpitRedraw ?? "diff";
  const history = options.cockpitHistory ?? "static";

  const terminalWidth = process.stdout.columns ?? 0;
  const terminalHeight = process.stdout.rows ?? 0;
  const minHistoryPaneHeight = 5;
  const requestedCockpitHeight = parseCockpitHeight(options.cockpitHeight);
  const hasRoomForHistoryPane =
    history === "watch" &&
    terminalWidth >= 80 &&
    terminalHeight >= MIN_CHAT_COCKPIT_HEIGHT + minHistoryPaneHeight;

  const chromeTheme = resolveCockpitChromeTheme({ brand, env: process.env });
  const leftCmd = buildLeftPaneCommand({ nodeCmd, cliCmd, runId, brand, agentFile: options.agentFile, workers: options.workers, maxStepsPerTurn: options.maxStepsPerTurn, mcpScope: options.mcpScope, provider: options.provider, model: options.model, execution: options.execution, ui: options.ui });
  // When no explicit --cockpit-height is provided, pass undefined so the child
  // measures its actual tmux pane after splits/resizes and keeps the right rail pinned.
  const rightTopCmd = buildRightPaneCommand({
    nodeCmd,
    cliCmd,
    runId,
    refreshMs,
    redraw,
    height: requestedCockpitHeight,
    theme: chromeTheme,
  });
  const rightBottomCmd = `${nodeCmd} ${cliCmd} runs --watch --limit 15 --refresh 5000`;

  // 3. Create detached tmux session with left-pane command already running
  const createResult = await runShell(
    "tmux",
    ["new-session", "-d", "-s", session, "-n", "chat", "-e", "OMK_CHAT_COCKPIT_CHILD=1", leftCmd],
    { cwd, timeout: 10000 }
  );
  if (createResult.failed) {
    console.error(`Failed to create tmux session: ${createResult.stderr || createResult.stdout}`);
    process.exitCode = 1;
    return;
  }

  // 4. Get the original pane ID before splitting (works with any pane-base-index)
  const originalPanesResult = await runShell(
    "tmux",
    ["list-panes", "-t", `${session}:chat`, "-F", "#{pane_id}"],
    { cwd, timeout: 5000 }
  );
  if (originalPanesResult.failed) {
    console.error(`Failed to list panes: ${originalPanesResult.stderr || originalPanesResult.stdout}`);
    process.exitCode = 1;
    return;
  }
  const originalPaneIds = originalPanesResult.stdout
    .trim()
    .split(/\r?\n/)
    .filter((s) => s.length > 0);
  const leftPaneId = originalPaneIds[0];
  if (!leftPaneId) {
    throw new Error("No pane found in newly created tmux session");
  }

  // 5. Split window vertically for right pane using configured side width
  // Use -P -F #{pane_id} to capture the new pane ID regardless of pane-base-index.
  const sideWidthArg = `${sideWidth}%`;
  let splitResult = await runShell(
    "tmux",
    ["split-window", "-h", "-P", "-F", "#{pane_id}", "-t", `${session}:chat`, "-l", sideWidthArg, rightTopCmd],
    { cwd, timeout: 5000 }
  );
  if (splitResult.failed) {
    splitResult = await runShell(
      "tmux",
      ["split-window", "-h", "-P", "-F", "#{pane_id}", "-t", `${session}:chat`, "-p", String(sideWidth), rightTopCmd],
      { cwd, timeout: 5000 }
    );
  }
  let rightTopPaneId: string | undefined;
  if (!splitResult.failed) {
    rightTopPaneId = splitResult.stdout.trim().split(/\r?\n/).filter((s) => s.length > 0)[0];
  } else {
    const msg = `Failed to split tmux window: ${splitResult.stderr || splitResult.stdout}`;
    console.warn(msg);
  }

  // 6. Split right pane horizontally for bottom history pane only when there's enough space.
  // Keep the history pane small and fixed so it cannot steal space from the cockpit rail.
  if (hasRoomForHistoryPane && rightTopPaneId) {
    let bottomSplitResult = await runShell(
      "tmux",
      ["split-window", "-v", "-P", "-F", "#{pane_id}", "-t", rightTopPaneId, "-l", String(minHistoryPaneHeight), rightBottomCmd],
      { cwd, timeout: 5000 }
    );
    if (bottomSplitResult.failed) {
      bottomSplitResult = await runShell(
        "tmux",
        ["split-window", "-v", "-P", "-F", "#{pane_id}", "-t", rightTopPaneId, "-p", "25", rightBottomCmd],
        { cwd, timeout: 5000 }
      );
    }
    if (bottomSplitResult.failed) {
      console.warn(`Failed to split bottom pane: ${bottomSplitResult.stderr || bottomSplitResult.stdout}`);
    }
  }

  await pinTmuxRightDashboard({ session, cwd, leftPaneId, terminalWidth, sideWidthPercent: sideWidth });

  // 7. Enable mouse mode so scrolling shows output history, not shell input history
  const mouseResult = await runShell(
    "tmux",
    ["set-option", "-t", session, "mouse", "on"],
    { cwd, timeout: 5000 }
  );
  if (mouseResult.failed) {
    console.warn(`Failed to enable tmux mouse mode: ${mouseResult.stderr || mouseResult.stdout}`);
  }

  // Increase scrollback history so previous code edits remain accessible
  const historyResult = await runShell(
    "tmux",
    ["set-option", "-t", session, "history-limit", "10000"],
    { cwd, timeout: 5000 }
  );
  if (historyResult.failed) {
    console.warn(`Failed to set tmux history limit: ${historyResult.stderr || historyResult.stdout}`);
  }

  await applyTmuxBrandTheme(session, cwd, chromeTheme);

  // 8. Set a hook so the session is destroyed when the chat pane dies
  const hookResult = await runShell(
    "tmux",
    ["set-hook", "-t", leftPaneId, "pane-died", `if-shell -F '#{==:#{hook_pane},${leftPaneId}}' 'kill-session -t ${session}'`],
    { cwd, timeout: 5000 }
  );
  if (hookResult.failed) {
    const msg = `Failed to set tmux hook: ${hookResult.stderr || hookResult.stdout}`;
    console.warn(msg);
  }

  // 8. Select the left pane
  const selectResult = await runShell(
    "tmux",
    ["select-pane", "-t", leftPaneId],
    { cwd, timeout: 5000 }
  );
  if (selectResult.failed) {
    console.warn(`Failed to select left pane: ${selectResult.stderr || selectResult.stdout}`);
  }

  // 8. Attach to the session (avoid nested-session warning when already inside tmux)
  if (process.env.TMUX) {
    await runShell("tmux", ["switch-client", "-t", session], { cwd, timeout: 0, stdio: "inherit" });
  } else {
    await runShell("tmux", ["attach", "-t", session], { cwd, timeout: 0, stdio: "inherit" });
  }
}

export function buildLeftPaneCommand(options: {
  nodeCmd: string;
  cliCmd: string;
  runId: string;
  brand: string;
  agentFile?: string;
  workers?: string;
  maxStepsPerTurn?: string;
  mcpScope?: string;
  provider?: string;
  model?: string;
  execution?: string;
  ui?: string;
}): string {
  const { nodeCmd, cliCmd, runId, brand, agentFile, workers, maxStepsPerTurn, mcpScope, provider, model, execution, ui } = options;
  let cmd = `${nodeCmd} ${cliCmd} chat --layout plain --run-id ${shellQuote(runId)} --brand ${shellQuote(brand)}`;
  if (agentFile) cmd += ` --agent-file ${shellQuote(agentFile)}`;
  if (workers) cmd += ` --workers ${shellQuote(workers)}`;
  if (maxStepsPerTurn) cmd += ` --max-steps-per-turn ${shellQuote(maxStepsPerTurn)}`;
  if (mcpScope) cmd += ` --mcp-scope ${shellQuote(mcpScope)}`;
  if (provider) cmd += ` --provider ${shellQuote(provider)}`;
  if (model) cmd += ` --model ${shellQuote(model)}`;
  if (execution) cmd += ` --execution ${shellQuote(execution)}`;
  if (ui) cmd += ` --ui ${shellQuote(ui)}`;
  return cmd;
}

export function shellQuote(value: string): string {
  return `'${value.replace(/'/g, "'\\''")}'`;
}
