import { spawn } from "node:child_process";
import { tmpdir } from "node:os";
import { projectPublicActivityItem } from "./public-thread-item.js";
import { sanitizePublicText } from "./public-sanitizer.js";

export const INTERNAL_GENERATED_IMAGE_ITEM = Symbol("generatedImageItem");

function createPendingTurnStart() {
  let resolveCancellation;
  let rejectCancellation;
  const cancellation = new Promise((resolve, reject) => {
    resolveCancellation = resolve;
    rejectCancellation = reject;
  });
  cancellation.catch(() => {});
  return {
    canceled: false,
    interrupted: false,
    startSettled: false,
    cancellation,
    resolveCancellation,
    rejectCancellation,
    cancellationSettled: false
  };
}

function settleStartCancellation(attempt, error) {
  if (!attempt?.canceled || attempt.cancellationSettled) return;
  attempt.cancellationSettled = true;
  if (error) attempt.rejectCancellation(error);
  else attempt.resolveCancellation(true);
}

function unconfirmedTurnStopError() {
  const error = new Error("turn_stop_unconfirmed");
  error.status = 409;
  return error;
}

function unknownTurnStartOutcomeError() {
  const error = new Error("turn_start_outcome_unknown");
  error.status = 409;
  return error;
}

export async function startCodexRuntime({ codexCliPath, codexHome, cwd, emit, threadId, clientFactory = (command, home, callback) => new CodexRpcClient(command, home, callback) }) {
  let activeTurnId;
  let pendingStart;
  const runtimeEmit = (event, data) => {
    if (event === "turn.started") activeTurnId = data.turnId;
    if (event === "turn.completed" && data.turnId === activeTurnId) activeTurnId = undefined;
    emit(event, data);
  };
  const client = clientFactory(codexCliPath, codexHome, runtimeEmit);
  await client.start();
  const threadRequest = getThreadRequest(threadId, cwd);
  const thread = await client.request(threadRequest.method, threadRequest.params);
  activeTurnId = activeTurnIdFromThread(thread.thread);

  return {
    threadId: thread.thread?.id,
    async sendMessage(message) {
      const payload = typeof message === "string" ? { text: message } : message;
      const attempt = createPendingTurnStart();
      pendingStart = attempt;
      let result;
      try {
        result = await client.request("turn/start", buildTurnStartParams({
          threadId: thread.thread?.id,
          cwd,
          text: payload.text,
          options: payload.options,
          attachments: payload.attachments,
          skills: payload.skills
        }));
      } catch (startError) {
        attempt.startSettled = true;
        if (activeTurnId) {
          try {
            await client.request("turn/interrupt", { threadId: thread.thread?.id, turnId: activeTurnId });
            activeTurnId = undefined;
            attempt.interrupted = true;
          } catch {
            const error = unconfirmedTurnStopError();
            settleStartCancellation(attempt, error);
            throw error;
          }
        }
        if (attempt.canceled) {
          settleStartCancellation(attempt);
          if (pendingStart === attempt) pendingStart = undefined;
          throw new Error("turn_start_canceled");
        }
        if (pendingStart === attempt) pendingStart = undefined;
        throw startError;
      }
      attempt.startSettled = true;
      try {
        activeTurnId = result.turn?.id ?? activeTurnId;
        if (attempt.canceled) {
          if (activeTurnId && !attempt.interrupted) {
            try {
              await client.request("turn/interrupt", { threadId: thread.thread?.id, turnId: activeTurnId });
            } catch {
              const error = unconfirmedTurnStopError();
              settleStartCancellation(attempt, error);
              throw error;
            }
          }
          activeTurnId = undefined;
          attempt.interrupted = true;
          settleStartCancellation(attempt);
          throw new Error("turn_start_canceled");
        }
        return activeTurnId;
      } finally {
        if (pendingStart === attempt) pendingStart = undefined;
      }
    },
    async cancelPendingStart() {
      if (!pendingStart && !activeTurnId) return false;
      if (pendingStart) pendingStart.canceled = true;
      if (activeTurnId) {
        try {
          await client.request("turn/interrupt", { threadId: thread.thread?.id, turnId: activeTurnId });
        } catch {
          const error = unconfirmedTurnStopError();
          settleStartCancellation(pendingStart, error);
          throw error;
        }
        if (pendingStart) pendingStart.interrupted = true;
        activeTurnId = undefined;
        settleStartCancellation(pendingStart);
      }
      if (pendingStart?.startSettled && pendingStart.cancellationSettled) pendingStart = undefined;
      if (pendingStart && !pendingStart.cancellationSettled) await pendingStart.cancellation;
      return true;
    },
    async steerMessage(message) {
      const text = typeof message === "string" ? message : message.text;
      if (!activeTurnId) throw new Error("active_turn_unavailable");
      const result = await client.request("turn/steer", {
        threadId: thread.thread?.id,
        expectedTurnId: activeTurnId,
        input: [{ type: "text", text }]
      });
      return result.turnId;
    },
    resolveApproval(requestId, decision, approval) {
      client.respond(requestId, approvalResponse(approval, decision));
    },
    async interrupt() {
      if (!activeTurnId) throw new Error("active_turn_unavailable");
      return client.request("turn/interrupt", {
        threadId: thread.thread?.id,
        turnId: activeTurnId
      });
    },
    async stop() {
      client.stop();
    }
  };
}

