import type { ModelMetadata, ReasoningEffort, SkillMetadata } from "../../../../domain/catalog/metadata";
import type { PendingRequestId } from "../../../../domain/pending-requests/model";
import type { RuntimeConfigSnapshot } from "../../../../domain/runtime/config";
import type { RateLimitSnapshot, ThreadTokenUsage } from "../../../../domain/runtime/metrics";
import type { RuntimePermissionProfileSummary } from "../../../../domain/runtime/permissions";
import type { ApprovalsReviewer } from "../../../../domain/runtime/policy";
import type { RuntimeSettingsPatch } from "../../../../domain/runtime/thread-settings";
import type { Diagnostics } from "../../../../domain/server/diagnostics";
import { createServerDiagnostics } from "../../../../domain/server/diagnostics";
import type { ServerInitialization } from "../../../../domain/server/initialization";
import type { ThreadGoal } from "../../../../domain/threads/goal";
import type { Thread } from "../../../../domain/threads/model";
import { type CollaborationModeSelection, type RequestedFastMode, unchangedCollaborationModeIntent } from "../../domain/runtime/intent";
import {
  type ChatRuntimeState,
  clearRequestedApprovalsReviewerRuntimeState,
  clearRequestedFastModeRuntimeState,
  commitAppliedRuntimeSettingsPatchState,
  initialActiveChatRuntimeState,
  initialChatRuntimeState,
  requestApprovalsReviewerRuntimeState,
  requestFastModeRuntimeState,
  requestModelRuntimeState,
  requestPermissionProfileRuntimeState,
  requestReasoningEffortRuntimeState,
  resetModelToConfigRuntimeState,
  resetPermissionProfileToConfigRuntimeState,
  resetReasoningEffortToConfigRuntimeState,
  setSelectedCollaborationModeRuntimeState,
} from "../../domain/runtime/state";
import type { ThreadStreamDialogueItem, ThreadStreamItem } from "../../domain/thread-stream/items";
import type { ComposerSuggestion } from "../composer/suggestions";
import {
  type ChatRequestState,
  initialChatRequestState,
  isRequestAction,
  type RequestAction,
  reduceRequestSlice,
  resolveChatRequest,
} from "../pending-requests/state";
import {
  type ChatTurnState,
  initialChatTurnState,
  type PendingTurnStart,
  STATUS_TURN_RUNNING,
  transitionChatTurnLifecycleState,
} from "../turns/turn-state";
import type {
  ActiveThreadResumedAction,
  ActiveThreadSettingsAppliedAction,
  ClearActiveThreadAction,
  ClearDisconnectedConnectionStateAction,
  ClearLocalTurnAction,
  ConnectionInitializedAction,
  ReplaceConnectionContextAction,
  ThreadListAppliedAction,
  TurnOptimisticStartedAction,
  TurnStartAcknowledgedAction,
  TurnStartFailedAction,
} from "./actions";
import { definedPatch, patchObject } from "./patch";
import type { ChatPendingSubmissionState, PendingSubmissionAction } from "./pending-submission";
import {
  type ChatThreadStreamState,
  initialChatThreadStreamState,
  isThreadStreamAction,
  reduceThreadStreamSlice,
  type ThreadStreamAction,
  threadStreamItems,
  threadStreamStartActiveSegment,
  threadStreamWithActiveTurnItems,
  threadStreamWithItems,
} from "./thread-stream";
import {
  type ChatUiState,
  clearAllRequestDisclosures,
  clearResolvedRequestDisclosures,
  initialUiState,
  isUiAction,
  maybeClearGoalObjectiveExpansion,
  reduceUiSlice,
  type UiAction,
} from "./ui-state";

export type ChatConnectionPhase =
  | { kind: "idle" }
  | { kind: "connecting" }
  | { kind: "connected" }
  | { kind: "failed"; message: string }
  | { kind: "disconnected"; message: string };

interface ChatConnectionState {
  readonly phase: ChatConnectionPhase;
  readonly statusText: string;
  readonly runtimeConfig: RuntimeConfigSnapshot | null;
  readonly initializeResponse: ServerInitialization | null;
  readonly rateLimit: RateLimitSnapshot | null;
  readonly serverDiagnostics: Diagnostics;
  readonly availableModels: readonly ModelMetadata[];
  readonly availableSkills: readonly SkillMetadata[];
  readonly availablePermissionProfiles: readonly RuntimePermissionProfileSummary[];
}

