import type { ChatMessage, CodeAgentAvailability, ConversationRecord, LiveSurface, SessionTab } from "../types/domain";
import { firstNonEmptyString, nonEmptyString } from "../utils";
import { mergeRunnerTrace } from "./runner-trace";
import type { ComposerState, WorkbenchState } from "./workbench-state";

export type Action =
  | { type: "hydrate:start" }
  | { type: "hydrate:done"; workspace: WorkbenchState["workspace"]; conversations?: ConversationRecord[]; messages: ChatMessage[] }
  | { type: "workspace:sync"; workspace: WorkbenchState["workspace"] }
  | { type: "hydrate:error"; error: string }
  | { type: "live:set"; live: WorkbenchState["live"]; availability: CodeAgentAvailability | null }
  | { type: "profile:set"; profile: WorkbenchState["providerProfile"] }
  | { type: "timeout:set"; codeAgentTimeoutMs: number }
  | { type: "gateway-timeout:set"; gatewayShellTimeoutMs: number }
  | { type: "message:pending"; user: ChatMessage; pending: ChatMessage; request: NonNullable<WorkbenchState["currentRequest"]> }
  | { type: "message:steer"; user: ChatMessage; targetMessageId: string; request: NonNullable<WorkbenchState["currentRequest"]> }
  | { type: "message:replace"; messageId: string; message: ChatMessage }
  | { type: "message:trace-status"; messageId: string; status: string }
  | { type: "message:trace"; messageId: string; trace: NonNullable<ChatMessage["runnerTrace"]> }
  | { type: "message:complete"; messageId: string; message: ChatMessage; availability: CodeAgentAvailability | null; workspace?: WorkbenchState["workspace"] }
  | { type: "message:fail"; messageId: string; message: ChatMessage }
  | { type: "chat:steer-done" }
  | { type: "chat:done" }
  | { type: "conversation:select"; conversation: ConversationRecord | null; workspace?: WorkbenchState["workspace"] }
  | { type: "conversation:list"; conversations: ConversationRecord[] }
  | { type: "conversation:clear" };

export function workbenchReducer(state: WorkbenchState, action: Action): WorkbenchState {
  switch (action.type) {
    case "hydrate:start": return { ...state, loading: true, error: null };
    case "hydrate:done": return { ...state, loading: false, workspace: action.workspace, conversations: action.conversations ?? state.conversations, messages: action.messages, error: null };
    case "workspace:sync": return { ...state, workspace: action.workspace ?? state.workspace };
    case "hydrate:error": return { ...state, loading: false, error: action.error };
    case "live:set": return { ...state, live: action.live, codeAgentAvailability: action.availability };
    case "profile:set": return { ...state, providerProfile: action.profile };
    case "timeout:set": return { ...state, codeAgentTimeoutMs: action.codeAgentTimeoutMs };
    case "gateway-timeout:set": return { ...state, gatewayShellTimeoutMs: action.gatewayShellTimeoutMs };
    case "message:pending": return { ...state, messages: [...state.messages, action.user, action.pending], chatPending: true, currentRequest: { ...action.request, status: "running" } };
    case "message:steer": return { ...state, messages: insertBeforeMessage(state.messages, action.targetMessageId, action.user), chatPending: true, currentRequest: { ...action.request, status: "running" } };
    case "message:replace": return { ...state, messages: replaceMessage(state.messages, action.messageId, action.message) };
    case "message:trace-status": {
      const updated = state.messages.map((message) => message.id === action.messageId ? { ...message, traceReplayStatus: action.status, updatedAt: new Date().toISOString() } : message);
      return { ...state, messages: updated };
    }
    case "message:trace": {
      const updated = state.messages.map((message) => message.id === action.messageId ? { ...message, runnerTrace: mergeRunnerTrace(message.runnerTrace, action.trace) } : message);
      return { ...state, messages: updated };
    }
    case "message:complete": return { ...state, messages: replaceMessage(state.messages, action.messageId, action.message), codeAgentAvailability: action.availability ?? state.codeAgentAvailability, workspace: action.workspace ?? state.workspace };
    case "message:fail": return { ...state, messages: replaceMessage(state.messages, action.messageId, action.message) };
    case "chat:steer-done": return { ...state, chatPending: false };
    case "chat:done": return { ...state, chatPending: false, currentRequest: state.currentRequest ? { ...state.currentRequest, status: "completed" } : null };
    case "conversation:select": {
      const messages = action.conversation?.messages ?? [];
      return { ...state, workspace: action.workspace ?? state.workspace, messages, chatPending: hasRunningAgentMessage(messages), currentRequest: null, error: null };
    }
    case "conversation:list": return { ...state, conversations: action.conversations };
    case "conversation:clear": return { ...state, messages: [], currentRequest: null, chatPending: false };
    default: return state;
  }
}

function replaceMessage(messages: ChatMessage[], messageId: string, replacement: ChatMessage): ChatMessage[] {
  return messages.map((message) => message.id === messageId ? replacement : message);
}

