import { useCallback, useEffect, useMemo, useReducer, useRef } from "react";

import { api } from "../services/api/client";
import type {
  AgentChatResponse,
  AgentChatResultResponse,
  ChatMessage,
  CodeAgentAvailability,
  ConversationRecord,
  LiveBuildsPayload,
  LiveSurface,
  ProviderProfile,
  SessionTab,
  WorkspaceRecord
} from "../types/domain";
import { firstNonEmptyString, nextProtocolId } from "../utils";
import { WORKBENCH_PROJECT_ID, rememberWorkbenchProjectId, workspaceProjectId } from "./constants";
import {
  availabilityFromLive,
  composerFromState,
  conversationFromTab,
  readProviderProfile,
  readStoredNumber,
  workbenchReducer,
  type Action
} from "./workbench-reducer";
import type { ComposerState, WorkbenchState } from "./workbench-state";
import { conversationsToTabs, ensureWorkspace, makeMessage, messageFromAgentResponse, messagesFromWorkspace, persistConversation } from "./conversation";
import {
  isResultUrlStatus,
  isTerminalStatus,
  fetchTraceSnapshot,
  mergeTraceResults,
  snapshotToRunnerTrace,
  subscribeToTrace,
  type TraceSnapshot
} from "./runner-trace";
import { useTraceReattach } from "./trace-reattach";
import {
  cancelAgentMessageAction,
  replayAgentTraceAction,
  retryAgentMessageAction
} from "./runner-actions";

const DEFAULT_CODE_AGENT_TIMEOUT_MS = 1_800_000;
const DEFAULT_GATEWAY_TIMEOUT_MS = 120_000;
const initialState: WorkbenchState = {
  workspace: null,
  conversations: [],
  messages: [],
  providerProfile: readProviderProfile(),
  codeAgentTimeoutMs: readStoredNumber("hwlab.workbench.codeAgentTimeoutMs.v1", DEFAULT_CODE_AGENT_TIMEOUT_MS, 30_000, 2_400_000),
  gatewayShellTimeoutMs: readStoredNumber("hwlab.workbench.gatewayShellTimeoutMs.v1", DEFAULT_GATEWAY_TIMEOUT_MS, 30_000, 600_000),
  live: null,
  codeAgentAvailability: null,
  loading: true,
  chatPending: false,
  currentRequest: null,
  error: null
};

export interface WorkbenchStore {
  state: WorkbenchState;
  sessionTabs: SessionTab[];
  activeConversationId: string | null;
  composer: ComposerState;
  hydrate(): Promise<void>;
  refreshLive(): Promise<void>;
  createSession(): Promise<void>;
  selectConversation(tab: SessionTab): Promise<void>;
  deleteCurrentSession(): Promise<void>;
  submitMessage(text: string): Promise<void>;
  cancelAgentMessage(messageId: string): Promise<void>;
  retryAgentMessage(messageId: string): Promise<void>;
  replayAgentTrace(messageId: string): Promise<void>;
  clearConversation(): void;
  /**
   * Note recent user / system activity so the inactivity-timeout in fetchJson
   * (services/api/client.ts) treats the user-configured codeAgentTimeoutMs as
   * an inactivity budget rather than a total budget. Hooked to the Web
   * composer onTyping callback in App.tsx; also called internally on submit
   * and trace snapshot updates. This is the core of the HWLAB #795
   * regression fix: the React migration removed the pre-React
   * fetchJson activityRef wiring, so the Web UI was
   * falling back to a total-timeout that killed long-running Code Agent
   * operations even while trace events kept flowing.
   */
  recordActivity(): void;
  setProviderProfile(profile: ProviderProfile): void;
  setCodeAgentTimeoutMs(value: number): void;
  setGatewayShellTimeoutMs(value: number): void;
}