interface ChatThreadListState {
  readonly listedThreads: readonly Thread[];
}

export interface ChatActiveThreadState {
  readonly id: string;
  readonly title?: string | null;
  readonly cwd: string | null;
  readonly goal: ThreadGoal | null;
  readonly tokenUsage: ThreadTokenUsage | null;
  readonly lifetime: ActiveThreadLifetime | null;
  readonly provenance: Thread["provenance"] | null;
}

type ChatPanelThreadState =
  | { readonly kind: "empty" }
  | { readonly kind: "awaiting-resume"; readonly threadId: string; readonly fallbackTitle: string | null }
  | { readonly kind: "active"; readonly thread: ChatActiveThreadState };

type ActiveThreadLifetime =
  | { readonly kind: "persistent" }
  | { readonly kind: "ephemeral"; readonly sourceThreadId: string; readonly sourceThreadTitle: string | null };

interface ChatComposerState {
  readonly draft: string;
  readonly suggestSelected: number;
  readonly suggestions: readonly ComposerSuggestion[];
  readonly suggestionsDismissedSignature: string | null;
}

interface ChatStateShape {
  connection: ChatConnectionState;
  threadList: ChatThreadListState;
  panelThread: ChatPanelThreadState;
  runtime: ChatRuntimeState;
  turn: ChatTurnState;
  threadStream: ChatThreadStreamState;
  pendingSubmission: ChatPendingSubmissionState | null;
  requests: ChatRequestState;
  composer: ChatComposerState;
  ui: ChatUiState;
}

export type ChatState = DeepReadonly<ChatStateShape>;

type ConnectionAction =
  | { type: "connection/status-set"; statusText: string; phase?: ChatConnectionPhase }
  | ConnectionInitializedAction
  | {
      type: "connection/metadata-applied";
      runtimeConfig?: RuntimeConfigSnapshot | null;
      availableModels?: readonly ModelMetadata[];
      availableSkills?: readonly SkillMetadata[];
      availablePermissionProfiles?: readonly RuntimePermissionProfileSummary[];
      rateLimit?: RateLimitSnapshot | null;
      serverDiagnostics?: Diagnostics;
    };

type ThreadListAction = ThreadListAppliedAction;

type ActiveThreadAction =
  | { type: "active-thread/cwd-set"; cwd: string | null }
  | { type: "active-thread/token-usage-set"; tokenUsage: ThreadTokenUsage | null };

type RuntimeAction =
  | { type: "runtime/model-requested"; model: string }
  | { type: "runtime/model-reset-to-config" }
  | { type: "runtime/reasoning-effort-requested"; effort: ReasoningEffort }
  | { type: "runtime/reasoning-effort-reset-to-config" }
  | { type: "runtime/permission-profile-requested"; permissionProfile: string }
  | { type: "runtime/permission-profile-reset-to-config" }
  | { type: "runtime/fast-mode-requested"; fastMode: RequestedFastMode }
  | { type: "runtime/fast-mode-request-cleared" }
  | { type: "runtime/approvals-reviewer-requested"; approvalsReviewer: ApprovalsReviewer }
  | { type: "runtime/approvals-reviewer-request-cleared" }
  | { type: "runtime/requested-collaboration-mode-set"; collaborationMode: CollaborationModeSelection }
  | { type: "runtime/pending-thread-settings-committed"; update: RuntimeSettingsPatch };

interface TurnStartedAction {
  type: "turn/started";
  threadId: string;
  turnId: string;
  items?: readonly ThreadStreamItem[];
}

interface TurnCompletedAction {
  type: "turn/completed";
  turnId: string;
  status: string;
  items: readonly ThreadStreamItem[];
}

type TurnAction =
  | TurnStartedAction
  | TurnCompletedAction
  | ClearLocalTurnAction
  | TurnOptimisticStartedAction
  | TurnStartAcknowledgedAction
  | TurnStartFailedAction;

type ComposerAction =
  | {
      type: "composer/draft-set";
      draft: string;
      clearSuggestions?: boolean;
      resetDismissedSignature?: boolean;
    }
  | {
      type: "composer/input-set";
      draft: string;
      suggestions: readonly ComposerSuggestion[];
      selected?: number;
      dismissedSignature?: string | null;
    }
  | {
      type: "composer/suggestions-set";
      suggestions: readonly ComposerSuggestion[];
      selected?: number;
      dismissedSignature?: string | null;
    };