export async function requestCodexAppServer({ codexCliPath, codexHome, method, params }) {
  const client = new CodexRpcClient(codexCliPath, codexHome, () => {});
  try {
    await client.start();
    return await client.request(method, params);
  } finally {
    client.stop();
  }
}

export async function startCodexDeviceLogin({
  codexCliPath,
  codexHome,
  onComplete = () => {},
  clientFactory = (command, home, emit) => new CodexRpcClient(command, home, emit)
}) {
  let client;
  let stopped = false;
  const stop = () => {
    if (stopped) return;
    stopped = true;
    client?.stop();
  };
  const emit = (event, data) => {
    if (event !== "account.login.completed") return;
    onComplete(data);
    stop();
  };
  client = clientFactory(codexCliPath, codexHome, emit);
  try {
    await client.start();
    const result = await client.request("account/login/start", { type: "chatgptDeviceCode" });
    return {
      loginId: result.loginId,
      verificationUrl: result.verificationUrl,
      userCode: result.userCode,
      stop
    };
  } catch (error) {
    stop();
    throw error;
  }
}

export async function startCodexThreadWatcher({
  codexCliPath,
  codexHome,
  threadId,
  onThread,
  onEvent = () => {},
  onError = () => {},
  intervalMs = 1000,
  clientFactory = (command, home, emit) => new CodexRpcClient(command, home, emit)
}) {
  const client = clientFactory(codexCliPath, codexHome, onEvent);
  await client.start();
  let stopped = false;
  let timer;

  async function poll() {
    try {
      const result = await client.request("thread/read", { threadId, includeTurns: true });
      if (result.thread) onThread(result.thread);
    } catch (error) {
      if (!stopped) onError(error);
    } finally {
      if (!stopped) timer = setTimeout(poll, intervalMs);
    }
  }

  void poll();
  return {
    stop() {
      if (stopped) return;
      stopped = true;
      clearTimeout(timer);
      client.stop();
    }
  };
}

export function getThreadRequest(threadId, cwd) {
  return threadId
    ? { method: "thread/resume", params: { threadId, approvalPolicy: "on-request" } }
    : { method: "thread/start", params: compactObject({ approvalPolicy: "on-request", cwd }) };
}

export function buildTurnStartParams({ threadId, cwd, text, options = {}, attachments = [], skills = [] }) {
  const params = compactObject({
    threadId,
    cwd,
    model: cleanString(options.model),
    effort: cleanString(options.effort),
    serviceTier: cleanString(options.serviceTier),
    permissions: permissionProfile(options.permissionProfile),
    approvalPolicy: approvalPolicy(options.approvalPolicy),
    personality: personality(options.personality),
    sandboxPolicy: permissionProfile(options.permissionProfile) ? undefined : sandboxPolicy(options.sandbox, cwd),
    input: turnInput({ text, attachments, skills, planMode: Boolean(options.planMode) })
  });
  if (!params.approvalPolicy) params.approvalPolicy = "on-request";
  return params;
}

export function publicCapabilitySummary({ models = {}, permissions = {}, plugins = {}, skills = {} }) {
  return {
    models: (models.data ?? []).filter((model) => model.id !== "codex-auto-review").map(publicModel),
    permissions: (permissions.data ?? []).map(publicPermissionProfile),
    plugins: (plugins.marketplaces ?? []).map(publicPluginMarketplace),
    skills: (skills.data ?? []).flatMap((entry) => (entry.skills ?? []).map(publicSkill))
  };
}

export function reviewDecision(decision) {
  if (decision === "approved") return "accept";
  if (decision === "approved_for_session") return "acceptForSession";
  if (decision === "rejected") return "decline";
  throw new Error("invalid_approval_decision");
}

export function approvalResponse(approval, decision) {
  if (approval?.kind !== "permissions") {
    if (decision === "approved_always") {
      const amendment = execpolicyAmendment(approval?.proposedExecpolicyAmendment);
      if (approval?.kind !== "command" || !amendment) throw new Error("approval_scope_unavailable");
      return {
        decision: {
          acceptWithExecpolicyAmendment: { execpolicy_amendment: amendment }
        }
      };
    }
    return { decision: reviewDecision(decision) };
  }

  if (!["approved", "approved_for_session", "rejected"].includes(decision)) {
    throw new Error("approval_scope_unavailable");
  }
  const permissions = decision !== "rejected"
    ? Object.fromEntries(Object.entries(approval.permissions ?? {}).filter(([, value]) => value != null))
    : {};
  return { permissions, scope: decision === "approved_for_session" ? "session" : "turn" };
}

