import { spawnSync } from "node:child_process";
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { basename, dirname, join, resolve } from "node:path";
import { discoverKxmProjectRoot, kxmResourceIdentifier, loadKxmProject } from "../project-config.ts";
import { KxmRunEventStore, KxmRuntimeRegistry, kxmProjectRunEventsPath, kxmRuntimePaths } from "../runtime-store.ts";
import { attachKxmSupervisor, ensureKxmSupervisor, kxmRuntimeRequest, kxmSupervisorRecordIsLive } from "../runtime-supervisor.ts";
import { print, printPlan, workspaceDirs, type Runtime } from "./types.ts";

/** One worktree lane recorded in the control checkout. The sha is the base,
 * not the ref: worktrees share refs, and a later fetch moves origin/main. */
export interface LaneRecord {
  path: string;
  branch: string;
  baseRef: string;
  baseSha: string;
  createdAt: string;
  lastRunId?: string;
}

interface LaneFile {
  schema: "kxm.lanes.v1";
  lanes: Record<string, LaneRecord>;
}

export interface LaneView extends LaneRecord {
  unit: string;
  dirty: number | null;
  ahead: number | null;
  behind: number | null;
  exists: boolean;
}

const LANE_SCHEMA = "kxm.lanes.v1";
const SHA_RE = /^[a-f0-9]{40,64}$/;
const TIMESTAMP_RE = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{1,9})?Z$/;
const SETTLED_RUN = new Set(["completed", "failed", "cancelled"]);
const LANE_RUN_LIST_LIMIT = 10_000;

function lanesFile(runtime: Runtime): string {
  return join(runtime.dirs.state, "lanes.json");
}

function gitEnv(): NodeJS.ProcessEnv {
  return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
}

function git(cwd: string, args: readonly string[]): { status: number; stdout: string; stderr: string } {
  const result = spawnSync("git", ["-C", cwd, ...args], {
    encoding: "utf8",
    env: gitEnv(),
    timeout: 30_000,
    windowsHide: true,
  });
  return {
    status: result.status ?? 1,
    stdout: result.stdout ?? "",
    stderr: `${result.stderr ?? ""}${result.error ? result.error.message : ""}`,
  };
}

function refuse(runtime: Runtime, command: string, error: string, text: string, extra: Record<string, unknown> = {}): number {
  print(runtime.io, runtime.json, { ok: false, command, error, ...extra }, text);
  return 1;
}

function isLaneRecord(value: unknown): value is LaneRecord {
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
  const record = value as Record<string, unknown>;
  const keys = Object.keys(record);
  const allowed = new Set(["path", "branch", "baseRef", "baseSha", "createdAt", "lastRunId"]);
  if (keys.some((key) => !allowed.has(key))) return false;
  if (typeof record.path !== "string" || record.path.length === 0) return false;
  if (typeof record.branch !== "string" || record.branch.length === 0) return false;
  if (typeof record.baseRef !== "string" || record.baseRef.length === 0) return false;
  if (typeof record.baseSha !== "string" || !SHA_RE.test(record.baseSha)) return false;
  if (typeof record.createdAt !== "string" || !TIMESTAMP_RE.test(record.createdAt)) return false;
  if (record.lastRunId !== undefined && (typeof record.lastRunId !== "string" || record.lastRunId.length === 0)) return false;
  return true;
}

function isLaneFile(value: unknown): value is LaneFile {
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
  const file = value as Record<string, unknown>;
  if (Object.keys(file).some((key) => key !== "schema" && key !== "lanes")) return false;
  if (file.schema !== LANE_SCHEMA || !file.lanes || typeof file.lanes !== "object" || Array.isArray(file.lanes)) return false;
  for (const [unit, record] of Object.entries(file.lanes as Record<string, unknown>)) {
    if (!kxmResourceIdentifier(unit) || !isLaneRecord(record)) return false;
  }
  return true;
}

/** Read `<state>/lanes.json`. A missing file is an empty record. A malformed
 * file is refused; it is never replaced with an empty record. */