export type ChatAction = ChatTransitionAction | ChatSliceAction;

interface RequestResolvedAction {
  type: "request/resolved";
  requestId: PendingRequestId;
  resultItem?: ThreadStreamItem;
}

interface PendingStartHookUpsertedAction {
  type: "turn/pending-start-hook-upserted";
  item: ThreadStreamItem;
  pendingTurnStart: PendingTurnStart | null;
}

type ChatTransitionAction =
  | ClearDisconnectedConnectionStateAction
  | ReplaceConnectionContextAction
  | ClearActiveThreadAction
  | ActiveThreadResumedAction
  | ActiveThreadSettingsAppliedAction
  | { type: "active-thread/goal-set"; goal: ThreadGoal | null }
  | { type: "panel/restored-thread-applied"; threadId: string; fallbackTitle: string | null }
  | { type: "panel/restored-thread-renamed"; threadId: string; name: string | null }
  | { type: "panel/view-state-cleared" }
  | TurnAction
  | RequestResolvedAction
  | PendingStartHookUpsertedAction
  | PendingSubmissionAction;

type ChatSliceAction =
  | ConnectionAction
  | ThreadListAction
  | ActiveThreadAction
  | RuntimeAction
  | RequestAction
  | ThreadStreamAction
  | ComposerAction
  | UiAction;

export function createChatState(): ChatState {
  return {
    connection: initialConnectionState(),
    threadList: initialThreadListState(),
    panelThread: initialPanelThreadState(),
    runtime: initialChatRuntimeState(),
    turn: initialTurnState(),
    threadStream: initialThreadStreamState(),
    pendingSubmission: null,
    requests: initialRequestState(),
    composer: initialComposerState(),
    ui: initialUiState(),
  };
}

export function panelThreadId(state: ChatState): string | null {
  return state.panelThread.kind === "awaiting-resume" ? state.panelThread.threadId : activeThreadId(state);
}

export function activeThreadId(state: ChatState): string | null {
  return activeThreadState(state)?.id ?? null;
}

export function activeThreadState(state: ChatState): DeepReadonly<ChatActiveThreadState> | null {
  return state.panelThread.kind === "active" ? state.panelThread.thread : null;
}

export function awaitingResumeThreadState(state: ChatState): Extract<ChatState["panelThread"], { kind: "awaiting-resume" }> | null {
  return state.panelThread.kind === "awaiting-resume" ? state.panelThread : null;
}

export function chatReducer(state: ChatState, action: ChatAction): ChatState {
  switch (action.type) {
    case "connection/scoped-cleared":
    case "connection/context-replaced":
    case "active-thread/cleared":
    case "active-thread/resumed":
    case "active-thread/settings-applied":
    case "active-thread/goal-set":
    case "panel/restored-thread-applied":
    case "panel/restored-thread-renamed":
    case "panel/view-state-cleared":
    case "turn/started":
    case "turn/completed":
    case "turn/scoped-cleared":
    case "turn/optimistic-started":
    case "turn/start-acknowledged":
    case "turn/start-failed":
    case "turn/pending-start-hook-upserted":
    case "request/resolved":
    case "web-submission/pending":
    case "web-submission/committed":
    case "web-submission/cancelled":
    case "web-submission/failed":
    case "web-submission/steer-adopted":
      return reduceChatTransition(state, action);
    default:
      return reduceChatSlices(state, action);
  }
}