export function approvalFromServerRequest(message) {
  const params = message.params ?? {};
  if (message.method === "item/commandExecution/requestApproval") {
    return {
      runtimeRequestId: message.id,
      ...(params.threadId ? { threadId: params.threadId } : {}),
      ...(params.turnId ? { turnId: params.turnId } : {}),
      kind: "command",
      itemId: params.itemId,
      title: "Command approval required",
      reason: params.reason ?? "Codex requests permission to run this command.",
      ...(params.reason == null ? { reasonKey: "approval.reason.command" } : {}),
      commandPreview: params.command ?? "Command details unavailable",
      ...(params.command == null ? { commandPreviewKey: "approval.preview.commandUnavailable" } : {}),
      ...(execpolicyAmendment(params.proposedExecpolicyAmendment)
        ? { proposedExecpolicyAmendment: params.proposedExecpolicyAmendment }
        : {}),
      cwd: params.cwd ?? ""
    };
  }
  if (message.method === "item/fileChange/requestApproval") {
    return {
      runtimeRequestId: message.id,
      ...(params.threadId ? { threadId: params.threadId } : {}),
      ...(params.turnId ? { turnId: params.turnId } : {}),
      kind: "file_change",
      itemId: params.itemId,
      title: "File change approval required",
      reason: params.reason ?? "Codex requests permission to write outside the workspace.",
      ...(params.reason == null ? { reasonKey: "approval.reason.fileChange" } : {}),
      commandPreview: params.grantRoot ?? "File change",
      ...(params.grantRoot == null ? { commandPreviewKey: "approval.preview.fileChange" } : {}),
      cwd: params.grantRoot ?? ""
    };
  }
  if (message.method === "item/permissions/requestApproval") {
    const preview = permissionPreview(params.permissions);
    return {
      runtimeRequestId: message.id,
      ...(params.threadId ? { threadId: params.threadId } : {}),
      ...(params.turnId ? { turnId: params.turnId } : {}),
      kind: "permissions",
      itemId: params.itemId,
      title: "Additional permission required",
      reason: params.reason ?? "Codex requests additional filesystem or network access.",
      ...(params.reason == null ? { reasonKey: "approval.reason.permissions" } : {}),
      commandPreview: preview.text,
      commandPreviewKey: preview.key,
      commandPreviewValues: preview.values,
      cwd: params.cwd ?? "",
      permissions: params.permissions ?? {}
    };
  }
  return null;
}

export function runtimeEventFromNotification(method, params = {}) {
  if (method === "account/login/completed") {
    return {
      event: "account.login.completed",
      data: {
        loginId: params.loginId ?? null,
        success: Boolean(params.success),
        ...(params.error ? { error: "login_failed" } : {})
      }
    };
  }

  if (method === "thread/tokenUsage/updated") {
    return { event: "thread.token_usage", data: { threadId: params.threadId, tokenUsage: params.tokenUsage } };
  }
  if (method === "turn/started") {
    return {
      event: "turn.started",
      data: {
        ...(params.threadId ? { threadId: params.threadId } : {}),
        turnId: params.turn?.id,
        status: runtimeStatus(params.turn?.status),
        startedAt: secondsToMilliseconds(params.turn?.startedAt)
      }
    };
  }

  if (method === "turn/completed") {
    const turn = params.turn ?? {};
    return {
      event: "turn.completed",
      data: compactObject({
        threadId: params.threadId,
        turnId: turn.id,
        status: runtimeStatus(turn.status),
        startedAt: secondsToMilliseconds(turn.startedAt),
        completedAt: secondsToMilliseconds(turn.completedAt),
        durationMs: turn.durationMs,
        error: turn.error?.message
      })
    };
  }

  if (method === "item/reasoning/summaryTextDelta") {
    return {
      event: "reasoning.delta",
      data: compactObject({ threadId: params.threadId, turnId: params.turnId, itemId: params.itemId, text: params.delta ?? "" })
    };
  }

  if (method === "turn/plan/updated") {
    return {
      event: "plan.updated",
      data: compactObject({ threadId: params.threadId, turnId: params.turnId, plan: params.plan ?? [] })
    };
  }

  if (method === "item/started" || method === "item/completed") {
    const completed = method === "item/completed";
    const activity = projectPublicActivityItem(params.item);
    return {
      event: completed ? "activity.completed" : "activity.started",
      data: compactObject({
        threadId: params.threadId,
        turnId: params.turnId,
        itemId: params.item?.id,
        ...activity,
        status: completed ? activity.status : "running",
        startedAt: params.startedAtMs,
        completedAt: params.completedAtMs,
        changes: params.item?.type === "fileChange" ? params.item.changes : undefined
      })
    };
  }

  if (["item/commandExecution/outputDelta", "item/fileChange/outputDelta"].includes(method)) {
    return {
      event: "activity.updated",
      data: compactObject({
        threadId: params.threadId,
        turnId: params.turnId,
        itemId: params.itemId,
        text: params.delta ?? ""
      })
    };
  }

  return null;
}

