import type { ThreadTokenUsage } from "../../../../domain/runtime/metrics";
import { resumedThreadAction } from "../state/actions";
import { activeThreadState } from "../state/root-reducer";
import type { ChatStateStore } from "../state/store";
import { threadStreamIsEmpty } from "../state/thread-stream";
import type { HistoryController } from "./history-controller";
import type { ActiveChatResume, ChatResumeWorkTracker } from "./resume-work";
import type { ThreadResumeSnapshot, ThreadResumeTransport } from "./thread-loading-transport";
import { canSwitchToThread } from "./thread-switching";

export interface ResumeActionsHost {
  stateStore: ChatStateStore;
  resumeWork: ChatResumeWorkTracker;
  history: HistoryController;
  resumeTransport: ThreadResumeTransport;
  closing: () => boolean;
  resetThreadTurnPresence: (hadTurns: boolean) => void;
  notifyActiveThreadIdentityChanged: () => void;
  addSystemMessage: (text: string) => void;
  refreshLiveState: () => void;
  syncThreadGoal: (threadId: string) => Promise<void>;
  recoverTokenUsageFromRollout?: (path: string) => Promise<ThreadTokenUsage | null>;
}

export interface ResumeActions {
  resumeThread(threadId: string): Promise<void>;
}

export function createResumeActions(host: ResumeActionsHost): ResumeActions {
  return {
    resumeThread: (threadId) => resumeThread(host, threadId),
  };
}

async function resumeThread(host: ResumeActionsHost, threadId: string): Promise<void> {
  if (!canSwitchToThread(host.stateStore.getState(), threadId)) {
    host.addSystemMessage("Finish or interrupt the current turn before switching threads.");
    return;
  }
  const resume = host.resumeWork.begin(threadId);
  host.history.invalidate();

  try {
    const response = await host.resumeTransport.resumeThread(threadId);
    if (!response) return;
    if (isStaleResume(host, resume)) return;
    applyResumedThread(host, response);
    recoverResumedThreadTokenUsage(host, response.activation.thread.id, response.rolloutPath, resume);
    if (response.initialHistoryPage) {
      host.history.applyLatestPage(response.activation.thread.id, response.initialHistoryPage);
    } else {
      await host.history.loadLatest(response.activation.thread.id);
    }
    if (isStaleResume(host, resume)) return;
    await host.syncThreadGoal(response.activation.thread.id);
    if (isStaleResume(host, resume)) return;
    const renderFallbackMessage = threadStreamIsEmpty(host.stateStore.getState().threadStream);
    if (renderFallbackMessage) {
      host.addSystemMessage(`Resumed thread ${response.activation.thread.id}`);
    }
    host.refreshLiveState();
  } catch (error) {
    if (isStaleResume(host, resume)) return;
    host.addSystemMessage(error instanceof Error ? error.message : String(error));
  }
}

function applyResumedThread(host: ResumeActionsHost, response: ThreadResumeSnapshot): void {
  host.stateStore.dispatch(
    resumedThreadAction({
      response: response.activation,
      listedThreads: host.stateStore.getState().threadList.listedThreads,
    }),
  );
  host.resetThreadTurnPresence(false);
  host.notifyActiveThreadIdentityChanged();
}

function recoverResumedThreadTokenUsage(host: ResumeActionsHost, threadId: string, path: string | null, resume: ActiveChatResume): void {
  if (!path || !host.recoverTokenUsageFromRollout) return;
  void host
    .recoverTokenUsageFromRollout(path)
    .then((tokenUsage) => {
      if (!tokenUsage || isStaleResume(host, resume)) return;
      const state = host.stateStore.getState();
      const activeThread = activeThreadState(state);
      if (!activeThread || activeThread.id !== threadId || activeThread.tokenUsage !== null) return;
      host.stateStore.dispatch({ type: "active-thread/token-usage-set", tokenUsage });
      host.refreshLiveState();
    })
    .catch(() => undefined);
}

function isStaleResume(host: ResumeActionsHost, resume: ActiveChatResume): boolean {
  return host.resumeWork.isStale(resume) || host.closing();
}