function reduceChatTransition(state: ChatState, action: ChatTransitionAction): ChatState {
  switch (action.type) {
    case "connection/scoped-cleared":
      return clearConnectionScopedState(state);
    case "connection/context-replaced":
      return clearConnectionContextState(state);
    case "active-thread/cleared":
      return clearThreadScopedState(state);
    case "active-thread/resumed":
      return reduceActiveThreadResumedTransition(state, action);
    case "active-thread/settings-applied":
      return reduceActiveThreadSettingsAppliedTransition(state, action);
    case "active-thread/goal-set":
      return reduceActiveThreadGoalSetTransition(state, action.goal);
    case "panel/restored-thread-applied":
      return reduceRestoredThreadAppliedTransition(state, action.threadId, action.fallbackTitle);
    case "panel/restored-thread-renamed":
      return reduceRestoredThreadRenamedTransition(state, action.threadId, action.name);
    case "panel/view-state-cleared":
      return reduceViewStateClearedTransition(state);
    case "turn/started":
      return reduceTurnStartedTransition(state, action);
    case "turn/completed":
      return reduceTurnCompletedTransition(state, action);
    case "turn/scoped-cleared":
      return clearTurnScopedState(state);
    case "turn/optimistic-started":
      return reduceTurnOptimisticStartedTransition(state, action);
    case "turn/start-acknowledged":
      return reduceTurnStartAcknowledgedTransition(state, action);
    case "turn/start-failed":
      return reduceTurnStartFailedTransition(state, action);
    case "turn/pending-start-hook-upserted":
      return reducePendingStartHookUpsertedTransition(state, action);
    case "request/resolved":
      return reduceRequestResolvedTransition(state, action);
    case "web-submission/pending":
      return patchChatState(state, { pendingSubmission: action.submission });
    case "web-submission/committed":
      return state.pendingSubmission?.id === action.submissionId && state.pendingSubmission.phase === "cancellable"
        ? patchChatState(state, { pendingSubmission: { ...state.pendingSubmission, phase: "committed" } })
        : state;
    case "web-submission/cancelled":
      return state.pendingSubmission?.id === action.submissionId && state.pendingSubmission.phase === "cancellable"
        ? patchChatState(state, { pendingSubmission: null })
        : state;
    case "web-submission/failed":
      return state.pendingSubmission?.id === action.submissionId && state.pendingSubmission.phase === "committed"
        ? patchChatState(state, { pendingSubmission: null })
        : state;
    case "web-submission/steer-adopted":
      if (state.pendingSubmission?.id !== action.submissionId || state.pendingSubmission.phase !== "committed") return state;
      return patchChatState(state, {
        pendingSubmission: null,
        threadStream: adoptPendingSteerItem(state.threadStream, action.item),
      });
  }
}

function adoptPendingSteerItem(state: ChatThreadStreamState, item: ThreadStreamDialogueItem): ChatThreadStreamState {
  if (!item.clientId) {
    return reduceThreadStreamSlice(state, { type: "thread-stream/item-added", item });
  }
  const matchedIndex = state.stableItems.findIndex(
    (current) => current.kind === "dialogue" && current.role === "user" && current.clientId === item.clientId,
  );
  if (matchedIndex === -1) return reduceThreadStreamSlice(state, { type: "thread-stream/item-added", item });
  const current = state.stableItems[matchedIndex];
  if (current?.kind !== "dialogue") return state;
  const stableItems = [...state.stableItems];
  stableItems[matchedIndex] = {
    ...current,
    ...(item.contextAttachments ? { contextAttachments: item.contextAttachments } : {}),
    ...(item.mentionedFiles ? { mentionedFiles: item.mentionedFiles } : {}),
    ...(item.referencedThread ? { referencedThread: item.referencedThread } : {}),
  };
  return { ...state, stableItems };
}

function reduceActiveThreadResumedTransition(state: ChatState, action: ActiveThreadResumedAction): ChatState {
  const runtimeBase = action.preserveRequestedRuntimeSettings ? state.runtime : initialChatRuntimeState();
  const turnScopedState = clearTurnScopedState(state);
  return patchChatState(turnScopedState, {
    connection: {
      ...state.connection,
      statusText: action.status ?? state.connection.statusText,
    },
    threadList: {
      ...state.threadList,
      listedThreads: action.listedThreads ?? state.threadList.listedThreads,
    },
    panelThread: {
      kind: "active",
      thread: {
        id: action.thread.id,
        title: (action.thread.name ?? action.thread.preview) || null,
        cwd: action.cwd,
        goal: null,
        tokenUsage: null,
        lifetime: action.lifetime ?? { kind: "persistent" },
        provenance: action.thread.provenance,
      },
    },
    runtime: {
      ...runtimeBase,
      active: {
        serviceTierKnown: action.serviceTierKnown ?? true,
        model: action.model,
        reasoningEffort: action.reasoningEffort,
        collaborationMode: initialActiveChatRuntimeState().collaborationMode,
        serviceTier: action.serviceTier,
        approvalsReviewer: action.approvalsReviewer,
        approvalPolicyKnown: action.approvalPolicyKnown,
        sandboxPolicyKnown: action.sandboxPolicyKnown,
        permissionProfileKnown: action.permissionProfileKnown,
        approvalPolicy: action.approvalPolicy,
        sandboxPolicy: action.sandboxPolicy,
        activePermissionProfile: action.activePermissionProfile,
      },
    },
    turn: initialTurnState(),
    threadStream: initialThreadStreamState(action.items ?? []),
    pendingSubmission:
      action.preservePendingSubmissionId && state.pendingSubmission?.id === action.preservePendingSubmissionId
        ? { ...state.pendingSubmission, targetThreadId: action.thread.id }
        : null,
    requests: initialRequestState(),
    composer: initialComposerState(),
    ui: initialUiState(),
  });
}