class CodexRpcClient {
  constructor(command, codexHome, emit) {
    this.command = command;
    this.codexHome = codexHome;
    this.emit = emit;
    this.nextId = 1;
    this.pending = new Map();
    this.buffer = "";
  }

  async start() {
    this.child = spawn(this.command, ["app-server", "--stdio"], {
      env: { ...process.env, CODEX_HOME: this.codexHome },
      stdio: ["pipe", "pipe", "pipe"],
      shell: needsShell(this.command),
      windowsHide: true
    });

    this.child.stdout.on("data", (chunk) => this.onStdout(chunk));
    this.child.stderr.on("data", (chunk) => this.onStderr(chunk));
    this.child.on("close", (code) => {
      this.rejectAll(new Error(`codex app-server closed with ${code}`));
      this.emit("process.exited", { code });
    });
    this.child.on("error", (error) => {
      this.rejectAll(error);
      this.emit("error", { message: error.message });
    });

    await this.request("initialize", {
      clientInfo: {
        name: "codex_remote",
        title: "Code Remote Manager",
        version: "0.1.0"
      },
      capabilities: { experimentalApi: true }
    });
    this.notify("initialized", {});
  }

  request(method, params) {
    const id = this.nextId++;
    const message = { id, method, params };
    this.child.stdin.write(`${JSON.stringify(message)}\n`);

    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => {
        this.pending.delete(id);
        reject(new Error(`${method} timeout`));
      }, 30000);
      this.pending.set(id, { resolve, reject, timer });
    });
  }

  notify(method, params) {
    this.child.stdin.write(`${JSON.stringify({ method, params })}\n`);
  }

  respond(id, result) {
    this.child.stdin.write(`${JSON.stringify({ id, result })}\n`);
  }

  stop() {
    this.child?.kill();
  }

  onStdout(chunk) {
    this.buffer += chunk.toString("utf8");
    const lines = this.buffer.split(/\r?\n/);
    this.buffer = lines.pop() ?? "";

    for (const line of lines) {
      if (!line.trim()) continue;
      let message;
      try {
        message = JSON.parse(line);
      } catch (error) {
        this.emit("error", { message: `invalid codex json: ${error.message}` });
        continue;
      }
      this.onMessage(message);
    }
  }

  onStderr(chunk) {
    const text = this.redact(chunk.toString("utf8").trim());
    if (text) {
      this.emit("process.stderr", { text });
    }
  }

  onMessage(message) {
    if (message.id && this.pending.has(message.id)) {
      const pending = this.pending.get(message.id);
      this.pending.delete(message.id);
      clearTimeout(pending.timer);
      if (message.error) {
        pending.reject(new Error(this.redact(message.error.message ?? "codex_error")));
      } else {
        pending.resolve(message.result ?? {});
      }
      return;
    }

    if (message.id != null && message.method) {
      const approval = approvalFromServerRequest(message);
      if (approval) {
        this.emit("approval.requested", {
          ...approval,
          reason: this.redact(approval.reason),
          commandPreview: this.redact(approval.commandPreview),
          cwd: this.redact(approval.cwd)
        });
        return;
      }
    }

    if (message.method) {
      this.forwardNotification(message.method, message.params ?? {});
    }
  }

  forwardNotification(method, params) {
    if (method === "item/agentMessage/delta") {
      this.emit("session.output", {
        ...(params.threadId ? { threadId: params.threadId } : {}),
        turnId: params.turnId,
        itemId: params.itemId,
        text: this.redact(params.delta ?? params.text ?? ""),
        kind: "assistant_delta"
      });
      return;
    }

    const translated = runtimeEventFromNotification(method, params);
    if (translated) {
      const data = this.redactData(translated.data);
      if (method === "item/completed" && ["imageGeneration", "dynamicToolCall"].includes(params.item?.type)) {
        Object.defineProperty(data, INTERNAL_GENERATED_IMAGE_ITEM, { value: params.item });
      }
      this.emit(translated.event, data);
      return;
    }

    this.emit("codex.event", {
      method,
      params: summarizeParams(params)
    });
  }

  rejectAll(error) {
    for (const pending of this.pending.values()) {
      clearTimeout(pending.timer);
      pending.reject(error);
    }
    this.pending.clear();
  }

  redact(value) {
    return redactCodexHome(sanitizePublicText(value), this.codexHome);
  }

  redactData(value) {
    if (typeof value === "string") return this.redact(value);
    if (Array.isArray(value)) return value.map((item) => this.redactData(item));
    if (!value || typeof value !== "object") return value;
    return Object.fromEntries(
      Object.entries(value).map(([key, item]) => [key, this.redactData(item)])
    );
  }
}