export function useWorkbenchStore(enabled: boolean, projectId = WORKBENCH_PROJECT_ID): WorkbenchStore {
  const [state, dispatch] = useReducer(workbenchReducer, initialState);
  const activeProjectId = workspaceProjectId(state.workspace, projectId);
  // Single shared activity clock so submit, trace polling, and user-typing
  // all reset the same inactivity window. Kept in a ref (not state) so
  // updates never trigger re-renders.
  const lastActivityAtRef = useRef<number>(Date.now());
  const updateActivity = useCallback(() => {
    lastActivityAtRef.current = Date.now();
  }, []);
  const buildActivityRef = useCallback(
    (waitingFor: string, lastEventLabel: string | null): (() => import("../services/api/client").ActivityRef | null) => {
      return () => {
        const at = lastActivityAtRef.current;
        if (!Number.isFinite(at) || at <= 0) return null;
        return {
          lastActivityAt: at,
          lastActivityIso: new Date(at).toISOString(),
          waitingFor,
          lastEventLabel
        };
      };
    },
    []
  );

  const activeConversationId = firstNonEmptyString(
    state.workspace?.selectedConversationId,
    state.workspace?.workspace?.selectedConversationId,
    state.messages.find((message) => message.conversationId)?.conversationId
  );

  const sessionTabs = useMemo(() => conversationsToTabs(state.conversations, activeConversationId), [state.conversations, activeConversationId]);

  const composer = useMemo(() => composerFromState(state, activeConversationId), [activeConversationId, state]);

  const hydrate = useCallback(async () => {
    dispatch({ type: "hydrate:start" });
    const [workspaceResult, conversationsResult] = await Promise.all([
      api.workspace(projectId),
      api.conversations(projectId)
    ]);
    if (!workspaceResult.ok) {
      dispatch({ type: "hydrate:error", error: workspaceResult.error ?? "workspace unavailable" });
      return;
    }
    const workspace = workspaceResult.data?.workspace ?? null;
    rememberWorkbenchProjectId(workspaceProjectId(workspace, projectId));
    const conversations = conversationsResult.ok ? conversationsResult.data?.conversations ?? [] : [];
    dispatch({
      type: "hydrate:done",
      workspace,
      conversations,
      messages: messagesFromWorkspace(workspace)
    });
  }, [projectId]);

  const refreshLive = useCallback(async () => {
    const [healthLive, health, restIndex, adapter, hwpodSpecs, hwpodNodeOps, liveBuilds] = await Promise.all([
      api.healthLive(),
      api.health(),
      api.restIndex(),
      api.adapter(),
      api.hwpodSpecs(),
      api.hwpodNodeOpsHealth(),
      api.liveBuilds()
    ]);
    const live: LiveSurface = { healthLive, health, restIndex, adapter, hwpodSpecs, hwpodNodeOps, liveBuilds, loadedAt: new Date().toISOString() };
    dispatch({ type: "live:set", live, availability: availabilityFromLive(live) });
  }, []);

  useEffect(() => {
    if (!enabled) return;
    void hydrate();
  }, [enabled, hydrate]);

  useEffect(() => {
    if (!enabled) return;
    void refreshLive();
    const timer = window.setInterval(() => void refreshLive(), 30_000);
    return () => window.clearInterval(timer);
  }, [enabled, refreshLive]);

  // HWLAB #802: re-attach to a running trace on hydrate.
  useTraceReattach({ enabled, projectId: activeProjectId, activeTraceId: firstNonEmptyString(state.workspace?.activeTraceId, state.workspace?.workspace?.activeTraceId), codeAgentTimeoutMs: state.codeAgentTimeoutMs, chatPending: state.chatPending, workspace: state.workspace, messages: state.messages, onActivity: updateActivity, onDispatch: dispatch, onPersist: persistConversation });

  const createSession = useCallback(async () => {
    const workspace = await ensureWorkspace(state.workspace, activeProjectId);
    if (!workspace) return;
    const response = await api.selectConversation(workspace.workspaceId, { projectId: activeProjectId, create: true, updatedByClient: "cloud-web-react" });
    if (response.ok) {
      const nextWorkspace = response.data?.workspace ?? workspace;
      dispatch({ type: "conversation:select", conversation: nextWorkspace.selectedConversation ?? null, workspace: nextWorkspace });
      const conversations = await api.conversations(workspaceProjectId(nextWorkspace, activeProjectId));
      if (conversations.ok) dispatch({ type: "conversation:list", conversations: conversations.data?.conversations ?? [] });
    }
  }, [activeProjectId, state.workspace]);

  const selectConversation = useCallback(async (tab: SessionTab) => {
    const tabProjectId = tab.projectId ?? activeProjectId;
    const workspace = await ensureWorkspace(state.workspace, tabProjectId);
    if (!workspace || !tab.conversationId) return;
    const response = await api.selectConversation(workspace.workspaceId, {
      projectId: tabProjectId,
      conversationId: tab.conversationId,
      sessionId: tab.sessionId,
      threadId: tab.threadId,
      updatedByClient: "cloud-web-react"
    });
    if (response.ok) {
      const nextWorkspace = response.data?.workspace ?? workspace;
      rememberWorkbenchProjectId(workspaceProjectId(nextWorkspace, tabProjectId));
      dispatch({ type: "conversation:select", conversation: nextWorkspace.selectedConversation ?? conversationFromTab(tab), workspace: nextWorkspace });
    }
  }, [activeProjectId, state.workspace]);

  const deleteCurrentSession = useCallback(async () => {
    const conversationId = activeConversationId;
    if (!conversationId) return;
    const workspaceId = state.workspace?.workspaceId;
    const response = await api.deleteConversation(conversationId, { projectId: activeProjectId, workspaceId, updatedByClient: "cloud-web-react" });
    if (response.ok) {
      dispatch({ type: "conversation:select", conversation: response.data?.workspace?.selectedConversation ?? null, workspace: response.data?.workspace ?? state.workspace });
      const conversations = await api.conversations(activeProjectId);
      if (conversations.ok) dispatch({ type: "conversation:list", conversations: conversations.data?.conversations ?? [] });
    }
  }, [activeConversationId, activeProjectId, state.workspace]);

  const submitMessage = useCallback(async (text: string) => {
    const value = text.trim();
    if (!value) return;
    const steerMode = composer.submitMode === "steer";
    const targetTraceId = steerMode && composer.targetTraceId ? composer.targetTraceId : null;
    const traceId = targetTraceId ?? nextProtocolId("trc");
    const steerTraceId = steerMode ? nextProtocolId("trc_steer") : null;
    const displayTraceId = steerTraceId ?? traceId;
    const conversationId = composer.conversationId ?? activeConversationId ?? nextProtocolId("cnv");
    const sessionId = composer.sessionId ?? state.workspace?.selectedAgentSessionId ?? state.workspace?.workspace?.selectedAgentSessionId ?? null;
    const threadId = composer.threadId ?? state.workspace?.workspace?.threadId ?? null;
    const user = makeMessage("user", value, "sent", { traceId: displayTraceId, conversationId, sessionId, threadId, title: steerMode ? "用户引导" : "用户" });
    const pending = makeMessage("agent", "正在处理这次 Code Agent 请求；复杂问题可能需要几分钟。", "running", { traceId, conversationId, sessionId, threadId, title: "Code Agent 处理中" });
    const pendingWithRetry: ChatMessage = { ...pending, retryInput: value };
    const existingActivePending = steerMode ? findActiveAgentMessage(state.messages, traceId, conversationId) : null;
    const activePending = existingActivePending ?? pendingWithRetry;
    const visibleMessages = steerMode && existingActivePending ? insertBeforeMessage(state.messages, existingActivePending.id, user) : [...state.messages, user, pendingWithRetry];
    if (steerMode && existingActivePending) dispatch({ type: "message:steer", user, targetMessageId: existingActivePending.id, request: { traceId, conversationId, sessionId, threadId } });
    else dispatch({ type: "message:pending", user, pending: pendingWithRetry, request: { traceId, conversationId, sessionId, threadId } });
    let currentWorkspace = await persistConversation({ workspace: state.workspace, projectId: activeProjectId, conversationId, sessionId, threadId, messages: visibleMessages });
    if (currentWorkspace) dispatch({ type: "workspace:sync", workspace: currentWorkspace });
    const route = steerMode ? api.steerAgentMessage : api.sendAgentMessage;
    updateActivity();
    const submitActivityRef = buildActivityRef("code-agent-submit", null);
    const response = await route({
      projectId: activeProjectId,
      message: value,
      prompt: value,
      conversationId,
      sessionId,
      threadId,
      traceId,
      providerProfile: state.providerProfile,
      gatewayShellTimeoutMs: state.gatewayShellTimeoutMs,
      workspaceId: currentWorkspace?.workspaceId ?? state.workspace?.workspaceId,
      workspaceRevision: currentWorkspace?.revision ?? state.workspace?.revision,
      targetTraceId,
      steerTraceId
    }, state.codeAgentTimeoutMs, submitActivityRef);
    const finalize = async (result: AgentChatResultResponse, availability: CodeAgentAvailability | null): Promise<void> => {
      const completed = messageFromAgentResponse(activePending.id, activePending, result);
      dispatch({ type: "message:complete", messageId: activePending.id, message: completed, availability: availability ?? result.availability ?? null });
      currentWorkspace = await persistConversation({ workspace: currentWorkspace ?? state.workspace, projectId: activeProjectId, conversationId, sessionId: completed.sessionId ?? sessionId, threadId: completed.threadId ?? threadId, messages: replaceMessage(visibleMessages, activePending.id, completed) });
      if (currentWorkspace) dispatch({ type: "workspace:sync", workspace: currentWorkspace });
      const conversations = await api.conversations(activeProjectId);
      if (conversations.ok) dispatch({ type: "conversation:list", conversations: conversations.data?.conversations ?? [] });
    };
    if (response.ok && response.data) {
      const subscriptionController = new AbortController();
      let lastSnapshot: TraceSnapshot | null = null;
      let terminalResult: AgentChatResultResponse | null = null;
      try {
        await subscribeToTrace({
          traceId,
          projectId: activeProjectId,
          initial: response.data,
          inactivityTimeoutMs: state.codeAgentTimeoutMs,
          signal: subscriptionController.signal,
          onActivity: () => updateActivity(),
          onSnapshot: (snapshot) => {
            lastSnapshot = snapshot;
            dispatch({ type: "message:trace", messageId: activePending.id, trace: snapshotToRunnerTrace(snapshot) });
          },
          onComplete: (result) => {
            terminalResult = result;
          },
          onInfrastructureError: (error) => {
            // HWLAB #802: persist fail too. The user refreshing should see
            // the frontend-reported failure, not a silent "running" state.
            const failed = makeMessage("agent", `Code Agent 在 ${state.codeAgentTimeoutMs}ms 无活动后中断（${error}）；可以继续 steer 或查看 trace。`, "failed", { traceId, conversationId, sessionId, threadId, title: "Code Agent 超时" });
            dispatch({ type: "message:fail", messageId: activePending.id, message: failed });
            void persistConversation({ workspace: currentWorkspace ?? state.workspace, projectId: activeProjectId, conversationId, sessionId, threadId, messages: replaceMessage(visibleMessages, activePending.id, failed) })
              .then((workspace) => { if (workspace) dispatch({ type: "workspace:sync", workspace }); });
            subscriptionController.abort();
          }
        });
      } finally {
        subscriptionController.abort();
      }
      if (terminalResult) {
        const traceTerminal = lastSnapshot;
        await finalize(mergeTraceResults(terminalResult, traceTerminal), response.data.availability ?? null);
      } else if (isResultUrlStatus(response.data) && isTerminalStatus(response.data.status)) {
        const traceTerminal = lastSnapshot;
        await finalize(mergeTraceResults(response.data, traceTerminal), response.data.availability ?? null);
      }
    } else {
      const traceSnapshot = await fetchTraceSnapshot(traceId, traceId, Math.max(8000, state.codeAgentTimeoutMs), undefined, activeProjectId);
      const runnerTrace = traceSnapshot ? snapshotToRunnerTrace(traceSnapshot) : null;
      const keepSteerActive = steerMode && response.status === 0 && !isTerminalStatus(activePending.status) && (!runnerTrace || !isTerminalStatus(runnerTrace.status));
      if (keepSteerActive) {
        const runningWithTrace = runnerTrace ? { ...activePending, runnerTrace } : activePending;
        if (runnerTrace) dispatch({ type: "message:trace", messageId: activePending.id, trace: runnerTrace });
        currentWorkspace = await persistConversation({ workspace: currentWorkspace ?? state.workspace, projectId: activeProjectId, conversationId, sessionId, threadId, messages: replaceMessage(visibleMessages, activePending.id, runningWithTrace) });
        if (currentWorkspace) dispatch({ type: "workspace:sync", workspace: currentWorkspace });
        dispatch({ type: "chat:steer-done" });
        return;
      }
      const failed = makeMessage("agent", response.error ?? "Code Agent 请求失败", "failed", { traceId, conversationId, sessionId, threadId, title: "Code Agent 请求失败" });
      const failedWithTrace = runnerTrace ? { ...failed, runnerTrace } : failed;
      if (runnerTrace) dispatch({ type: "message:trace", messageId: activePending.id, trace: runnerTrace });
      dispatch({ type: "message:fail", messageId: activePending.id, message: failedWithTrace });
      currentWorkspace = await persistConversation({ workspace: currentWorkspace ?? state.workspace, projectId: activeProjectId, conversationId, sessionId, threadId, messages: replaceMessage(visibleMessages, activePending.id, failedWithTrace) });
      if (currentWorkspace) dispatch({ type: "workspace:sync", workspace: currentWorkspace });
    }
    dispatch({ type: "chat:done" });
  }, [activeConversationId, activeProjectId, composer.conversationId, composer.sessionId, composer.submitMode, composer.targetTraceId, composer.threadId, state.codeAgentTimeoutMs, state.gatewayShellTimeoutMs, state.messages, state.providerProfile, state.workspace]);

  const cancelAgentMessage = useCallback(async (messageId: string) => {
    return cancelAgentMessageAction(messageId, {
      activeConversationId,
      projectId: activeProjectId,
      workspace: state.workspace,
      messages: state.messages,
      codeAgentTimeoutMs: state.codeAgentTimeoutMs,
      onReplaceMessage: (id, message) => dispatch({ type: "message:replace", messageId: id, message }),
      onTraceStatus: (id, status) => dispatch({ type: "message:trace-status", messageId: id, status }),
      onTrace: (id, trace) => dispatch({ type: "message:trace", messageId: id, trace }),
      submitMessage
    });
  }, [activeConversationId, activeProjectId, state.workspace, state.messages, state.codeAgentTimeoutMs, submitMessage]);

  const retryAgentMessage = useCallback(async (messageId: string) => {
    return retryAgentMessageAction(messageId, {
      activeConversationId,
      projectId: activeProjectId,
      workspace: state.workspace,
      messages: state.messages,
      codeAgentTimeoutMs: state.codeAgentTimeoutMs,
      onReplaceMessage: (id, message) => dispatch({ type: "message:replace", messageId: id, message }),
      onTraceStatus: (id, status) => dispatch({ type: "message:trace-status", messageId: id, status }),
      onTrace: (id, trace) => dispatch({ type: "message:trace", messageId: id, trace }),
      submitMessage
    });
  }, [activeConversationId, activeProjectId, state.workspace, state.messages, state.codeAgentTimeoutMs, submitMessage]);

  const replayAgentTrace = useCallback(async (messageId: string) => {
    return replayAgentTraceAction(messageId, {
      activeConversationId,
      projectId: activeProjectId,
      workspace: state.workspace,
      messages: state.messages,
      codeAgentTimeoutMs: state.codeAgentTimeoutMs,
      onReplaceMessage: (id, message) => dispatch({ type: "message:replace", messageId: id, message }),
      onTraceStatus: (id, status) => dispatch({ type: "message:trace-status", messageId: id, status }),
      onTrace: (id, trace) => dispatch({ type: "message:trace", messageId: id, trace }),
      submitMessage
    });
  }, [activeConversationId, activeProjectId, state.workspace, state.messages, state.codeAgentTimeoutMs, submitMessage]);

  const clearConversation = useCallback(() => dispatch({ type: "conversation:clear" }), []);

  const recordActivity = useCallback(() => {
    updateActivity();
  }, [updateActivity]);

  const setProviderProfile = useCallback((profile: ProviderProfile) => {
    window.localStorage.setItem("hwlab.workbench.codeAgentProviderProfile.v1", profile);
    dispatch({ type: "profile:set", profile });
  }, []);
  const setCodeAgentTimeoutMs = useCallback((value: number) => {
    window.localStorage.setItem("hwlab.workbench.codeAgentTimeoutMs.v1", String(value));
    dispatch({ type: "timeout:set", codeAgentTimeoutMs: value });
  }, []);
  const setGatewayShellTimeoutMs = useCallback((value: number) => {
    window.localStorage.setItem("hwlab.workbench.gatewayShellTimeoutMs.v1", String(value));
    dispatch({ type: "gateway-timeout:set", gatewayShellTimeoutMs: value });
  }, []);
  return { state, sessionTabs, activeConversationId, composer, hydrate, refreshLive, createSession, selectConversation, deleteCurrentSession, submitMessage, cancelAgentMessage, retryAgentMessage, replayAgentTrace, clearConversation, recordActivity, setProviderProfile, setCodeAgentTimeoutMs, setGatewayShellTimeoutMs };
}

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

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

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)];
}

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