function reduceActiveThreadSettingsAppliedTransition(state: ChatState, action: ActiveThreadSettingsAppliedAction): ChatState {
  const activeThread = activeThreadState(state);
  if (!activeThread) return state;
  return patchChatState(state, {
    panelThread: {
      kind: "active",
      thread: { ...activeThread, cwd: action.cwd },
    },
    runtime: {
      ...state.runtime,
      active: {
        serviceTierKnown: true,
        model: action.model,
        reasoningEffort: action.reasoningEffort,
        collaborationMode: action.collaborationMode,
        serviceTier: action.serviceTier,
        approvalsReviewer: action.approvalsReviewer,
        approvalPolicyKnown: action.approvalPolicyKnown,
        sandboxPolicyKnown: action.sandboxPolicyKnown,
        permissionProfileKnown: action.permissionProfileKnown,
        approvalPolicy: action.approvalPolicy,
        sandboxPolicy: action.sandboxPolicy,
        activePermissionProfile: action.activePermissionProfile,
      },
      pending: {
        ...state.runtime.pending,
        collaborationMode: unchangedCollaborationModeIntent(),
      },
    },
  });
}

function reduceActiveThreadGoalSetTransition(state: ChatState, goal: ThreadGoal | null): ChatState {
  const activeThread = activeThreadState(state);
  if (!activeThread) return state;
  return patchChatState(state, {
    panelThread: { kind: "active", thread: { ...activeThread, goal } },
    ui: maybeClearGoalObjectiveExpansion(state.ui, activeThread.goal, goal),
  });
}

function reduceRestoredThreadAppliedTransition(state: ChatState, threadId: string, fallbackTitle: string | null): ChatState {
  const cleared = clearThreadScopedState(state);
  return patchChatState(cleared, {
    connection: { ...cleared.connection, statusText: "Thread ready to resume." },
    panelThread: createAwaitingResumeThreadState(threadId, fallbackTitle),
  });
}

function reduceRestoredThreadRenamedTransition(state: ChatState, threadId: string, name: string | null): ChatState {
  if (state.panelThread.kind !== "awaiting-resume" || state.panelThread.threadId !== threadId) return state;
  return patchChatState(state, { panelThread: { ...state.panelThread, fallbackTitle: name } });
}

function reduceViewStateClearedTransition(state: ChatState): ChatState {
  const cleared = clearThreadScopedState(state);
  return patchChatState(cleared, { connection: { ...cleared.connection, statusText: "Idle" } });
}

function reduceTurnStartedTransition(state: ChatState, action: TurnStartedAction): ChatState {
  const lifecycle = transitionChatTurnLifecycleState(state.turn.lifecycle, { type: "started", turnId: action.turnId });
  if (lifecycle === state.turn.lifecycle) return state;
  const activeThread =
    activeThreadState(state) ?? (state.turn.lifecycle.kind === "starting" ? createActiveThreadState(action.threadId) : null);
  if (!activeThread || activeThread.id !== action.threadId) return state;
  return patchChatState(state, {
    panelThread: { kind: "active", thread: activeThread },
    turn: { lifecycle },
    connection: { ...state.connection, statusText: STATUS_TURN_RUNNING },
    threadStream: action.items
      ? threadStreamWithActiveTurnItems(state.threadStream, action.turnId, action.items)
      : threadStreamStartActiveSegment(state.threadStream, action.turnId, []),
  });
}