export function redactCodexHome(value, codexHome) {
  const escapedHome = JSON.stringify(codexHome).slice(1, -1);
  return value
    .replace(new RegExp(escapeRegExp(escapedHome), "gi"), "[CODEX_HOME]")
    .replace(new RegExp(escapeRegExp(codexHome), "gi"), "[CODEX_HOME]");
}

function summarizeParams(params) {
  if (!params || typeof params !== "object") return params;
  const summary = {};
  for (const [key, value] of Object.entries(params)) {
    if (typeof value === "string") {
      summary[key] = redact(value);
    } else if (["number", "boolean"].includes(typeof value) || value == null) {
      summary[key] = value;
    } else if (value?.id) {
      summary[key] = { id: value.id };
    } else {
      summary[key] = Array.isArray(value) ? `[${value.length} items]` : "{...}";
    }
  }
  return summary;
}

function redact(value) {
  return sanitizePublicText(value);
}

export function createCodexRuntimePool(config, {
  clientFactory = (command, home, callback) => new CodexRpcClient(command, home, callback),
  titleGenerationTimeoutMs = 45000,
  turnStartReconcileAttempts = 3,
  turnStartReconcileDelayMs = 250
} = {}) {
  const states = new Map();

  function stateFor(accountId, codexHome) {
    const key = accountId ?? codexHome;
    if (!states.has(key)) states.set(key, {
      codexHome,
      client: null,
      starting: null,
      handles: new Set(),
      handlesByThread: new Map(),
      handlesByTurn: new Map(),
      handlesByItem: new Map(),
      detachedThreadIds: new Set()
    });
    return states.get(key);
  }

  function routeEvent(state, event, data = {}) {
    if (["process.exited", "error"].includes(event)) {
      for (const handle of state.handles) handle.activeTurnId = undefined;
      for (const handle of state.handles) handle.emit(event, data);
      if (event === "process.exited") {
        state.handles.clear();
        state.handlesByThread.clear();
        state.handlesByTurn.clear();
        state.handlesByItem.clear();
        state.detachedThreadIds.clear();
      }
      return;
    }
    if (event === "process.stderr") {
      for (const handle of state.handles) handle.emit(event, data);
      return;
    }
    const owners = new Set([
      state.handlesByThread.get(data.threadId),
      state.handlesByTurn.get(data.turnId),
      state.handlesByItem.get(data.itemId)
    ].filter(Boolean));
    const hasRoutingIdentifier = Boolean(data.threadId || data.turnId || data.itemId);
    const handle = owners.size === 1
      ? [...owners][0]
      : owners.size === 0 && state.handles.size === 1 && !hasRoutingIdentifier
        ? [...state.handles][0]
        : null;
    if (!handle) {
      if (event === "approval.requested" && data.runtimeRequestId != null) {
        state.client?.respond(data.runtimeRequestId, approvalResponse(data, "rejected"));
      }
      return;
    }
    if (data.threadId) state.handlesByThread.set(data.threadId, handle);
    if (data.turnId) state.handlesByTurn.set(data.turnId, handle);
    if (data.itemId) state.handlesByItem.set(data.itemId, handle);
    if (event === "turn.started" && data.turnId) handle.activeTurnId = data.turnId;
    if (event === "turn.completed" && handle.activeTurnId === data.turnId) handle.activeTurnId = undefined;
    handle.emit(event, data);
  }

  function removeHandle(state, handle) {
    state.handles.delete(handle);
    if (handle.threadId) state.detachedThreadIds.add(handle.threadId);
    if (handle.threadId && state.handlesByThread.get(handle.threadId) === handle) state.handlesByThread.delete(handle.threadId);
    for (const [turnId, owner] of state.handlesByTurn) if (owner === handle) state.handlesByTurn.delete(turnId);
    for (const [itemId, owner] of state.handlesByItem) if (owner === handle) state.handlesByItem.delete(itemId);
  }

  async function reconcileTimedOutTurn(client, threadId) {
    const attempts = Math.max(2, turnStartReconcileAttempts);
    for (let index = 0; index < attempts; index += 1) {
      let snapshot;
      try {
        snapshot = await client.request("thread/read", { threadId, includeTurns: true });
      } catch {
        return { confirmed: false };
      }
      const activeTurnId = activeTurnIdFromThread(snapshot.thread);
      if (activeTurnId) return { confirmed: true, activeTurnId };
      if (index < attempts - 1) {
        await new Promise((resolve) => setTimeout(resolve, Math.max(1, turnStartReconcileDelayMs)));
      }
    }
    return { confirmed: true, activeTurnId: undefined };
  }

  async function ensureClient(state) {
    if (state.client) return state.client;
    if (!state.starting) {
      state.starting = (async () => {
        const client = clientFactory(config.codexCliPath, state.codexHome, (event, data) => {
          routeEvent(state, event, data);
          if (event === "process.exited") state.client = null;
        });
        try {
          await client.start();
        } catch (error) {
          client.stop();
          throw error;
        }
        state.client = client;
        return client;
      })().finally(() => { state.starting = null; });
    }
    return state.starting;
  }

  async function startRuntime({ accountId, codexHome, cwd, emit, threadId }) {
    const state = stateFor(accountId, codexHome);
    const client = await ensureClient(state);
    const handle = { emit, threadId, activeTurnId: undefined, pendingStart: undefined };
    state.handles.add(handle);
    const request = getThreadRequest(threadId, cwd);
    let result;
    try {
      result = await client.request(request.method, request.params);
    } catch (error) {
      removeHandle(state, handle);
      throw error;
    }
    handle.threadId = result.thread?.id;
    handle.activeTurnId = activeTurnIdFromThread(result.thread);
    if (handle.threadId) {
      state.detachedThreadIds.delete(handle.threadId);
      state.handlesByThread.set(handle.threadId, handle);
    }
    if (handle.activeTurnId) state.handlesByTurn.set(handle.activeTurnId, handle);

    return {
      get threadId() { return handle.threadId; },
      async sendMessage(message) {
        const payload = typeof message === "string" ? { text: message } : message;
        const attempt = createPendingTurnStart();
        handle.pendingStart = attempt;
        let turn;
        try {
          turn = await client.request("turn/start", buildTurnStartParams({
            threadId: handle.threadId,
            cwd,
            text: payload.text,
            options: payload.options,
            attachments: payload.attachments,
            skills: payload.skills
          }));
        } catch (startError) {
          attempt.startSettled = true;
          if (!handle.activeTurnId && /turn\/start timeout/i.test(startError?.message ?? "")) {
            const reconciliation = await reconcileTimedOutTurn(client, handle.threadId);
            if (!reconciliation.confirmed) {
              attempt.outcomeUnknown = true;
              throw unknownTurnStartOutcomeError();
            }
            handle.activeTurnId = reconciliation.activeTurnId;
            if (handle.activeTurnId) state.handlesByTurn.set(handle.activeTurnId, handle);
          }
          if (handle.activeTurnId) {
            try {
              await client.request("turn/interrupt", { threadId: handle.threadId, turnId: handle.activeTurnId });
              state.handlesByTurn.delete(handle.activeTurnId);
              handle.activeTurnId = undefined;
              attempt.interrupted = true;
            } catch {
              const error = unconfirmedTurnStopError();
              settleStartCancellation(attempt, error);
              throw error;
            }
          }
          if (attempt.canceled) {
            settleStartCancellation(attempt);
            if (handle.pendingStart === attempt) handle.pendingStart = undefined;
            throw new Error("turn_start_canceled");
          }
          if (handle.pendingStart === attempt) handle.pendingStart = undefined;
          throw startError;
        }
        attempt.startSettled = true;
        try {
          handle.activeTurnId = turn.turn?.id ?? handle.activeTurnId;
          if (handle.activeTurnId) state.handlesByTurn.set(handle.activeTurnId, handle);
          if (attempt.canceled) {
            if (handle.activeTurnId && !attempt.interrupted) {
              try {
                await client.request("turn/interrupt", { threadId: handle.threadId, turnId: handle.activeTurnId });
              } catch {
                const error = unconfirmedTurnStopError();
                settleStartCancellation(attempt, error);
                throw error;
              }
            }
            if (handle.activeTurnId) state.handlesByTurn.delete(handle.activeTurnId);
            handle.activeTurnId = undefined;
            attempt.interrupted = true;
            settleStartCancellation(attempt);
            throw new Error("turn_start_canceled");
          }
          return handle.activeTurnId;
        } finally {
          if (handle.pendingStart === attempt) handle.pendingStart = undefined;
        }
      },
      async cancelPendingStart() {
        const attempt = handle.pendingStart;
        if (!attempt && !handle.activeTurnId) return false;
        if (attempt) attempt.canceled = true;
        if (attempt?.outcomeUnknown && !handle.activeTurnId) {
          const reconciliation = await reconcileTimedOutTurn(client, handle.threadId);
          if (!reconciliation.confirmed) throw unconfirmedTurnStopError();
          handle.activeTurnId = reconciliation.activeTurnId;
          if (handle.activeTurnId) state.handlesByTurn.set(handle.activeTurnId, handle);
          if (!handle.activeTurnId) {
            settleStartCancellation(attempt);
            if (handle.pendingStart === attempt) handle.pendingStart = undefined;
            return true;
          }
        }
        if (handle.activeTurnId) {
          try {
            await client.request("turn/interrupt", { threadId: handle.threadId, turnId: handle.activeTurnId });
          } catch {
            const error = unconfirmedTurnStopError();
            settleStartCancellation(attempt, error);
            throw error;
          }
          if (attempt) attempt.interrupted = true;
          state.handlesByTurn.delete(handle.activeTurnId);
          handle.activeTurnId = undefined;
          settleStartCancellation(attempt);
        }
        if (attempt?.startSettled && attempt.cancellationSettled && handle.pendingStart === attempt) handle.pendingStart = undefined;
        if (attempt && !attempt.cancellationSettled) await attempt.cancellation;
        return true;
      },
      async steerMessage(message) {
        if (!handle.activeTurnId) throw new Error("active_turn_unavailable");
        const result = await client.request("turn/steer", {
          threadId: handle.threadId,
          expectedTurnId: handle.activeTurnId,
          input: [{ type: "text", text: typeof message === "string" ? message : message.text }]
        });
        return result.turnId;
      },
      resolveApproval(requestId, decision, approval) {
        client.respond(requestId, approvalResponse(approval, decision));
      },
      async interrupt() {
        if (!handle.activeTurnId) throw new Error("active_turn_unavailable");
        return client.request("turn/interrupt", { threadId: handle.threadId, turnId: handle.activeTurnId });
      },
      stop() {
        removeHandle(state, handle);
      }
    };
  }

  async function requestHistory({ accountId, codexHome, method, params }) {
    if (!new Set(["thread/list", "thread/read"]).has(method)) {
      throw new Error("unsupported_history_method");
    }
    const client = await ensureClient(stateFor(accountId, codexHome));
    return client.request(method, params);
  }

  async function generateConversationTitle({ accountId, codexHome, threadId, conversation }) {
    const state = stateFor(accountId, codexHome);
    const client = await ensureClient(state);
    let output = "";
    let finishTurn;
    let completionTimer;
    let turnFinished = false;
    const completed = new Promise((resolve) => {
      finishTurn = (data) => {
        if (turnFinished) return;
        turnFinished = true;
        clearTimeout(completionTimer);
        resolve(data);
      };
      completionTimer = setTimeout(
        () => finishTurn({ status: "timeout" }),
        Math.max(1, titleGenerationTimeoutMs)
      );
      completionTimer.unref?.();
    });
    const handle = {
      threadId: undefined,
      activeTurnId: undefined,
      emit(event, data) {
        if (event === "session.output" && data.kind === "assistant_delta") output += data.text ?? "";
        if (event === "turn.completed") finishTurn(data);
        if (event === "error" || event === "process.exited") finishTurn({ status: "failed" });
      }
    };
    state.handles.add(handle);
    try {
      const started = await client.request("thread/start", {
        approvalPolicy: "never",
        sandbox: "read-only",
        cwd: tmpdir(),
        personality: "none",
        ephemeral: true
      });
      handle.threadId = started.thread?.id;
      if (!handle.threadId) throw new Error("title_thread_unavailable");
      state.handlesByThread.set(handle.threadId, handle);

      const turn = await client.request("turn/start", {
        threadId: handle.threadId,
        approvalPolicy: "never",
        input: [{ type: "text", text: conversationTitlePrompt(conversation) }],
        outputSchema: conversationTitleOutputSchema()
      });
      handle.activeTurnId = turn.turn?.id;
      if (handle.activeTurnId) state.handlesByTurn.set(handle.activeTurnId, handle);
      const completion = await completed;
      if (completion.status === "timeout") throw new Error("title_generation_timeout");
      if (completion.status !== "completed") throw new Error("title_generation_failed");

      const title = generatedConversationTitle(output);
      const source = await client.request("thread/read", { threadId, includeTurns: false });
      if (source.thread?.name?.trim()) return undefined;
      await client.request("thread/name/set", { threadId, name: title });
      return title;
    } finally {
      clearTimeout(completionTimer);
      removeHandle(state, handle);
    }
  }

  return {
    startRuntime,
    requestHistory,
    generateConversationTitle,
    async prewarm() {
      await Promise.allSettled((config.accounts ?? [])
        .filter((account) => !account.historyOnly)
        .map((account) => ensureClient(stateFor(account.id, account.codexHome))));
    },
    stop() {
      for (const state of states.values()) state.client?.stop();
      states.clear();
    }
  };
}

