import type { Message, ToolCall } from "#cli/core/model";
import type {
  SessionQueueErrorCode,
  SessionQueueItem,
  SessionQueueStatus,
} from "#cli/core/session-meta";
import type { DimSdkStore } from "#cli/core/store";
import type { ComputedRef, Ref } from "vue";
import type { SessionMetaMerged, UpdateSessionMeta } from "./session-meta";
import { debugLog } from "#cli/core/debug-log";
import { resolveModelQuirkOverrides } from "#cli/core/model-quirks";
import { resolveProviderRuntimeEnv } from "#cli/core/provider-runtime-env";
import { SESSION_QUEUE_LIMIT } from "#cli/core/session-meta";
import { id } from "#cli/core/store/ids";
import { normalizeText } from "#cli/core/utils";
import { t } from "#cli/i18n";
import { getBuiltinProvider, getDefaultModelId, isCustomProviderId } from "#cli/compat/providers";
import { sliceByCells, textCellWidth } from "@simon_he/vue-tui";
import { computed, onBeforeUnmount, ref, watch } from "vue";

export type SessionQueueDisplayItem = SessionQueueItem &
  Readonly<{
    livePending?: boolean;
  }>;

type UiConfirmPrompt = Readonly<{
  id: string;
  expectedAnswers: readonly string[];
  expiresAtMs: number;
  onConfirm: () => void;
}>;

type PausedQueueDraft = Readonly<{
  text: string;
}>;

type AssistantMessage = Extract<Message, { role: "assistant" }>;
type AssistantStatusPart = Extract<
  AssistantMessage["parts"][number],
  {
    type: "status";
  }
>;

function asRecord(value: unknown): Record<string, unknown> | null {
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
  return value as Record<string, unknown>;
}