function reduceTurnCompletedTransition(state: ChatState, action: TurnCompletedAction): ChatState {
  const lifecycle = transitionChatTurnLifecycleState(state.turn.lifecycle, { type: "completed", turnId: action.turnId });
  if (lifecycle === state.turn.lifecycle) return state;
  return patchChatState(state, {
    turn: { lifecycle },
    threadStream: threadStreamWithItems(state.threadStream, action.items),
    connection: { ...state.connection, statusText: `Turn ${action.status}.` },
  });
}

function reduceTurnOptimisticStartedTransition(state: ChatState, action: TurnOptimisticStartedAction): ChatState {
  if (
    action.pendingSubmissionId &&
    (state.pendingSubmission?.id !== action.pendingSubmissionId || state.pendingSubmission.phase !== "committed")
  ) {
    return state;
  }
  const lifecycle = transitionChatTurnLifecycleState(state.turn.lifecycle, {
    type: "optimistic-started",
    pendingTurnStart: action.pendingTurnStart,
  });
  return patchChatState(state, {
    turn: { lifecycle },
    pendingSubmission: action.pendingSubmissionId ? null : state.pendingSubmission,
    threadStream: threadStreamStartActiveSegment(state.threadStream, null, [action.item]),
  });
}

function reduceTurnStartAcknowledgedTransition(state: ChatState, action: TurnStartAcknowledgedAction): ChatState {
  const lifecycle = transitionChatTurnLifecycleState(state.turn.lifecycle, {
    type: "start-acknowledged",
    turnId: action.turnId,
  });
  if (lifecycle === state.turn.lifecycle) return state;
  return patchChatState(state, {
    turn: { lifecycle },
    threadStream: threadStreamWithActiveTurnItems(state.threadStream, action.turnId, action.items),
  });
}

function reduceTurnStartFailedTransition(state: ChatState, action: TurnStartFailedAction): ChatState {
  const lifecycle = transitionChatTurnLifecycleState(state.turn.lifecycle, { type: "start-failed" });
  if (lifecycle === state.turn.lifecycle) return state;
  return patchChatState(state, {
    turn: { lifecycle },
    threadStream: threadStreamWithItems(state.threadStream, action.items),
  });
}

function reducePendingStartHookUpsertedTransition(state: ChatState, action: PendingStartHookUpsertedAction): ChatState {
  return patchChatState(state, {
    threadStream: reduceThreadStreamSlice(state.threadStream, { type: "thread-stream/item-upserted", item: action.item }),
    turn: {
      lifecycle: transitionChatTurnLifecycleState(state.turn.lifecycle, {
        type: "pending-start-hook-upserted",
        pendingTurnStart: action.pendingTurnStart,
      }),
    },
  });
}

function reduceRequestResolvedTransition(state: ChatState, action: RequestResolvedAction): ChatState {
  const requests = resolveChatRequest(state.requests, action.requestId);
  if (requests === state.requests) return state;
  return patchChatState(state, {
    requests,
    ui: clearResolvedRequestDisclosures(state.ui, action.requestId),
    threadStream: action.resultItem
      ? reduceThreadStreamSlice(state.threadStream, { type: "thread-stream/item-added", item: action.resultItem })
      : state.threadStream,
  });
}

function clearTurnScopedState(state: ChatState): ChatState {
  return patchChatState(state, {
    turn: {
      lifecycle: transitionChatTurnLifecycleState(state.turn.lifecycle, { type: "cleared" }),
    },
    threadStream: threadStreamWithItems(state.threadStream, threadStreamItems(state.threadStream)),
    requests: initialRequestState(),
    ui: clearAllRequestDisclosures(state.ui),
  });
}

function clearThreadScopedState(state: ChatState): ChatState {
  return clearTurnScopedState(
    patchChatState(state, {
      panelThread: initialPanelThreadState(),
      runtime: initialChatRuntimeState(),
      threadStream: initialThreadStreamState(),
      pendingSubmission: null,
      composer: initialComposerState(),
      ui: initialUiState(),
    }),
  );
}