function conversationTitlePrompt(conversation) {
  const visibleConversation = String(conversation ?? "").slice(0, 8000);
  return [
    "Create one concise title for this coding conversation.",
    "Use the user's language. Describe the main task, not the outcome.",
    "Keep the title under 40 characters and do not include quotes or ending punctuation.",
    "Return only the JSON object required by the output schema.",
    "",
    visibleConversation
  ].join("\n");
}

function conversationTitleOutputSchema() {
  return {
    type: "object",
    additionalProperties: false,
    required: ["title"],
    properties: { title: { type: "string", minLength: 1, maxLength: 40 } }
  };
}

function generatedConversationTitle(output) {
  let value;
  try {
    value = JSON.parse(output)?.title;
  } catch {
    throw new Error("title_generation_invalid");
  }
  const title = sanitizePublicText(value).replace(/[\r\n]+/g, " ").trim();
  if (!title) throw new Error("title_generation_invalid");
  return [...title].slice(0, 40).join("");
}

function escapeRegExp(value) {
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

function needsShell(command) {
  return command.toLowerCase().endsWith(".cmd") || command.toLowerCase().endsWith(".bat");
}

function permissionPreview(permissions) {
  const network = Boolean(permissions?.network?.enabled);
  const entries = permissions?.fileSystem?.entries ?? [];
  if (network && entries.length) return {
    text: `Network access, ${entries.length} filesystem permission(s)`,
    key: "approval.preview.permissionsNetworkAndFilesystem",
    values: { count: entries.length }
  };
  if (network) return { text: "Network access", key: "approval.preview.permissionsNetwork" };
  if (entries.length) return {
    text: `${entries.length} filesystem permission(s)`,
    key: "approval.preview.permissionsFilesystem",
    values: { count: entries.length }
  };
  return { text: "Additional permissions", key: "approval.preview.permissionsAdditional" };
}

function secondsToMilliseconds(value) {
  return Number.isFinite(value) ? value * 1000 : undefined;
}

function runtimeStatus(status) {
  if (status === "inProgress") return "running";
  return status ?? "completed";
}

function compactObject(value) {
  return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && item !== null));
}