function loadLanes(runtime: Runtime, command: string): LaneFile | number {
  const file = lanesFile(runtime);
  if (!existsSync(file)) return { schema: LANE_SCHEMA, lanes: {} };
  try {
    const parsed: unknown = JSON.parse(readFileSync(file, "utf8"));
    if (!isLaneFile(parsed)) return refuse(runtime, command, "lanes_unreadable", `lane record ${file} is malformed`);
    return parsed;
  } catch {
    return refuse(runtime, command, "lanes_unreadable", `lane record ${file} is unreadable`);
  }
}

function writeLanes(runtime: Runtime, file: LaneFile): void {
  const target = lanesFile(runtime);
  mkdirSync(runtime.dirs.state, { recursive: true });
  const lanes: Record<string, LaneRecord> = {};
  for (const unit of Object.keys(file.lanes).sort()) {
    const record = file.lanes[unit];
    if (!record) continue;
    lanes[unit] = {
      path: record.path,
      branch: record.branch,
      baseRef: record.baseRef,
      baseSha: record.baseSha,
      createdAt: record.createdAt,
      ...(record.lastRunId ? { lastRunId: record.lastRunId } : {}),
    };
  }
  writeFileSync(target, `${JSON.stringify({ schema: LANE_SCHEMA, lanes }, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
  try { chmodSync(target, 0o600); } catch { /* Windows may not expose POSIX modes. */ }
}

function controlRoot(runtime: Runtime, command: string): string | number {
  const root = discoverKxmProjectRoot(runtime.cwd);
  if (!root) return refuse(runtime, command, "project_required", `${command} requires a KXM project (run kxm init first)`);
  return root;
}

function refuseUnit(runtime: Runtime, command: string, unit: string): number | undefined {
  if (kxmResourceIdentifier(unit)) return undefined;
  return refuse(runtime, command, "lane_unit_invalid", `lane unit ${unit} must match the KXM identifier pattern`);
}

function laneWorktreePath(projectRoot: string, unit: string): string {
  return resolve(dirname(projectRoot), `${basename(projectRoot)}-${unit}`);
}

function branchExists(projectRoot: string, unit: string): boolean {
  return git(projectRoot, ["rev-parse", "--verify", "--quiet", `refs/heads/${unit}`]).status === 0;
}

function resolveBase(projectRoot: string, base: string): string | undefined {
  const result = git(projectRoot, ["rev-parse", "--verify", "--end-of-options", `${base}^{commit}`]);
  if (result.status !== 0) return undefined;
  const sha = result.stdout.trim();
  return SHA_RE.test(sha) ? sha : undefined;
}

/** The only `--lane` mechanism: discovery cwd and workspace dirs come from the lane path. */
export function withLaneCwd(runtime: Runtime, unit: string, command = "lane"): Runtime | number {
  const invalid = refuseUnit(runtime, command, unit);
  if (invalid !== undefined) return invalid;
  const loaded = loadLanes(runtime, command);
  if (typeof loaded === "number") return loaded;
  const lane = loaded.lanes[unit];
  if (!lane || !existsSync(lane.path)) {
    return refuse(runtime, command, "lane_missing", `lane ${unit} is not recorded or its worktree is absent`, { unit });
  }
  return {
    ...runtime,
    cwd: lane.path,
    dirs: workspaceDirs(lane.path, undefined, runtime.env),
  };
}

export function applyLane(runtime: Runtime, unit: string | undefined, command: string): Runtime | number {
  if (unit === undefined) return runtime;
  return withLaneCwd(runtime, unit, command);
}

/** `--lane` for `kxm runs`: same cwd swap as `withLaneCwd`, or the original runtime when unset. */
export function runtimeForLane(runtime: Runtime, unit: string | undefined): Runtime | number {
  return applyLane(runtime, unit, "lane");
}

interface StartedRun {
  code: number;
  runId?: string;
}

/** Shared path for `kxm run --brief --lane` and `kxm lane run`. */
async function startRecordedRun(
  runtime: Runtime,
  workflow: string | undefined,
  promptParts: string[],
  options: { brief?: string; lane?: string; command: string; allowDefaultWorkflow?: boolean; skipOpenCheck?: boolean },
): Promise<StartedRun> {
  let prompt: string | undefined;
  if (options.brief !== undefined) {
    if (promptParts.length > 0) {
      return { code: refuse(runtime, options.command, "brief_and_prompt", "pass either --brief or a prompt, not both", { brief: options.brief }) };
    }
    const brief = readRunBrief(runtime, options.brief, options.command);
    if (typeof brief === "number") return { code: brief };
    prompt = brief.text;
  }
  let active = runtime;
  if (options.lane !== undefined) {
    const scoped = withLaneCwd(runtime, options.lane, options.command);
    if (typeof scoped === "number") return { code: scoped };
    active = scoped;
    if (options.skipOpenCheck !== true) {
      const open = await refuseIfLaneRunOpen(runtime, options.lane, options.command);
      if (open !== undefined) return { code: open };
    }
  }
  const project = await import("./project.ts");
  const runIdOut: { runId?: string } = {};
  const code = await project.cmdKxmRun(active, workflow, prompt === undefined ? promptParts : [], false, {
    ...(options.brief !== undefined ? { brief: options.brief } : {}),
    ...(prompt !== undefined ? { prompt } : {}),
    ...(options.allowDefaultWorkflow ? { allowDefaultWorkflow: true } : {}),
    runIdOut,
  });
  if (code !== 0 || !runIdOut.runId) return { code, ...(runIdOut.runId ? { runId: runIdOut.runId } : {}) };
  if (options.lane !== undefined) {
    const recorded = recordLaneRun(runtime, options.lane, runIdOut.runId, options.command);
    if (recorded !== undefined) return { code: recorded };
  }
  return { code: 0, runId: runIdOut.runId };
}

export async function cmdKxmRunCli(
  runtime: Runtime,
  workflow: string | undefined,
  promptParts: string[],
  options: { brief?: string; lane?: string } = {},
): Promise<number> {
  const started = await startRecordedRun(runtime, workflow, promptParts, { ...options, command: "run" });
  return started.code;
}

export function readRunBrief(runtime: Runtime, file: string, command: string): { text: string } | number {
  try {
    return { text: readFileSync(resolve(runtime.cwd, file), "utf8").trim() };
  } catch {
    return refuse(runtime, command, "brief_unreadable", `cannot read brief ${file}`, { brief: file });
  }
}

function porcelainCount(path: string): number | null {
  const result = git(path, ["status", "--porcelain"]);
  if (result.status !== 0) return null;
  return result.stdout.split("\n").filter((line) => line.length > 0).length;
}

/** Left count is commits on the base that HEAD lacks (behind). Right is the reverse (ahead). */
function aheadBehind(path: string, baseSha: string): { ahead: number | null; behind: number | null } {
  const result = git(path, ["rev-list", "--left-right", "--count", `${baseSha}...HEAD`]);
  const match = result.status === 0 ? result.stdout.trim().match(/^(\d+)\s+(\d+)$/) : null;
  if (!match) return { ahead: null, behind: null };
  return { behind: Number(match[1]), ahead: Number(match[2]) };
}

function laneView(unit: string, record: LaneRecord): LaneView {
  const exists = existsSync(record.path);
  const counts = exists ? aheadBehind(record.path, record.baseSha) : { ahead: null, behind: null };
  return {
    unit,
    path: record.path,
    branch: record.branch,
    baseRef: record.baseRef,
    baseSha: record.baseSha,
    createdAt: record.createdAt,
    ...(record.lastRunId ? { lastRunId: record.lastRunId } : {}),
    dirty: exists ? porcelainCount(record.path) : null,
    ahead: counts.ahead,
    behind: counts.behind,
    exists,
  };
}

function formatCount(value: number | null): string {
  return value === null ? "?" : String(value);
}

function formatLaneLine(view: LaneView): string {
  return [
    view.unit,
    view.path,
    `branch=${view.branch}`,
    `base=${view.baseSha}`,
    `dirty=${formatCount(view.dirty)}`,
    `ahead=${formatCount(view.ahead)}`,
    `behind=${formatCount(view.behind)}`,
    `exists=${view.exists}`,
  ].join(" ");
}

/** A running supervisor can answer. This never starts one. A test seam stands in for that process. */
async function projectedRunStatus(runtime: Runtime, projectRoot: string, runId: string, allowStart: boolean): Promise<string> {
  try {
    const project = await import("./project.ts");
    const request = project.kxmDriveCliSeams.runtimeRequest ?? kxmRuntimeRequest;
    // `lane status` never starts the supervisor. Run and drop may, except under --dry-run.
    const handle = !allowStart || runtime.dryRun
      ? await attachKxmSupervisor({ env: runtime.env })
      : await (project.kxmDriveCliSeams.ensureSupervisor ?? ensureKxmSupervisor)({ env: runtime.env });
    if (!handle) return "unknown";
    const result = await request(
      handle,
      "GET",
      `/v1/runs/${encodeURIComponent(runId)}?projectRoot=${encodeURIComponent(projectRoot)}`,
    );
    const run = result.run;
    if (!run || typeof run !== "object" || Array.isArray(run)) return "unknown";
    const status = (run as { status?: unknown }).status;
    return typeof status === "string" && status.length > 0 ? status : "unknown";
  } catch {
    return "unknown";
  }
}

function isSettled(status: string): boolean {
  return SETTLED_RUN.has(status);
}

/** Runs in the lane event store. Undefined when the lane has no store and no supervisor list. */
async function laneStoreRuns(runtime: Runtime, lanePath: string): Promise<Array<{ runId: string; status: string }> | undefined> {
  const project = await import("./project.ts");
  const request = project.kxmDriveCliSeams.runtimeRequest ?? kxmRuntimeRequest;
  const handle = await attachKxmSupervisor({ env: runtime.env });
  const projectId = (): string => String(loadKxmProject(lanePath, {}).project.value.id);
  if (handle) {
    try {
      const result = await request(
        handle,
        "GET",
        `/v1/projects/${encodeURIComponent(projectId())}/runs?projectRoot=${encodeURIComponent(lanePath)}&limit=${LANE_RUN_LIST_LIMIT}`,
      );
      const runs = Array.isArray(result.runs) ? result.runs : [];
      return runs.flatMap((run) => {
        if (!run || typeof run !== "object" || Array.isArray(run)) return [];
        const record = run as { runId?: unknown; status?: unknown };
        if (typeof record.runId !== "string" || record.runId.length === 0) return [];
        const status = typeof record.status === "string" && record.status.length > 0 ? record.status : "unknown";
        return [{ runId: record.runId, status }];
      });
    } catch {
      // The supervisor could not list this root. The file read below still can.
    }
  }
  const eventsPath = kxmProjectRunEventsPath(lanePath, runtime.env);
  if (!existsSync(eventsPath)) return undefined;
  const store = new KxmRunEventStore(eventsPath);
  try {
    return store.runsForProject(projectId(), LANE_RUN_LIST_LIMIT).map((run) => ({ runId: run.runId, status: run.status }));
  } finally {
    store.close();
  }
}

async function refuseIfLaneStoreRunsOpen(runtime: Runtime, unit: string, command: string): Promise<number | undefined> {
  const loaded = loadLanes(runtime, command);
  if (typeof loaded === "number") return loaded;
  const lane = loaded.lanes[unit];
  if (!lane) return undefined;
  let runs: Array<{ runId: string; status: string }> | undefined;
  try {
    runs = await laneStoreRuns(runtime, lane.path);
  } catch (error) {
    const detail = error instanceof Error ? error.message : "run list failed";
    return refuse(runtime, command, "lane_run_open", `lane ${unit} runs could not be read: ${detail.slice(0, 200)}`, { unit });
  }
  if (!runs) return undefined;
  const unsettled = runs.filter((run) => !isSettled(run.status)).map((run) => run.runId);
  if (unsettled.length === 0) return undefined;
  return refuse(
    runtime,
    command,
    "lane_run_open",
    `lane ${unit} run ${unsettled.join(", ")} is unsettled`,
    { unit, runIds: unsettled },
  );
}

export async function refuseIfLaneRunOpen(runtime: Runtime, unit: string, command: string): Promise<number | undefined> {
  const loaded = loadLanes(runtime, command);
  if (typeof loaded === "number") return loaded;
  const lane = loaded.lanes[unit];
  if (!lane?.lastRunId) return undefined;
  const runStatus = await projectedRunStatus(runtime, lane.path, lane.lastRunId, true);
  if (isSettled(runStatus)) return undefined;
  return refuse(
    runtime,
    command,
    "lane_run_open",
    `lane ${unit} run ${lane.lastRunId} is ${runStatus}`,
    { unit, runId: lane.lastRunId, runStatus },
  );
}

export function recordLaneRun(runtime: Runtime, unit: string, runId: string, command: string): number | undefined {
  const loaded = loadLanes(runtime, command);
  if (typeof loaded === "number") return loaded;
  const lane = loaded.lanes[unit];
  if (!lane) return refuse(runtime, command, "lane_missing", `lane ${unit} is not recorded or its worktree is absent`, { unit });
  lane.lastRunId = runId;
  try {
    writeLanes(runtime, loaded);
  } catch {
    return refuse(runtime, command, "lanes_unreadable", `lane record ${lanesFile(runtime)} could not be written`);
  }
  return undefined;
}

export async function cmdLaneCreate(runtime: Runtime, unit: string, options: { base?: string } = {}): Promise<number> {
  const command = "lane create";
  const invalid = refuseUnit(runtime, command, unit);
  if (invalid !== undefined) return invalid;
  const root = controlRoot(runtime, command);
  if (typeof root === "number") return root;
  const base = options.base?.trim() || "origin/main";
  const loaded = loadLanes(runtime, command);
  if (typeof loaded === "number") return loaded;
  const path = laneWorktreePath(root, unit);
  if (loaded.lanes[unit] || existsSync(path) || branchExists(root, unit)) {
    return refuse(runtime, command, "lane_exists", `lane ${unit} already exists`, { unit });
  }
  const baseSha = resolveBase(root, base);
  if (!baseSha) {
    return refuse(
      runtime,
      command,
      "lane_base_unresolved",
      `base ${base} does not resolve; git fetch origin`,
      { unit, base, hint: "git fetch origin" },
    );
  }
  if (runtime.dryRun) {
    printPlan(runtime, { command, unit, base, baseSha, path }, [
      { action: "write", target: path },
      { action: "write", target: lanesFile(runtime) },
    ], `create lane ${unit} at ${path} from ${baseSha}`);
    return 0;
  }
  const added = git(root, ["worktree", "add", "-b", unit, path, baseSha]);
  if (added.status !== 0) {
    return refuse(runtime, command, "lane_git_failed", `git worktree add failed: ${added.stderr.trim().slice(0, 300)}`, { unit });
  }
  // Same string `discoverKxmProjectRoot` will return when `--lane` swaps cwd.
  const recordedPath = discoverKxmProjectRoot(path) ?? path;
  if (!existsSync(join(recordedPath, ".kxm", "project.yaml"))) {
    git(root, ["worktree", "remove", "--force", path]);
    return refuse(runtime, command, "lane_not_project", `lane ${recordedPath} has no .kxm/project.yaml; the worktree was removed`, { unit, path: recordedPath });
  }
  const lane: LaneRecord = {
    path: recordedPath,
    branch: unit,
    baseRef: base,
    baseSha,
    createdAt: new Date().toISOString(),
  };
  loaded.lanes[unit] = lane;
  try {
    writeLanes(runtime, loaded);
  } catch {
    git(root, ["worktree", "remove", "--force", path]);
    return refuse(runtime, command, "lanes_unreadable", `lane record ${lanesFile(runtime)} could not be written`, { unit });
  }
  print(runtime.io, runtime.json, { ok: true, command, unit, lane }, formatLaneLine(laneView(unit, lane)));
  return 0;
}

export async function cmdLaneList(runtime: Runtime): Promise<number> {
  const command = "lane list";
  const loaded = loadLanes(runtime, command);
  if (typeof loaded === "number") return loaded;
  const lanes = Object.keys(loaded.lanes).sort().map((unit) => laneView(unit, loaded.lanes[unit]!));
  print(
    runtime.io,
    runtime.json,
    { ok: true, command, lanes },
    lanes.length === 0 ? "no lanes" : lanes.map((lane) => formatLaneLine(lane)).join("\n"),
  );
  return 0;
}

export async function cmdLaneStatus(runtime: Runtime, unit: string): Promise<number> {
  const command = "lane status";
  const invalid = refuseUnit(runtime, command, unit);
  if (invalid !== undefined) return invalid;
  const loaded = loadLanes(runtime, command);
  if (typeof loaded === "number") return loaded;
  const record = loaded.lanes[unit];
  if (!record) return refuse(runtime, command, "lane_missing", `lane ${unit} is not recorded`, { unit });
  const view = laneView(unit, record);
  const runStatus = record.lastRunId ? await projectedRunStatus(runtime, record.path, record.lastRunId, false) : "unknown";
  print(
    runtime.io,
    runtime.json,
    { ok: true, command, root: record.path, lane: { ...view, status: runStatus } },
    `${formatLaneLine(view)} status=${runStatus} root=${record.path}`,
  );
  return 0;
}

/** Remove the lane root from the Runtime registry. A stopped supervisor is not
 * an error: the row is deleted from the registry file when that file exists.
 * The delete holds the write lock and rechecks the supervisor row; a live
 * supervisor is left for the unregister route. */
async function unregisterLaneRoot(runtime: Runtime, projectRoot: string, force: boolean): Promise<string | undefined> {
  const body = force ? { projectRoot, force: true as const } : { projectRoot };
  try {
    const project = await import("./project.ts");
    const request = project.kxmDriveCliSeams.runtimeRequest ?? kxmRuntimeRequest;
    const post = async (handle: { runtimeId: string; port: number; token: string; started: boolean }): Promise<void> => {
      await request(handle, "POST", "/v1/projects/unregister", body);
    };
    const handle = await attachKxmSupervisor({ env: runtime.env });
    if (handle) {
      await post(handle);
      return undefined;
    }
    const paths = kxmRuntimePaths({ env: runtime.env });
    if (!existsSync(paths.registryDb)) return undefined;
    const registry = new KxmRuntimeRegistry(paths.registryDb);
    let outcome: "removed" | "absent" | "supervisor_live";
    try {
      outcome = registry.unregisterProjectIfIdle(projectRoot, kxmSupervisorRecordIsLive);
    } finally {
      registry.close();
    }
    if (outcome === "supervisor_live") {
      const live = await attachKxmSupervisor({ env: runtime.env });
      if (!live) return "runtime supervisor is live but could not be reached";
      await post(live);
    }
    return undefined;
  } catch (error) {
    return error instanceof Error ? error.message : "lane root could not be unregistered";
  }
}

export async function cmdLaneDrop(runtime: Runtime, unit: string, options: { force?: boolean } = {}): Promise<number> {
  const command = "lane drop";
  const invalid = refuseUnit(runtime, command, unit);
  if (invalid !== undefined) return invalid;
  const root = controlRoot(runtime, command);
  if (typeof root === "number") return root;
  const loaded = loadLanes(runtime, command);
  if (typeof loaded === "number") return loaded;
  const record = loaded.lanes[unit];
  if (!record) return refuse(runtime, command, "lane_missing", `lane ${unit} is not recorded`, { unit });
  const force = options.force === true;
  if (!force) {
    if (existsSync(record.path)) {
      const dirty = porcelainCount(record.path);
      if (dirty === null) {
        return refuse(runtime, command, "lane_git_failed", `lane ${unit} worktree status could not be read`, { unit });
      }
      if (dirty !== 0) {
        return refuse(runtime, command, "lane_dirty", `lane ${unit} has ${dirty} uncommitted change(s)`, { unit, dirty });
      }
    }
    const listed = await refuseIfLaneStoreRunsOpen(runtime, unit, command);
    if (listed !== undefined) return listed;
    const open = await refuseIfLaneRunOpen(runtime, unit, command);
    if (open !== undefined) return open;
  }
  if (runtime.dryRun) {
    printPlan(runtime, { command, unit, branchKept: record.branch }, [
      { action: "delete", target: `runtime registry ${record.path}` },
      { action: "delete", target: record.path },
      { action: "write", target: lanesFile(runtime) },
    ], `drop lane ${unit}; branch ${record.branch} is not deleted`);
    return 0;
  }
  const unregistered = await unregisterLaneRoot(runtime, record.path, force);
  if (unregistered !== undefined) {
    return refuse(runtime, command, "lane_unregister_failed", `lane ${unit} could not be unregistered: ${unregistered.slice(0, 300)}`, { unit });
  }
  if (existsSync(record.path)) {
    const removed = git(root, ["worktree", "remove", ...(force ? ["--force"] : []), record.path]);
    if (removed.status !== 0) {
      return refuse(runtime, command, "lane_git_failed", `git worktree remove failed: ${removed.stderr.trim().slice(0, 300)}`, { unit });
    }
  }
  delete loaded.lanes[unit];
  try {
    writeLanes(runtime, loaded);
  } catch {
    return refuse(runtime, command, "lanes_unreadable", `lane record ${lanesFile(runtime)} could not be written`, { unit });
  }
  print(
    runtime.io,
    runtime.json,
    { ok: true, command, unit, branch: record.branch, branchDeleted: false },
    `dropped lane ${unit}; branch ${record.branch} was not deleted`,
  );
  return 0;
}

export async function cmdLaneRun(
  runtime: Runtime,
  unit: string,
  options: { brief?: string; workflow?: string; base?: string; wait?: boolean; timeoutMs?: string },
): Promise<number> {
  const command = "lane run";
  const invalid = refuseUnit(runtime, command, unit);
  if (invalid !== undefined) return invalid;
  if (!options.brief) return refuse(runtime, command, "brief_unreadable", "lane run requires --brief <file>");
  const brief = readRunBrief(runtime, options.brief, command);
  if (typeof brief === "number") return brief;
  const loaded = loadLanes(runtime, command);
  if (typeof loaded === "number") return loaded;
  const existed = Boolean(loaded.lanes[unit]);
  if (!existed) {
    const created = await cmdLaneCreate(runtime, unit, options.base !== undefined ? { base: options.base } : {});
    if (created !== 0) return created;
  }
  if (runtime.dryRun) {
    if (existed) {
      const open = await refuseIfLaneRunOpen(runtime, unit, command);
      if (open !== undefined) return open;
    }
    const root = controlRoot(runtime, command);
    const path = existed ? loaded.lanes[unit]!.path : (typeof root === "number" ? unit : laneWorktreePath(root, unit));
    printPlan(runtime, { command, unit, brief: options.brief }, [
      { action: "request", target: `POST /v1/runs projectRoot=${path}` },
      { action: "request", target: `POST /v1/runs/<new>/drive projectRoot=${path}` },
    ], `run lane ${unit} from ${options.brief} and drive it`);
    return 0;
  }
  const open = await refuseIfLaneRunOpen(runtime, unit, command);
  if (open !== undefined) return open;
  const outcome = await startRecordedRun(runtime, options.workflow, [], {
    brief: options.brief,
    lane: unit,
    command,
    allowDefaultWorkflow: true,
    skipOpenCheck: true,
  });
  if (outcome.code !== 0 || !outcome.runId) return outcome.code === 0 ? 1 : outcome.code;
  const scoped = withLaneCwd(runtime, unit, command);
  if (typeof scoped === "number") return scoped;
  const project = await import("./project.ts");
  return project.cmdKxmRunDrive(scoped, outcome.runId, false, {
    wait: options.wait === true,
    ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
  });
}