function clearConnectionScopedState(state: ChatState): ChatState {
  return patchChatState(clearTurnScopedState(state), {
    panelThread: state.panelThread.kind === "awaiting-resume" ? state.panelThread : initialPanelThreadState(),
    runtime: initialChatRuntimeState(),
    connection: {
      ...state.connection,
      serverDiagnostics: createServerDiagnostics(),
      rateLimit: null,
      availableModels: [],
      availableSkills: [],
      availablePermissionProfiles: [],
    },
    threadList: initialThreadListState(),
    pendingSubmission: null,
    composer:
      state.pendingSubmission?.phase === "cancellable"
        ? { ...initialComposerState(), draft: state.pendingSubmission.originalDraft }
        : state.composer,
  });
}

function clearConnectionContextState(state: ChatState): ChatState {
  const cleared = clearConnectionScopedState(state);
  return patchChatState(cleared, {
    connection: {
      ...cleared.connection,
      runtimeConfig: null,
      initializeResponse: null,
    },
  });
}

function reduceChatSlices(state: ChatState, action: ChatSliceAction): ChatState {
  return patchChatState(state, {
    connection: reduceConnectionSlice(state.connection, action),
    threadList: reduceThreadListSlice(state.threadList, action),
    panelThread: reducePanelThreadSlice(state.panelThread, action),
    runtime: reduceRuntimeSlice(state.runtime, action),
    turn: state.turn,
    pendingSubmission: state.pendingSubmission,
    requests: isRequestAction(action) ? reduceRequestSlice(state.requests, action) : state.requests,
    threadStream: isThreadStreamAction(action) ? reduceThreadStreamSlice(state.threadStream, action) : state.threadStream,
    composer: reduceComposerSlice(state.composer, action),
    ui: isUiAction(action) ? reduceUiSlice(state.ui, action) : state.ui,
  });
}

function reduceConnectionSlice(state: ChatConnectionState, action: ChatSliceAction): ChatConnectionState {
  switch (action.type) {
    case "connection/status-set":
      return patchObject(state, { statusText: action.statusText, ...definedPatch("phase", action.phase) });
    case "connection/initialized":
      return patchObject(state, { initializeResponse: action.initializeResponse });
    case "connection/metadata-applied":
      return patchObject(state, {
        ...definedPatch("runtimeConfig", action.runtimeConfig),
        ...definedPatch("availableModels", action.availableModels),
        ...definedPatch("availableSkills", action.availableSkills),
        ...definedPatch("availablePermissionProfiles", action.availablePermissionProfiles),
        ...definedPatch("rateLimit", action.rateLimit),
        ...definedPatch("serverDiagnostics", action.serverDiagnostics),
      });
    default:
      return state;
  }
}

function reduceThreadListSlice(state: ChatThreadListState, action: ChatSliceAction): ChatThreadListState {
  if (action.type !== "thread-list/applied") return state;
  return patchObject(state, { listedThreads: action.threads });
}

function reducePanelThreadSlice(state: ChatPanelThreadState, action: ChatSliceAction): ChatPanelThreadState {
  if (state.kind !== "active") return state;
  switch (action.type) {
    case "active-thread/cwd-set":
      return patchObject(state, { thread: patchObject(state.thread, { cwd: action.cwd }) });
    case "active-thread/token-usage-set":
      return patchObject(state, { thread: patchObject(state.thread, { tokenUsage: action.tokenUsage }) });
    default:
      return state;
  }
}

function reduceRuntimeSlice(state: ChatRuntimeState, action: ChatSliceAction): ChatRuntimeState {
  switch (action.type) {
    case "runtime/model-requested":
      return patchObject(state, requestModelRuntimeState(state, action.model));
    case "runtime/model-reset-to-config":
      return patchObject(state, resetModelToConfigRuntimeState(state));
    case "runtime/reasoning-effort-requested":
      return patchObject(state, requestReasoningEffortRuntimeState(state, action.effort));
    case "runtime/reasoning-effort-reset-to-config":
      return patchObject(state, resetReasoningEffortToConfigRuntimeState(state));
    case "runtime/permission-profile-requested":
      return patchObject(state, requestPermissionProfileRuntimeState(state, action.permissionProfile));
    case "runtime/permission-profile-reset-to-config":
      return patchObject(state, resetPermissionProfileToConfigRuntimeState(state));
    case "runtime/fast-mode-requested":
      return patchObject(state, requestFastModeRuntimeState(state, action.fastMode));
    case "runtime/fast-mode-request-cleared":
      return patchObject(state, clearRequestedFastModeRuntimeState(state));
    case "runtime/approvals-reviewer-requested":
      return patchObject(state, requestApprovalsReviewerRuntimeState(state, action.approvalsReviewer));
    case "runtime/approvals-reviewer-request-cleared":
      return patchObject(state, clearRequestedApprovalsReviewerRuntimeState(state));
    case "runtime/requested-collaboration-mode-set":
      return patchObject(state, setSelectedCollaborationModeRuntimeState(state, action.collaborationMode));
    case "runtime/pending-thread-settings-committed":
      return patchObject(state, commitAppliedRuntimeSettingsPatchState(state, action.update));
    default:
      return state;
  }
}