function activeTurnIdFromThread(thread) {
  const activeTurn = (thread?.turns ?? [])
    .findLast((turn) => ["active", "inProgress", "running"].includes(turn.status?.type ?? turn.status));
  return activeTurn?.id;
}

function turnInput({ text, attachments, skills, planMode }) {
  const input = [{ type: "text", text }];
  for (const attachment of attachments ?? []) {
    if (attachment?.kind === "image" && typeof attachment.path === "string") {
      input.push({ type: "localImage", path: attachment.path });
    }
  }
  for (const skill of skills ?? []) {
    if (typeof skill?.name === "string" && typeof skill?.path === "string") {
      input.push({ type: "skill", name: skill.name, path: skill.path });
    }
  }
  if (planMode) {
    input.push({
      type: "text",
      text: "Use Plan mode for this turn. Create a plan first and wait for confirmation before implementation."
    });
  }
  return input;
}

function approvalPolicy(value) {
  return ["untrusted", "on-failure", "on-request", "never"].includes(value) ? value : undefined;
}

function execpolicyAmendment(value) {
  return Array.isArray(value) && value.length > 0 && value.every((token) => typeof token === "string" && token.length > 0)
    ? value
    : undefined;
}

function permissionProfile(value) {
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
}

function personality(value) {
  return ["none", "friendly", "pragmatic"].includes(value) ? value : undefined;
}