export function useChatQueue(
  opts: Readonly<{
    store: DimSdkStore;
    anyDialogOpen: ComputedRef<boolean>;
    showSessions: Ref<boolean>;
    showCopyToast: (text: string, durationMs?: number | null) => void;
    queueConfirmVisible: Ref<boolean>;
    queueConfirmReason: Ref<SessionQueueErrorCode | null>;
    pausedQueueDecisionVisible?: Ref<boolean>;

    input: Ref<string>;
    focusFiles: Ref<string[]>;
    history: ComputedRef<readonly string[]>;
    historyIndex: Ref<number>;
    setStickToBottom: () => void;
    sendPausedQueueDraft?: (draft: PausedQueueDraft) => Promise<void>;

    currentSessionId: ComputedRef<string>;
    sessionMeta: ComputedRef<SessionMetaMerged | null>;
    queueItems: ComputedRef<readonly SessionQueueItem[]>;
    queueStatus: ComputedRef<SessionQueueStatus>;
    queueErrorCode: ComputedRef<SessionQueueErrorCode | null>;
    updateSessionMeta: UpdateSessionMeta;
  }>,
): Readonly<{
  displayQueueItems: ComputedRef<readonly SessionQueueDisplayItem[]>;
  enqueueMessage: (raw: string, opts?: { preservePaused?: boolean }) => boolean;
  clearQueue: (opts?: { toast?: boolean }) => void;
  summarizeQueueText: (text: string, maxCells?: number) => string;
  deleteQueueItem: (id: string) => void;
  upgradeQueueItemToSteer: (id: string) => Promise<void>;
  confirmQueueResume: () => void;
  pauseQueueResume: () => void;
  capturePausedQueueInput: (raw: string) => boolean;
  closePausedQueueDecision: () => void;
  continuePausedQueueDecision: () => void;
  appendPausedQueueDecision: () => void;
  replacePausedQueueDecision: () => Promise<void>;
  pausedQueueDecisionSummary: ComputedRef<string>;
  tryHandlePreSend: (text: string) => boolean;
  resumeQueue: (opts?: { force?: boolean }) => void;

  queueUserPaused: Ref<boolean>;
  queuePauseTipPending: Ref<boolean>;
  shouldShowQueuePausedTip: () => boolean;
  showQueuePausedTip: () => void;
  onDialogsClosed: () => void;

  requestInterrupt: () => void;
  hasQueueUndo: ComputedRef<boolean>;
  onQueueUndo: () => void;
}> {
  const {
    store,
    anyDialogOpen,
    showSessions,
    showCopyToast,
    queueConfirmVisible,
    queueConfirmReason,
    pausedQueueDecisionVisible: pausedQueueDecisionVisibleProp,
    input,
    focusFiles,
    history,
    historyIndex,
    setStickToBottom,
    sendPausedQueueDraft,
    currentSessionId,
    sessionMeta,
    queueItems,
    queueStatus,
    queueErrorCode,
    updateSessionMeta,
  } = opts;

  const QUEUE_UNDO_WINDOW_MS = 4000;
  const QUEUE_PAUSED_TIP_MS = 3200;
  const pausedQueueDecisionVisible = pausedQueueDecisionVisibleProp ?? ref(false);
  const queueUndo = ref<{
    entries: Array<{ item: SessionQueueItem; index: number }>;
    expiresAt: number;
  } | null>(null);
  const pendingLiveSteersBySession = ref<
    Record<string, Array<SessionQueueDisplayItem & { livePending: true; liveAcceptedAt: number }>>
  >({});
  const pendingPausedQueueDraft = ref<PausedQueueDraft | null>(null);
  const processedSteerInjectedMessageKeysBySession = new Map<string, Set<string>>();

  const normalizeConfirmInput = (text: string): string => {
    return String(text ?? "")
      .trim()
      .toLowerCase()
      .replace(/[。．.!！?？]+$/g, "");
  };

  const CONFIRM_YES_ANSWERS = [
    "是",
    "要",
    "好",
    "好的",
    "可以",
    "行",
    "y",
    "yes",
    "ok",
    "okay",
  ].map(normalizeConfirmInput);

  const pendingConfirm = ref<UiConfirmPrompt | null>(null);

  const TEST_PROMPT_COOLDOWN_MS = 60_000;
  const TEST_PROMPT_TTL_MS = 20_000;
  const TEST_PROMPT_TOAST_MS = 6000;
  let lastTestPromptAtMs = 0;

  const TOOL_MUTATION_NAMES = new Set(["write", "edit"]);
  const TEST_PATH_RE = /(?:^|[\\/])(?:__tests__|tests?|spec)(?:[\\/]|$)|[._-](?:test|spec)[._-]/i;
  const CODE_EXT_RE =
    /\.(?:ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|kt|cs|cpp|c|h|hpp|rb|php|swift|scala|lua|svelte|vue)$/i;

  const normalizeFilePath = (raw: string): string =>
    String(raw ?? "")
      .trim()
      .replace(/\\/g, "/");
  const isTestPath = (raw: string): boolean => {
    const path = normalizeFilePath(raw);
    if (!path) return false;
    if (TEST_PATH_RE.test(path)) return true;
    return /\.(?:test|spec)\.[^/\\]+$/i.test(path);
  };
  const isLikelyCodePath = (raw: string): boolean => {
    const path = normalizeFilePath(raw);
    if (!path) return false;
    return CODE_EXT_RE.test(path);
  };
  const extractFilePathFromCall = (call: ToolCall): string => {
    const args = (call?.arguments ?? {}) as Record<string, unknown>;
    return String(args.file_path ?? args.path ?? args.filePath ?? args.filename ?? "").trim();
  };
  const collectChangedPathsSince = (startIndex: number): string[] => {
    const out: string[] = [];
    const messages = store.state.messages.slice(Math.max(0, startIndex));
    for (const msg of messages) {
      if (msg.role !== "assistant") continue;
      for (const part of msg.parts) {
        if (part.type !== "tool_call") continue;
        const toolName = String(part.call.name ?? "")
          .trim()
          .toLowerCase();
        if (!TOOL_MUTATION_NAMES.has(toolName)) continue;
        const filePath = extractFilePathFromCall(part.call);
        if (filePath) out.push(filePath);
      }
    }
    return out;
  };
  const analyzeRoundChanges = (startIndex: number) => {
    const paths = collectChangedPathsSince(startIndex);
    const unique = Array.from(new Set(paths.map((p) => normalizeFilePath(p)).filter(Boolean)));
    if (!unique.length) return { hasCodeChange: false, hasTestChange: false };
    const hasTestChange = unique.some(isTestPath);
    const hasCodeChange = unique.some(isLikelyCodePath);
    return { hasCodeChange, hasTestChange };
  };

  const runAutoPrompt = (content: string) => {
    if (!content) return;
    setStickToBottom();
    store.clearRevertedMessages();
    store.send(content);
    focusFiles.value = [];
    historyIndex.value = history.value.length;
    input.value = "";
  };

  const getRuntimeEnv = (): Record<string, string | undefined> => {
    return store.state.context.env ?? {};
  };

  const syncProcessEnv = (key: string, value: string | undefined) => {
    if (typeof process === "undefined" || !process?.env) return;
    if (value == null || value === "") delete process.env[key];
    else process.env[key] = value;
  };

  const syncRuntimeEnvVar = (key: string, value: string | undefined) => {
    store.setEnvVar(key, value);
    syncProcessEnv(key, value);
  };

  const syncRuntimeEnvForProvider = (providerId: string) => {
    const resolved = resolveProviderRuntimeEnv({
      providerId,
      providerState: store.state.provider,
      config: store.state.config,
      env: getRuntimeEnv(),
      legacyCustomProviderName: "Custom Provider",
    });
    if (!resolved) return;

    for (const [key, value] of Object.entries(resolved.updates)) {
      syncRuntimeEnvVar(key, value);
    }
  };

  const syncRuntimeEnvForModel = (providerId: string, modelId: string) => {
    const nextModel = String(modelId ?? "").trim();
    if (!nextModel) return;
    store.state.config.model = nextModel;
    syncRuntimeEnvVar("DIMCODE_MODEL", nextModel);

    const provider = store.state.provider;
    provider.activeModelByProvider = {
      ...provider.activeModelByProvider,
      [providerId]: nextModel,
    };

    const quirks = resolveModelQuirkOverrides(nextModel);
    if (typeof quirks?.temperature === "number") {
      store.state.config.temperature = quirks.temperature;
      syncRuntimeEnvVar("DIMCODE_TEMPERATURE", String(quirks.temperature));
    }
    if (typeof quirks?.interleavedThinking === "boolean") {
      const flag = quirks.interleavedThinking ? "1" : "0";
      store.state.config.interleaved_thinking = quirks.interleavedThinking;
      syncRuntimeEnvVar("DIMCODE_INTERLEAVED_THINKING", flag);
    }
  };

  const maybePromptAddTests = (roundStartIndex: number) => {
    if (pendingConfirm.value) return;
    const now = Date.now();
    if (now - lastTestPromptAtMs < TEST_PROMPT_COOLDOWN_MS) return;
    const { hasCodeChange, hasTestChange } = analyzeRoundChanges(roundStartIndex);
    if (!hasCodeChange || hasTestChange) return;
    pendingConfirm.value = {
      id: "add-tests",
      expectedAnswers: CONFIRM_YES_ANSWERS,
      expiresAtMs: now + TEST_PROMPT_TTL_MS,
      onConfirm: () => runAutoPrompt(t("chatPage.confirm.addTests")),
    };
    showCopyToast(t("chatPage.tips.addTests"), TEST_PROMPT_TOAST_MS);
    lastTestPromptAtMs = now;
  };

  const queueAutoSending = ref(false);
  const queueResumeBlocked = ref(false);
  const queueUserPaused = ref(false);
  const queuePauseTipSessionId = ref<string | null>(null);
  const queuePauseTipPending = ref(false);
  let queueResumePending = false;
  const interruptRequested = ref(false);
  let queueHeartbeatTimer: ReturnType<typeof setInterval> | null = null;
  const QUEUE_HEARTBEAT_MS = 20_000;
  const QUEUE_STALE_MS = 120_000;

  const summarizeQueueText = (text: string, maxCells = 44) => {
    const firstLine =
      String(text ?? "")
        .trim()
        .split("\n")[0] ?? "";
    const squashed = firstLine.replace(/\s+/g, " ").trim();
    const max = Math.max(12, Math.floor(Number(maxCells) || 44));
    if (textCellWidth(squashed) <= max) return squashed;
    const slice = sliceByCells(squashed, Math.max(0, max - 1));
    return `${slice}…`;
  };

  const getPendingLiveSteers = (
    sessionId = currentSessionId.value,
  ): Array<SessionQueueDisplayItem & { livePending: true; liveAcceptedAt: number }> => {
    const sid = String(sessionId ?? "").trim();
    if (!sid) return [];
    return pendingLiveSteersBySession.value[sid] ?? [];
  };

  const setPendingLiveSteers = (
    sessionId: string,
    items: Array<SessionQueueDisplayItem & { livePending: true; liveAcceptedAt: number }>,
  ) => {
    const sid = String(sessionId ?? "").trim();
    if (!sid) return;
    const next = { ...pendingLiveSteersBySession.value };
    if (items.length > 0) next[sid] = items;
    else delete next[sid];
    pendingLiveSteersBySession.value = next;
  };

  const isQueueItemLivePending = (itemId: string, sessionId = currentSessionId.value): boolean => {
    const sid = String(sessionId ?? "").trim();
    const id = String(itemId ?? "").trim();
    if (!sid || !id) return false;
    return getPendingLiveSteers(sid).some((item) => item.id === id);
  };

  const pushPendingLiveSteer = (item: SessionQueueItem) => {
    const sid = currentSessionId.value;
    if (!sid) return;
    const liveAcceptedAt = Date.now();
    const next = getPendingLiveSteers(sid).filter((existing) => existing.id !== item.id);
    next.push({
      ...item,
      livePending: true,
      liveAcceptedAt,
    });
    next.sort((a, b) => {
      if (a.liveAcceptedAt !== b.liveAcceptedAt) return a.liveAcceptedAt - b.liveAcceptedAt;
      if (a.createdAt !== b.createdAt) return a.createdAt - b.createdAt;
      return a.id.localeCompare(b.id);
    });
    setPendingLiveSteers(sid, next);
  };

  const consumePendingLiveSteers = (sessionId: string, count: number) => {
    const sid = String(sessionId ?? "").trim();
    const removeCount = Math.max(0, Math.floor(Number(count) || 0));
    if (!sid || removeCount <= 0) return;
    const current = getPendingLiveSteers(sid);
    if (!current.length) return;
    const removed = Math.min(removeCount, current.length);
    const consumedIds = current.slice(0, removed).map((item) => item.id);
    setPendingLiveSteers(sid, current.slice(removed));
    if (sid === currentSessionId.value && consumedIds.length > 0) {
      removeQueueEntries(consumedIds, {
        toast: false,
        undo: false,
      });
    }
    debugLog("queue_steer_live_reconciled", {
      sessionId: sid,
      removed,
      remaining: Math.max(0, current.length - removed),
    });
  };

  const restorePendingLiveSteersToQueue = (sessionId = currentSessionId.value): number => {
    const sid = String(sessionId ?? "").trim();
    if (!sid) return 0;

    reconcilePendingLiveSteers(sid);
    const pending = getPendingLiveSteers(sid);
    if (!pending.length) return 0;

    setPendingLiveSteers(sid, []);
    debugLog("queue_steer_live_restored", {
      sessionId: sid,
      restored: pending.length,
      queueLength: sid === currentSessionId.value ? queueItems.value.length : 0,
    });
    return pending.length;
  };

  function reconcilePendingLiveSteers(sessionId: string): void {
    const sid = String(sessionId ?? "").trim();
    if (!sid) return;
    let processed = processedSteerInjectedMessageKeysBySession.get(sid);
    if (!processed) {
      processed = new Set<string>();
      processedSteerInjectedMessageKeysBySession.set(sid, processed);
    }
    let injectedCount = 0;
    for (let i = 0; i < store.state.messages.length; i++) {
      const msg = store.state.messages[i];
      if (!msg || msg.role !== "user") continue;
      const metadata = asRecord(msg.metadata);
      if (!metadata || String(metadata.originalEventType ?? "") !== "steer_injected") continue;
      const key = String(msg.id ?? `idx:${i}`);
      if (processed.has(key)) continue;
      processed.add(key);
      const rawCount = Number(metadata.steerCount ?? 0);
      injectedCount += Number.isFinite(rawCount) && rawCount > 0 ? Math.floor(rawCount) : 1;
    }
    if (injectedCount > 0) consumePendingLiveSteers(sid, injectedCount);
  }

  const displayQueueItems = computed<SessionQueueDisplayItem[]>(() => {
    const pending = getPendingLiveSteers(currentSessionId.value);
    if (!pending.length) return [...queueItems.value];
    const pendingById = new Map(pending.map((item) => [item.id, item] as const));
    const queueIds = new Set(queueItems.value.map((item) => item.id));
    const missingPending = pending.filter((item) => !queueIds.has(item.id));
    const decoratedQueue = queueItems.value.map((item) => {
      const live = pendingById.get(item.id);
      return live ? { ...item, livePending: true } : item;
    });
    return [...missingPending, ...decoratedQueue];
  });

  const snapshotQueueConfig = () => {
    const providerId = String(store.state.provider.activeId ?? "").trim() || undefined;
    const model = String(store.state.config.model ?? "").trim() || undefined;
    const temperature = Number.isFinite(Number(store.state.config.temperature))
      ? Number(store.state.config.temperature)
      : undefined;
    const interleavedThinking = Boolean(store.state.config.interleaved_thinking);
    const approvalsMode = store.state.approval.mode ?? "normal";
    return {
      model,
      temperature,
      interleavedThinking,
      approvalsMode,
      providerId,
    };
  };

  const getQueueResumeToken = (): string => String(t("chatQueue.resumeToken") ?? "").trim();
  const getQueueResumePlainToken = (): string =>
    String(t("chatQueue.resumePlainToken") ?? "").trim();
  const getQueueResumeLegacyToken = (): string =>
    String(t("chatQueue.resumeLegacyToken") ?? "").trim();
  const normalizeQueueControlInput = (text: string): string =>
    String(text ?? "")
      .trim()
      .toLowerCase();
  const getQueueResumeInputs = (): string[] => {
    return Array.from(
      new Set(
        [
          getQueueResumeToken(),
          getQueueResumePlainToken(),
          getQueueResumeLegacyToken(),
          "resume",
          "resume queue",
        ]
          .map(normalizeQueueControlInput)
          .filter(Boolean),
      ),
    );
  };
  const isQueueResumeInput = (text: string): boolean => {
    const normalized = normalizeQueueControlInput(text);
    if (!normalized) return false;
    return getQueueResumeInputs().includes(normalized);
  };
  const pausedQueueDecisionSummary = computed(() =>
    summarizeQueueText(pendingPausedQueueDraft.value?.text ?? "", 48),
  );

  const hasPendingApproval = () =>
    Boolean(store.state.approval.pendingAutoRun) ||
    Boolean(store.state.approval.active && store.state.approval.active.status === "pending") ||
    store.state.approval.queue.length > 0;

  const clearQueue = (opts?: { toast?: boolean }) => {
    const count = queueItems.value.length;
    if (!count) return;
    updateSessionMeta({
      queue: [],
      queueLength: 0,
      status: store.state.runningAgentRound ? "running" : "idle",
      lastErrorCode: undefined,
    });
    queueUndo.value = null;
    queueConfirmReason.value = null;
    queueUserPaused.value = false;
    pausedQueueDecisionVisible.value = false;
    pendingPausedQueueDraft.value = null;
    if (opts?.toast !== false) showCopyToast(t("chatQueue.toast.cleared", { count }));
    debugLog("queue_clear", {
      sessionId: currentSessionId.value,
      queueLength: 0,
    });
  };

  const isQueueAwaitingResume = (): boolean => {
    if (queueItems.value.length === 0) return false;
    if (queueAutoSending.value) return false;
    if (store.state.runningAgentRound) return false;
    return true;
  };

  const shouldShowQueuePausedTip = (): boolean => {
    const sid = currentSessionId.value;
    if (!sid) return false;
    if (!isQueueAwaitingResume()) return false;
    return queuePauseTipSessionId.value !== sid;
  };

  const showQueuePausedTip = () => {
    if (!shouldShowQueuePausedTip()) return;
    showCopyToast(
      t("chatQueue.toast.pausedTip", {
        resume: getQueueResumeToken(),
        resumeText: getQueueResumePlainToken() || "resume",
      }),
      QUEUE_PAUSED_TIP_MS,
    );
    queuePauseTipSessionId.value = currentSessionId.value || null;
    queuePauseTipPending.value = false;
  };

  const enqueueMessage = (raw: string, opts?: { preservePaused?: boolean }): boolean => {
    const content = normalizeText(raw);
    if (!content) return false;
    const current = queueItems.value.slice();
    if (current.length >= SESSION_QUEUE_LIMIT) {
      showCopyToast(t("chatQueue.toast.full"));
      debugLog("queue_drop_full", {
        sessionId: currentSessionId.value,
        queueLength: current.length,
      });
      return false;
    }
    const item: SessionQueueItem = {
      id: id("q"),
      text: content,
      createdAt: Date.now(),
      kind: "user",
      config: snapshotQueueConfig(),
    };
    const next = [...current, item];
    updateSessionMeta({
      queue: next,
      queueLength: next.length,
      status: opts?.preservePaused ? "paused_confirm" : "running",
      lastErrorCode: undefined,
    });
    showCopyToast(t("chatQueue.toast.enqueued", { count: next.length }));
    debugLog("queue_enqueue", {
      sessionId: currentSessionId.value,
      queueLength: next.length,
    });
    return true;
  };

  const peekQueuedMessage = (): SessionQueueItem | null => {
    const current = queueItems.value;
    return current.length > 0 ? current[0]! : null;
  };

  const dequeueMessageById = (itemId: string): SessionQueueItem | null => {
    const current = queueItems.value.slice();
    if (!current.length) return null;
    const idx = current.findIndex((item) => item.id === itemId);
    if (idx < 0) return null;
    const [item] = current.splice(idx, 1);
    if (!item) return null;
    updateSessionMeta({
      queue: current,
      queueLength: current.length,
    });
    debugLog("queue_dequeue", {
      sessionId: currentSessionId.value,
      queueLength: current.length,
    });
    return item;
  };

  const restoreQueueMessage = (
    item: SessionQueueItem,
    opts?: { lastErrorCode?: SessionQueueErrorCode },
  ) => {
    const current = queueItems.value.slice();
    if (current.some((entry) => entry.id === item.id)) return;
    const next = [item, ...current];
    updateSessionMeta({
      queue: next,
      queueLength: next.length,
      status: "paused_confirm",
      ...(Object.prototype.hasOwnProperty.call(opts ?? {}, "lastErrorCode")
        ? { lastErrorCode: opts?.lastErrorCode }
        : {}),
    });
    debugLog("queue_restore", {
      sessionId: currentSessionId.value,
      queueLength: next.length,
      itemId: item.id,
    });
  };

  function removeQueueEntries(
    ids: string[],
    opts?: Readonly<{ toast?: boolean; undo?: boolean; toastText?: string }>,
  ): void {
    const current = queueItems.value.slice();
    if (!current.length) return;
    const toDelete = new Set(ids);
    const removed: Array<{ item: SessionQueueItem; index: number }> = [];
    const next = current.filter((item, idx) => {
      if (toDelete.has(item.id)) {
        removed.push({ item, index: idx });
        return false;
      }
      return true;
    });
    if (!removed.length) return;
    updateSessionMeta({
      queue: next,
      queueLength: next.length,
    });
    if (!store.state.runningAgentRound && next.length === 0)
      updateSessionMeta({ status: "idle", lastErrorCode: undefined }, { bumpUpdatedAt: false });
    if (opts?.undo !== false)
      queueUndo.value = {
        entries: removed,
        expiresAt: Date.now() + QUEUE_UNDO_WINDOW_MS,
      };
    if (opts?.toast !== false)
      showCopyToast(opts?.toastText ?? t("chatQueue.toast.deleted", { count: removed.length }));
    debugLog("queue_remove", {
      sessionId: currentSessionId.value,
      queueLength: next.length,
    });
  }

  const deleteQueueEntries = (ids: string[]) => {
    removeQueueEntries(ids, { toast: true, undo: true });
  };

  const undoQueueDelete = () => {
    const undo = queueUndo.value;
    if (!undo) return false;
    if (Date.now() > undo.expiresAt) {
      queueUndo.value = null;
      return false;
    }
    const current = queueItems.value.slice();
    const restored = undo.entries.slice().sort((a, b) => a.index - b.index);
    const next = current.slice();
    for (const entry of restored) {
      const idx = Math.max(0, Math.min(entry.index, next.length));
      next.splice(idx, 0, entry.item);
    }
    updateSessionMeta({
      queue: next,
      queueLength: next.length,
    });
    if (!store.state.runningAgentRound)
      updateSessionMeta({ status: "idle", lastErrorCode: undefined }, { bumpUpdatedAt: false });
    queueUndo.value = null;
    showCopyToast(t("chatQueue.toast.undo"));
    debugLog("queue_undo", {
      sessionId: currentSessionId.value,
      queueLength: next.length,
    });
    return true;
  };

  const deleteQueueItem = (id: string) => deleteQueueEntries([id]);

  const upgradeQueueItemToSteer = async (id: string): Promise<void> => {
    const current = queueItems.value.slice();
    const target = current.find((item) => item.id === id);
    if (!target || target.kind === "steer") return;

    const steeredAt = Date.now();
    const promotedTarget: SessionQueueItem = {
      ...target,
      kind: "steer" as const,
      steeredAt,
    };
    const next = current.map((item) => {
      if (item.id !== id) return item;
      return promotedTarget;
    });

    updateSessionMeta({
      queue: next,
      queueLength: next.length,
      lastErrorCode: undefined,
    });

    debugLog("queue_steer_promoted", {
      sessionId: currentSessionId.value,
      queueLength: next.length,
      itemId: id,
    });

    if (!store.state.runningAgentRound) {
      showCopyToast(t("chatQueue.toast.steerPromoted"));
      return;
    }

    let sent = false;
    try {
      sent = await store.steer(target.text);
    } catch (error) {
      debugLog(
        "queue_steer_live_error",
        error instanceof Error
          ? {
              sessionId: currentSessionId.value,
              itemId: id,
              message: error.message,
              stack: error.stack,
            }
          : {
              sessionId: currentSessionId.value,
              itemId: id,
              error: String(error),
            },
      );
    }

    if (sent) {
      pushPendingLiveSteer(promotedTarget);
      showCopyToast(t("chatQueue.toast.steerSent"));
      return;
    }

    showCopyToast(t("chatQueue.toast.steerQueued"));
  };

  const applyQueueConfig = (item: SessionQueueItem): boolean => {
    const cfg = item.config;
    if (!cfg) return true;
    if (cfg.model) {
      const provider = store.state.provider;
      const activeProviderId = String(provider?.activeId ?? "").trim();
      const desiredProviderId = cfg.providerId ?? activeProviderId;
      if (provider && desiredProviderId && provider.activeId !== desiredProviderId) {
        provider.activeId = desiredProviderId;
      }
      if (desiredProviderId) syncRuntimeEnvForProvider(desiredProviderId);
      const modelId = cfg.model;
      if (modelId && provider) {
        const available = (() => {
          if (desiredProviderId === "custom") {
            return provider.custom.models.map((m) => String(m.id ?? "").trim()).filter(Boolean);
          }
          if (isCustomProviderId(desiredProviderId)) {
            const match = provider.customProviders.find(
              (p) => String(p.id ?? "").trim() === desiredProviderId,
            );
            return (match?.models ?? []).map((m) => String(m.id ?? "").trim()).filter(Boolean);
          }
          const builtin = getBuiltinProvider(desiredProviderId || activeProviderId);
          const customModels = builtin?.customModels
            ? provider?.connections?.[desiredProviderId || activeProviderId]?.models
            : undefined;
          const models = Array.isArray(customModels) ? customModels : builtin?.models;
          const modelIds = models?.map((m) => String(m?.id ?? "").trim()).filter(Boolean) ?? [];
          if (modelIds.length > 0) return modelIds;
          return desiredProviderId ? [getDefaultModelId(builtin)].filter(Boolean) : [];
        })();
        if (available.length > 0 && !available.includes(modelId)) return false;
        if (desiredProviderId) syncRuntimeEnvForModel(desiredProviderId, modelId);
        else store.state.config.model = modelId;
      }
    }
    if (typeof cfg.temperature === "number") store.state.config.temperature = cfg.temperature;
    if (typeof cfg.interleavedThinking === "boolean")
      store.state.config.interleaved_thinking = cfg.interleavedThinking;
    const quirks = resolveModelQuirkOverrides(store.state.config.model);
    if (typeof quirks?.temperature === "number")
      store.state.config.temperature = quirks.temperature;
    if (typeof quirks?.interleavedThinking === "boolean")
      store.state.config.interleaved_thinking = quirks.interleavedThinking;
    if (cfg.approvalsMode) store.state.approval.mode = cfg.approvalsMode;
    return true;
  };

  const inferQueueErrorCode = (): SessionQueueErrorCode | null => {
    if (queueErrorCode.value) return queueErrorCode.value;
    const statusText = (() => {
      for (let i = store.state.messages.length - 1; i >= 0; i--) {
        const msg = store.state.messages[i];
        if (!msg || msg.role !== "assistant") continue;
        const part = msg.parts.find((item): item is AssistantStatusPart => item.type === "status");
        if (part?.text) return String(part.text);
      }
      return "";
    })();
    if (statusText.toLowerCase().includes(t("chatStatus.interrupted").toLowerCase()))
      return "canceled_by_user";
    if (statusText.toLowerCase().includes(t("chatStatus.error").toLowerCase())) return "unknown";
    return null;
  };

  const tryResumeQueue = async (opts?: { force?: boolean; ignoreSavedConfig?: boolean }) => {
    if (queueAutoSending.value) {
      queueResumePending = true;
      return;
    }
    if (queueUserPaused.value && !opts?.force) return;
    if (store.state.runningAgentRound) return;
    if (store.state.paused || hasPendingApproval()) return;
    if (!queueItems.value.length) return;
    if (isQueueItemLivePending(queueItems.value[0]?.id ?? "")) return;
    if (anyDialogOpen.value && !opts?.force) {
      queueResumeBlocked.value = true;
      return;
    }
    queueAutoSending.value = true;
    queueResumeBlocked.value = false;
    const next = peekQueuedMessage();
    if (!next) {
      queueAutoSending.value = false;
      return;
    }
    let dispatchItem = opts?.ignoreSavedConfig
      ? {
          ...next,
          config: snapshotQueueConfig(),
        }
      : next;
    if (opts?.ignoreSavedConfig) {
      debugLog("queue_resume_ignore_saved_config", {
        itemId: next.id,
        queueLength: queueItems.value.length,
        sessionId: currentSessionId.value,
      });
    }
    const ok = opts?.ignoreSavedConfig ? true : applyQueueConfig(next);
    if (!ok) {
      const provider = store.state.provider;
      debugLog("queue_resume_config_unavailable_fallback_current", {
        activeProviderId: String(provider?.activeId ?? "").trim() || null,
        model: String(store.state.config.model ?? "").trim() || null,
        nextItemConfig: next.config ?? null,
        nextItemId: next.id,
        nextItemText: next.text,
        queueIds: queueItems.value.map((item) => item.id),
        queueLength: queueItems.value.length,
        sessionId: currentSessionId.value,
      });
      dispatchItem = {
        ...next,
        config: snapshotQueueConfig(),
      };
    }
    const committed = dequeueMessageById(next.id);
    if (!committed) {
      queueAutoSending.value = false;
      return;
    }
    try {
      store.clearRevertedMessages();
      if (dispatchItem.kind === "steer") await store.steer(dispatchItem.text);
      else await store.send(dispatchItem.text);
    } catch {
      restoreQueueMessage(dispatchItem, { lastErrorCode: "unknown" });
      queueConfirmReason.value = "unknown";
      queueConfirmVisible.value = true;
    } finally {
      queueAutoSending.value = false;
      if (queueResumePending) {
        queueResumePending = false;
        queueMicrotask(() => void tryResumeQueue({ force: true }));
      }
    }
  };

  const confirmQueueResume = () => {
    const reason = queueConfirmReason.value;
    queueConfirmVisible.value = false;
    queueConfirmReason.value = null;
    queueUserPaused.value = false;
    updateSessionMeta({ status: "running", lastErrorCode: undefined });
    void tryResumeQueue({
      force: true,
      ignoreSavedConfig: reason === "config_unavailable",
    });
  };

  const pauseQueueResume = () => {
    queueConfirmVisible.value = false;
    updateSessionMeta({ status: "paused_confirm" });
    if (queueItems.value.length > 0) {
      queueUserPaused.value = true;
      showCopyToast(
        t("chatQueue.toast.pausedTip", {
          resume: getQueueResumeToken(),
          resumeText: getQueueResumePlainToken() || "resume",
        }),
        QUEUE_PAUSED_TIP_MS,
      );
      queuePauseTipSessionId.value = currentSessionId.value || null;
      queuePauseTipPending.value = false;
    }
  };

  const closePausedQueueDecision = () => {
    pausedQueueDecisionVisible.value = false;
    pendingPausedQueueDraft.value = null;
  };

  const capturePausedQueueInput = (raw: string): boolean => {
    if (!queueUserPaused.value || queueItems.value.length === 0) return false;
    const text = normalizeText(raw);
    if (!text) return false;
    pendingPausedQueueDraft.value = { text };
    pausedQueueDecisionVisible.value = true;
    return true;
  };

  const continuePausedQueueDecision = () => {
    pausedQueueDecisionVisible.value = false;
    pendingPausedQueueDraft.value = null;
    confirmQueueResume();
  };

  const appendPausedQueueDecision = () => {
    const draft = pendingPausedQueueDraft.value;
    if (!draft) return;
    pausedQueueDecisionVisible.value = false;
    pendingPausedQueueDraft.value = null;
    if (!enqueueMessage(draft.text, { preservePaused: true })) return;
    focusFiles.value = [];
    historyIndex.value = history.value.length;
    input.value = "";
  };

  const replacePausedQueueDecision = async () => {
    const draft = pendingPausedQueueDraft.value;
    if (!draft) return;
    pausedQueueDecisionVisible.value = false;
    pendingPausedQueueDraft.value = null;
    clearQueue({ toast: false });
    await (sendPausedQueueDraft?.(draft) ?? Promise.resolve());
  };

  const resumeQueue = (opts?: { force?: boolean }) => {
    void tryResumeQueue(opts);
  };

  const tryHandlePreSend = (expandedTrimmed: string): boolean => {
    const text = String(expandedTrimmed ?? "").trimEnd();
    if (
      queueUserPaused.value &&
      focusFiles.value.length === 0 &&
      isQueueResumeInput(text) &&
      queueItems.value.length > 0
    ) {
      confirmQueueResume();
      input.value = "";
      focusFiles.value = [];
      historyIndex.value = history.value.length;
      return true;
    }
    if (pendingConfirm.value && focusFiles.value.length === 0) {
      const now = Date.now();
      const pending = pendingConfirm.value;
      if (now > pending.expiresAtMs) {
        pendingConfirm.value = null;
        return false;
      }
      const normalized = normalizeConfirmInput(text);
      if (normalized && pending.expectedAnswers.includes(normalized)) {
        pendingConfirm.value = null;
        if (queueUserPaused.value && queueItems.value.length > 0) clearQueue();
        input.value = "";
        focusFiles.value = [];
        historyIndex.value = history.value.length;
        pending.onConfirm();
        return true;
      }
      pendingConfirm.value = null;
    }
    return false;
  };

  const isQueueStale = () => {
    const lastUpdatedAt = Number(sessionMeta.value?.lastUpdatedAt ?? 0);
    if (!store.state.runningAgentRound) return false;
    if (!lastUpdatedAt) return false;
    return Date.now() - lastUpdatedAt > QUEUE_STALE_MS;
  };

  const startQueueHeartbeat = () => {
    if (queueHeartbeatTimer) return;
    queueHeartbeatTimer = setInterval(() => {
      if (store.state.runningAgentRound) {
        updateSessionMeta({ lastUpdatedAt: Date.now() }, { bumpUpdatedAt: false });
      }
    }, QUEUE_HEARTBEAT_MS);
  };

  const stopQueueHeartbeat = () => {
    if (!queueHeartbeatTimer) return;
    clearInterval(queueHeartbeatTimer);
    queueHeartbeatTimer = null;
  };

  const roundStartIndex = ref(0);
  watch(
    () => store.state.runningAgentRound,
    (running, prev) => {
      if (running && !prev) {
        roundStartIndex.value = store.state.messages.length;
        updateSessionMeta({ status: "running", lastErrorCode: undefined });
        startQueueHeartbeat();
        return;
      }
      if (!running && prev) {
        stopQueueHeartbeat();
        const inferredErr = inferQueueErrorCode();
        const hadInterrupt = interruptRequested.value || inferredErr === "canceled_by_user";
        interruptRequested.value = false;
        const waitingForLoopResume = Boolean(store.state.paused || hasPendingApproval());
        if (waitingForLoopResume && !hadInterrupt) {
          updateSessionMeta({ status: "paused_confirm" });
          return;
        }
        const pendingLiveBeforeRestore = getPendingLiveSteers(currentSessionId.value).length;
        if (pendingLiveBeforeRestore > 0) restorePendingLiveSteersToQueue(currentSessionId.value);
        const queueLen = queueItems.value.length;
        if (waitingForLoopResume) {
          updateSessionMeta({ status: "paused_confirm" });
          return;
        }
        if (queueLen === 0) {
          updateSessionMeta({ status: "idle", lastErrorCode: undefined });
          return;
        }

        if (!anyDialogOpen.value && !pendingConfirm.value)
          maybePromptAddTests(roundStartIndex.value);

        const errCode = hadInterrupt ? "canceled_by_user" : inferredErr;
        if (errCode && errCode !== "canceled_by_user") {
          updateSessionMeta({
            status: "paused_confirm",
            lastErrorCode: errCode,
          });
          queueConfirmReason.value = errCode;
          queueConfirmVisible.value = true;
          debugLog("queue_pause_confirm", {
            sessionId: currentSessionId.value,
            reason: errCode,
          });
          return;
        }

        void tryResumeQueue();
      }
    },
  );

  watch(
    () =>
      [
        currentSessionId.value,
        queueStatus.value,
        queueItems.value.length,
        store.state.runningAgentRound,
        store.state.paused,
      ] as const,
    ([sid]) => {
      if (!sid) {
        queueUserPaused.value = false;
        queuePauseTipPending.value = false;
        return;
      }
      const paused = queueStatus.value === "paused_confirm" && isQueueAwaitingResume();
      queueUserPaused.value = paused;
      if (!paused) {
        if (queuePauseTipSessionId.value === sid) queuePauseTipSessionId.value = null;
        queuePauseTipPending.value = false;
        return;
      }
      if (anyDialogOpen.value) {
        queuePauseTipPending.value = true;
        return;
      }
      showQueuePausedTip();
    },
    { immediate: true },
  );

  watch(queueItems, (items) => {
    if (items.length === 0) {
      if (queueUserPaused.value) queueUserPaused.value = false;
      closePausedQueueDecision();
    }
  });

  watch(
    () => [currentSessionId.value, store.state.messages.length] as const,
    ([sid]) => {
      reconcilePendingLiveSteers(sid);
    },
    { immediate: true },
  );

  watch(showSessions, (open, prev) => {
    if (open) {
      queuePauseTipSessionId.value = null;
      queuePauseTipPending.value = false;
      return;
    }
    if (!prev) return;
    if (anyDialogOpen.value) {
      queuePauseTipPending.value = true;
      return;
    }
    showQueuePausedTip();
  });

  let lastStaleHandledAt = 0;
  const staleTimer = setInterval(() => {
    if (!isQueueStale()) return;
    const now = Date.now();
    if (now - lastStaleHandledAt < 15_000) return;
    lastStaleHandledAt = now;
    const hasQueue = queueItems.value.length > 0;
    updateSessionMeta({
      status: hasQueue ? "paused_confirm" : "idle",
      lastErrorCode: hasQueue ? "timeout" : undefined,
    });
    if (hasQueue) {
      queueConfirmReason.value = "timeout";
      queueConfirmVisible.value = true;
      showCopyToast(t("chatQueue.toast.stale"));
    }
  }, 5000);

  watch(
    currentSessionId,
    () => {
      pendingConfirm.value = null;
      lastTestPromptAtMs = 0;
      queueUndo.value = null;
      queueConfirmVisible.value = false;
      queueConfirmReason.value = null;
      closePausedQueueDecision();
      queueResumeBlocked.value = false;
      queueUserPaused.value = queueStatus.value === "paused_confirm" && isQueueAwaitingResume();
      queuePauseTipSessionId.value = null;
      queuePauseTipPending.value = false;
      interruptRequested.value = false;
    },
    { immediate: true },
  );

  onBeforeUnmount(() => {
    clearInterval(staleTimer);
    stopQueueHeartbeat();
  });

  const onDialogsClosed = () => {
    if (queueResumeBlocked.value) void tryResumeQueue({ force: true });
    if (queuePauseTipPending.value) showQueuePausedTip();
  };

  const requestInterrupt = () => {
    interruptRequested.value = true;
    store.interrupt();
  };

  const hasQueueUndo = computed(() =>
    Boolean(queueUndo.value && Date.now() <= queueUndo.value.expiresAt),
  );
  const onQueueUndo = () => {
    undoQueueDelete();
  };

  return {
    displayQueueItems,
    enqueueMessage,
    clearQueue,
    summarizeQueueText,
    deleteQueueItem,
    upgradeQueueItemToSteer,
    confirmQueueResume,
    pauseQueueResume,
    capturePausedQueueInput,
    closePausedQueueDecision,
    continuePausedQueueDecision,
    appendPausedQueueDecision,
    replacePausedQueueDecision,
    pausedQueueDecisionSummary,
    tryHandlePreSend,
    resumeQueue,
    queueUserPaused,
    queuePauseTipPending,
    shouldShowQueuePausedTip,
    showQueuePausedTip,
    onDialogsClosed,
    requestInterrupt,
    hasQueueUndo,
    onQueueUndo,
  };
}