function reduceComposerSlice(state: ChatComposerState, action: ChatSliceAction): ChatComposerState {
  switch (action.type) {
    case "composer/draft-set":
      return patchObject(state, {
        draft: action.draft,
        ...(action.clearSuggestions ? { suggestions: [], suggestSelected: 0 } : {}),
        ...(action.resetDismissedSignature ? { suggestionsDismissedSignature: null } : {}),
      });
    case "composer/input-set":
      return setComposerSuggestionsSlice(
        patchObject(state, {
          draft: action.draft,
          suggestionsDismissedSignature: action.dismissedSignature ?? null,
        }),
        action.suggestions,
        action.selected ?? state.suggestSelected,
        action.dismissedSignature ?? null,
      );
    case "composer/suggestions-set":
      return setComposerSuggestionsSlice(
        state,
        action.suggestions,
        action.selected ?? state.suggestSelected,
        action.dismissedSignature === undefined ? state.suggestionsDismissedSignature : action.dismissedSignature,
      );
    default:
      return state;
  }
}

function initialConnectionState(): ChatConnectionState {
  return {
    phase: { kind: "idle" },
    statusText: "Idle",
    runtimeConfig: null,
    initializeResponse: null,
    serverDiagnostics: createServerDiagnostics(),
    rateLimit: null,
    availableModels: [],
    availableSkills: [],
    availablePermissionProfiles: [],
  };
}

function initialThreadListState(): ChatThreadListState {
  return { listedThreads: [] };
}

function initialPanelThreadState(): ChatPanelThreadState {
  return { kind: "empty" };
}

function createAwaitingResumeThreadState(threadId: string, fallbackTitle: string | null): ChatPanelThreadState {
  return {
    kind: "awaiting-resume",
    threadId,
    fallbackTitle,
  };
}

function createActiveThreadState(id: string): ChatActiveThreadState {
  return {
    id,
    title: null,
    cwd: null,
    goal: null,
    tokenUsage: null,
    lifetime: null,
    provenance: null,
  };
}

function initialTurnState(): ChatTurnState {
  return initialChatTurnState();
}

function initialThreadStreamState(items: readonly ThreadStreamItem[] = []): ChatThreadStreamState {
  return initialChatThreadStreamState(items);
}

function initialRequestState(): ChatRequestState {
  return initialChatRequestState();
}

function initialComposerState(): ChatComposerState {
  return {
    draft: "",
    suggestSelected: 0,
    suggestions: [],
    suggestionsDismissedSignature: null,
  };
}

function setComposerSuggestionsSlice(
  state: ChatComposerState,
  suggestions: readonly ComposerSuggestion[],
  selected: number,
  dismissedSignature: string | null,
): ChatComposerState {
  if (
    state.suggestSelected === selected &&
    state.suggestionsDismissedSignature === dismissedSignature &&
    state.suggestions === suggestions
  ) {
    return state;
  }
  return patchObject(state, {
    suggestions,
    suggestSelected: selected,
    suggestionsDismissedSignature: dismissedSignature,
  });
}

function patchChatState(state: ChatState, patch: Partial<ChatState>): ChatState {
  return patchObject(state, patch);
}

type DeepReadonly<T> = T extends (...args: never[]) => unknown
  ? T
  : T extends ReadonlyMap<infer Key, infer Value>
    ? ReadonlyMap<DeepReadonly<Key>, DeepReadonly<Value>>
    : T extends ReadonlySet<infer Value>
      ? ReadonlySet<DeepReadonly<Value>>
      : T extends readonly (infer Value)[]
        ? readonly DeepReadonly<Value>[]
        : T extends object
          ? { readonly [Key in keyof T]: DeepReadonly<T[Key]> }
          : T;