function sandboxPolicy(value, cwd) {
  if (value === "read-only") return { type: "readOnly", networkAccess: false };
  if (value === "workspace-write") {
    return { type: "workspaceWrite", networkAccess: false, writableRoots: cwd ? [cwd] : [] };
  }
  if (value === "danger-full-access") return { type: "dangerFullAccess" };
  return undefined;
}

function cleanString(value) {
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
}

function publicModel(model) {
  return {
    id: model.id,
    model: model.model,
    displayName: model.displayName,
    description: model.description,
    isDefault: Boolean(model.isDefault),
    supportsImages: (model.inputModalities ?? []).includes("image"),
    defaultReasoningEffort: model.defaultReasoningEffort,
    reasoningEfforts: (model.supportedReasoningEfforts ?? []).map((item) => ({
      id: item.reasoningEffort,
      description: item.description
    })),
    serviceTiers: (model.serviceTiers ?? []).map((item) => ({
      id: item.id,
      name: item.name,
      description: item.description
    })),
    supportsPersonality: Boolean(model.supportsPersonality)
  };
}

function publicPermissionProfile(profile) {
  return {
    id: profile.id,
    allowed: Boolean(profile.allowed),
    description: profile.description ?? ""
  };
}

function publicPluginMarketplace(marketplace) {
  return {
    name: marketplace.name,
    displayName: marketplace.interface?.displayName ?? marketplace.name,
    plugins: (marketplace.plugins ?? []).map((plugin) => ({
      id: plugin.id,
      name: plugin.name,
      displayName: plugin.interface?.displayName ?? plugin.name,
      description: plugin.interface?.shortDescription ?? plugin.interface?.longDescription ?? "",
      enabled: Boolean(plugin.enabled),
      installed: Boolean(plugin.installed)
    }))
  };
}

function publicSkill(skill) {
  return {
    name: skill.name,
    displayName: skill.interface?.displayName ?? skill.name,
    description: skill.interface?.shortDescription ?? skill.shortDescription ?? skill.description ?? "",
    enabled: Boolean(skill.enabled),
    scope: skill.scope
  };
}