function insertBeforeMessage(messages: ChatMessage[], messageId: string, inserted: ChatMessage): ChatMessage[] {
  if (messages.some((message) => message.id === inserted.id)) return messages;
  const index = messages.findIndex((message) => message.id === messageId);
  if (index < 0) return [...messages, inserted];
  return [...messages.slice(0, index), inserted, ...messages.slice(index)];
}

export function composerFromState(state: WorkbenchState, activeConversationId: string | null): ComposerState {
  const latestMessage = latestConversationMessage(state.messages, activeConversationId);
  const runningMessage = findRunningAgentMessage(state.messages, activeConversationId);
  const currentRequestInConversation = state.currentRequest && state.currentRequest.conversationId === activeConversationId ? state.currentRequest : null;
  const activeTraceId = firstNonEmptyString(currentRequestInConversation?.traceId, runningMessage?.traceId, runningMessage?.runnerTrace?.traceId, state.workspace?.activeTraceId, state.workspace?.workspace?.activeTraceId);
  const workspaceStatus = firstNonEmptyString(state.workspace?.workspace?.sessionStatus);
  const sessionStatus = firstNonEmptyString(runningMessage?.status, currentRequestInConversation?.status, latestMessage?.status, workspaceStatus);
  const latestTerminal = isTerminalStatus(latestMessage?.status);
  const terminal = latestTerminal || isTerminalStatus(currentRequestInConversation?.status) || (!latestMessage && isTerminalStatus(workspaceStatus));
  const activeByStatus = isActiveStatus(runningMessage?.status) || isActiveStatus(runningMessage?.runnerTrace?.status) || isActiveStatus(currentRequestInConversation?.status) || isActiveStatus(workspaceStatus) || state.chatPending;
  const canSteer = Boolean(activeConversationId && activeTraceId && !terminal && activeByStatus);
  const conversationId = activeConversationId;
  const sessionId = firstNonEmptyString(runningMessage?.sessionId, currentRequestInConversation?.sessionId, state.workspace?.selectedAgentSessionId, state.workspace?.workspace?.selectedAgentSessionId);
  const threadId = firstNonEmptyString(runningMessage?.threadId, currentRequestInConversation?.threadId, state.workspace?.workspace?.threadId);
  if (canSteer) {
    return { disabled: false, disabledReason: null, submitMode: "steer", route: "/v1/agent/chat/steer", targetTraceId: activeTraceId, conversationId, sessionId, threadId };
  }
  if (!activeConversationId && !state.workspace?.workspaceId) {
    return { disabled: true, disabledReason: "session_required", submitMode: "turn", route: "/v1/agent/chat", targetTraceId: null, conversationId, sessionId, threadId };
  }
  return { disabled: false, disabledReason: null, submitMode: "turn", route: "/v1/agent/chat", targetTraceId: null, conversationId, sessionId, threadId };
}

function findRunningAgentMessage(messages: ChatMessage[], activeConversationId: string | null): ChatMessage | null {
  for (const message of [...messages].reverse()) {
    if (message.role !== "agent") continue;
    if (message.status !== "running" && message.runnerTrace?.status !== "running") continue;
    if (activeConversationId && message.conversationId && message.conversationId !== activeConversationId) continue;
    return message;
  }
  return null;
}

function latestConversationMessage(messages: ChatMessage[], activeConversationId: string | null): ChatMessage | null {
  for (const message of [...messages].reverse()) {
    if (activeConversationId && message.conversationId && message.conversationId !== activeConversationId) continue;
    return message;
  }
  return null;
}

function hasRunningAgentMessage(messages: ChatMessage[]): boolean {
  return Boolean(findRunningAgentMessage(messages, null));
}

function isActiveStatus(value: unknown): boolean {
  return ["running", "pending", "accepted", "processing"].includes(String(value ?? "").trim().toLowerCase());
}

function isTerminalStatus(value: unknown): boolean {
  return ["completed", "failed", "blocked", "timeout", "canceled", "cancelled"].includes(String(value ?? "").trim().toLowerCase());
}

export function availabilityFromLive(live: LiveSurface): CodeAgentAvailability | null {
  const candidates = [live.healthLive.data?.codeAgent, live.healthLive.data?.availability, live.health.data?.codeAgent, live.restIndex.data?.codeAgent];
  return candidates.find((item): item is CodeAgentAvailability => Boolean(item && typeof item === "object")) ?? null;
}

export function conversationFromTab(tab: SessionTab): ConversationRecord {
  return { conversationId: tab.conversationId ?? tab.key, sessionId: tab.sessionId, threadId: tab.threadId, status: tab.status, messages: [] };
}

export function readStoredString(key: string): string | null {
  try { return nonEmptyString(window.localStorage.getItem(key)); } catch { return null; }
}

export function readStoredNumber(key: string, fallback: number, min: number, max: number): number {
  const value = Number(readStoredString(key));
  if (!Number.isFinite(value)) return fallback;
  return Math.min(Math.max(Math.trunc(value), min), max);
}

export function readProviderProfile(): string {
  const value = readStoredString("hwlab.workbench.codeAgentProviderProfile.v1");
  return value && /^[a-z0-9][a-z0-9-]{0,63}$/u.test(value) && value !== "runtime-default" ? value : "deepseek";
}
