import type {
  ChatDocumentAttachment,
  ChatImageAttachment,
  ChatTimelineItem,
  ChatTimelineMessageItem,
} from '@easyflow/common-ui';

import type { AguiEvent } from '../../shared/agent-agui/client';

import { ChatTimelineBuilder } from '@easyflow/common-ui';

import { EventSchemas, EventType } from '@ag-ui/client';

import { easyFlowAguiCustomEvent } from '../../shared/agent-agui/custom-events';
import {
  applyAguiEventToTimeline,
  createAguiTimelineProjectionState,
} from '../../shared/agent-agui/projection';

type AgentTryoutRuntimeEvent = AguiEvent;
type AgentTryoutRoundStatus = 'completed' | 'error' | 'running';

interface AgentTryoutRawVariant {
  createdAt: number;
  inputAccepted: boolean;
  lastCursor: number;
  runId?: string;
  runtimeEvents: AgentTryoutRuntimeEvent[];
  status: AgentTryoutRoundStatus;
  updatedAt: number;
  variantIndex: number;
}

interface AgentTryoutRawRound {
  createdAt: number;
  documents?: ChatDocumentAttachment[];
  images?: ChatImageAttachment[];
  prompt: string;
  roundId: string;
  selectedVariantIndex: number;
  status: AgentTryoutRoundStatus;
  updatedAt: number;
  variants: AgentTryoutRawVariant[];
}

interface AgentTryoutRawSessionRecord {
  rounds: AgentTryoutRawRound[];
  sessionId: string;
  version: number;
}

const STORAGE_VERSION = 4;
const MAX_ROUNDS = 50;
const MAX_VARIANTS = 10;
const STORAGE_PREFIX = 'easyflow:agent-tryout-raw-rounds';
const PERSIST_DEBOUNCE_MS = 500;
const DRAFT_RETENTION_MS = 24 * 60 * 60 * 1000;
const memorySessions = new Map<string, AgentTryoutRawRound[]>();

function shouldRetainRound(round: AgentTryoutRawRound) {
  return (
    round.status === 'running' ||
    Date.now() - round.updatedAt < DRAFT_RETENTION_MS
  );
}

function createRoundId() {
  return `round-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
}

function clone<T>(value: T): T {
  // 草稿缓存最终写入 sessionStorage，仅接受 JSON 数据；JSON 往返同时解除 Vue/SDK Proxy，
  // 避免 structuredClone 对代理对象抛出 DataCloneError。
  // eslint-disable-next-line unicorn/prefer-structured-clone -- structuredClone 无法复制 Proxy
  return JSON.parse(JSON.stringify(value)) as T;
}

function storageKey(mode: string, sessionId: string) {
  return `${STORAGE_PREFIX}:${mode}:${sessionId}`;
}

function safeSessionStorage() {
  try {
    return globalThis.sessionStorage;
  } catch {
    return undefined;
  }
}

function createVariant(variantIndex: number): AgentTryoutRawVariant {
  const now = Date.now();
  return {
    createdAt: now,
    inputAccepted: false,
    lastCursor: 0,
    runtimeEvents: [],
    status: 'running',
    updatedAt: now,
    variantIndex,
  };
}

function normalizeRuntimeEvent(value: unknown) {
  const parsed = EventSchemas.safeParse(value);
  return parsed.success ? parsed.data : undefined;
}

function normalizeVariant(value: any, index: number): AgentTryoutRawVariant {
  if (!value || typeof value !== 'object') {
    return createVariant(index);
  }
  const runtimeEvents = Array.isArray(value.runtimeEvents)
    ? value.runtimeEvents
        .map((item: unknown) => normalizeRuntimeEvent(item))
        .filter(
          (
            item: AgentTryoutRuntimeEvent | undefined,
          ): item is AgentTryoutRuntimeEvent => item !== undefined,
        )
    : [];
  return {
    createdAt: Number(value.createdAt || Date.now()),
    inputAccepted: Boolean(
      value.inputAccepted ||
        runtimeEvents.some(
          (event: AgentTryoutRuntimeEvent) =>
            event.type === EventType.CUSTOM &&
            event.name === easyFlowAguiCustomEvent.inputAccepted,
        ),
    ),
    lastCursor: Math.max(0, Number(value.lastCursor || 0)),
    runId: typeof value.runId === 'string' ? value.runId : undefined,
    runtimeEvents,
    status:
      value.status === 'completed' || value.status === 'error'
        ? value.status
        : 'running',
    updatedAt: Number(value.updatedAt || value.createdAt || Date.now()),
    variantIndex: index,
  };
}

function normalizeRound(value: any): AgentTryoutRawRound | undefined {
  if (!value || typeof value !== 'object') {
    return undefined;
  }
  const prompt = String(value.prompt || '');
  const roundId = String(value.roundId || '');
  const documents = Array.isArray(value.documents)
    ? value.documents.slice(0, 3)
    : [];
  const images = Array.isArray(value.images) ? value.images.slice(0, 5) : [];
  if ((!prompt && images.length === 0 && documents.length === 0) || !roundId) {
    return undefined;
  }
  const variants = Array.isArray(value.variants)
    ? value.variants
        .slice(-MAX_VARIANTS)
        .map((item: unknown, index: number) =>
          normalizeVariant(item, index + 1),
        )
    : [];
  if (variants.length === 0) {
    variants.push(createVariant(1));
  }
  const selectedVariantIndex = Math.min(
    Math.max(Number(value.selectedVariantIndex ?? variants.length), 1),
    variants.length,
  );
  return {
    createdAt: Number(value.createdAt || Date.now()),
    documents,
    images,
    prompt,
    roundId,
    selectedVariantIndex,
    status:
      value.status === 'completed' || value.status === 'error'
        ? value.status
        : 'running',
    updatedAt: Number(value.updatedAt || value.createdAt || Date.now()),
    variants,
  };
}

function restoreSession(mode: string, sessionId: string) {
  const key = storageKey(mode, sessionId);
  const memoryRecords = memorySessions.get(key);
  if (memoryRecords) {
    const retained = memoryRecords.filter((round) => shouldRetainRound(round));
    memorySessions.set(key, retained);
    return retained.map((item) => clone(item));
  }
  const storage = safeSessionStorage();
  if (!storage) return [];
  try {
    const raw = storage.getItem(key);
    if (!raw) return [];
    const parsed = JSON.parse(raw) as AgentTryoutRawSessionRecord;
    if (parsed.sessionId !== sessionId || parsed.version !== STORAGE_VERSION) {
      return [];
    }
    const rounds = Array.isArray(parsed.rounds)
      ? parsed.rounds
          .map((item) => normalizeRound(item))
          .filter((item): item is AgentTryoutRawRound => item !== undefined)
          .filter((round) => shouldRetainRound(round))
      : [];
    memorySessions.set(key, clone(rounds));
    return rounds;
  } catch {
    return [];
  }
}

function persistSession(
  mode: string,
  sessionId: string,
  rounds: AgentTryoutRawRound[],
) {
  const key = storageKey(mode, sessionId);
  try {
    // 同一份 JSON 同时用于解除 Proxy、内存快照和 sessionStorage，
    // 避免对完整草稿会话重复做全量序列化。
    const serialized = JSON.stringify({
      rounds: rounds
        .filter((round) => shouldRetainRound(round))
        .slice(-MAX_ROUNDS),
      sessionId,
      version: STORAGE_VERSION,
    } satisfies AgentTryoutRawSessionRecord);
    const snapshot = JSON.parse(serialized) as AgentTryoutRawSessionRecord;
    memorySessions.set(key, snapshot.rounds);
    safeSessionStorage()?.setItem(key, serialized);
  } catch {
    // 本地试用缓存失败不影响当前聊天。
  }
}

function removeStoredSession(mode: string, sessionId: string) {
  const key = storageKey(mode, sessionId);
  memorySessions.delete(key);
  try {
    safeSessionStorage()?.removeItem(key);
  } catch {
    // 清理本地缓存失败不影响界面重置。
  }
}

function selectedVariant(round: AgentTryoutRawRound) {
  return (
    round.variants.find(
      (variant) => variant.variantIndex === round.selectedVariantIndex,
    ) || round.variants[round.variants.length - 1]
  );
}

function appendRuntimeEvent(
  variant: AgentTryoutRawVariant,
  event: AgentTryoutRuntimeEvent,
) {
  const previous = variant.runtimeEvents[variant.runtimeEvents.length - 1];
  if (
    previous?.type === EventType.TEXT_MESSAGE_CONTENT &&
    event.type === EventType.TEXT_MESSAGE_CONTENT &&
    previous.messageId === event.messageId
  ) {
    previous.delta += event.delta;
    return;
  }
  if (
    previous?.type === EventType.REASONING_MESSAGE_CONTENT &&
    event.type === EventType.REASONING_MESSAGE_CONTENT &&
    previous.messageId === event.messageId
  ) {
    previous.delta += event.delta;
    return;
  }
  if (
    previous?.type === EventType.TOOL_CALL_ARGS &&
    event.type === EventType.TOOL_CALL_ARGS &&
    previous.toolCallId === event.toolCallId
  ) {
    previous.delta += event.delta;
    return;
  }
  variant.runtimeEvents.push(clone(event));
}

function isUserMessage(
  item: ChatTimelineItem,
): item is ChatTimelineMessageItem {
  return item.type === 'message' && item.role === 'user';
}

function isAssistantMessage(
  item: ChatTimelineItem,
): item is ChatTimelineMessageItem {
  return item.type === 'message' && item.role === 'assistant';
}

function visibleText(item: ChatTimelineMessageItem) {
  return item.parts
    .filter((part) => part.type === 'text')
    .map((part) => part.content)
    .join('');
}

function markRoundCompleted(
  items: ChatTimelineItem[],
  round: AgentTryoutRawRound,
  variant: AgentTryoutRawVariant,
) {
  const userIndex = items.findIndex(
    (item) => isUserMessage(item) && item.roundId === round.roundId,
  );
  const nextUserIndex = items.findIndex(
    (item, index) => index > userIndex && isUserMessage(item),
  );
  const source = items.slice(
    userIndex + 1,
    nextUserIndex === -1 ? items.length : nextUserIndex,
  );
  const latest = [...source].reverse().find((item) => isAssistantMessage(item));
  if (!latest) return;
  latest.roundCompleted = true;
  latest.status = latest.status === 'error' ? 'error' : 'done';
  latest.regenerable = true;
  latest.switchable = round.variants.length > 1;
  latest.variantCount = round.variants.length;
  latest.variantIndex = variant.variantIndex;
  latest.selectedVariantIndex = round.selectedVariantIndex;
}

function sortedRounds(rounds: Map<string, AgentTryoutRawRound>) {
  return [...rounds.values()].sort(
    (first, second) =>
      (first.createdAt || first.updatedAt) -
      (second.createdAt || second.updatedAt),
  );
}

export function useAgentTryoutRawRounds(options: {
  mode: 'draft';
  sessionId: string;
}) {
  const rounds = new Map<string, AgentTryoutRawRound>();
  const liveProjectionStates = new Map<
    string,
    ReturnType<typeof createAguiTimelineProjectionState>
  >();
  let persistTimer: ReturnType<typeof setTimeout> | undefined;

  for (const round of restoreSession(options.mode, options.sessionId)) {
    rounds.set(round.roundId, round);
  }

  function persistNow() {
    if (persistTimer) {
      clearTimeout(persistTimer);
      persistTimer = undefined;
    }
    const overflow = rounds.size - MAX_ROUNDS;
    if (overflow > 0) {
      for (const round of sortedRounds(rounds).slice(0, overflow)) {
        rounds.delete(round.roundId);
      }
    }
    persistSession(options.mode, options.sessionId, [...rounds.values()]);
  }

  function schedulePersist() {
    if (persistTimer) return;
    persistTimer = setTimeout(persistNow, PERSIST_DEBOUNCE_MS);
  }

  function clear() {
    rounds.clear();
    liveProjectionStates.clear();
    if (persistTimer) clearTimeout(persistTimer);
    persistTimer = undefined;
    removeStoredSession(options.mode, options.sessionId);
  }

  function createRound(
    prompt: string,
    images: ChatImageAttachment[] = [],
    documents: ChatDocumentAttachment[] = [],
  ) {
    const now = Date.now();
    const roundId = createRoundId();
    rounds.set(roundId, {
      createdAt: now,
      documents: clone(documents),
      images: clone(images),
      prompt,
      roundId,
      selectedVariantIndex: 1,
      status: 'running',
      updatedAt: now,
      variants: [createVariant(1)],
    });
    persistNow();
    return roundId;
  }

  function regenerateRound(roundId: string) {
    const round = rounds.get(roundId);
    if (!round) return undefined;
    round.variants.push(createVariant(round.variants.length + 1));
    if (round.variants.length > MAX_VARIANTS) {
      round.variants.splice(0, round.variants.length - MAX_VARIANTS);
      round.variants.forEach((variant, index) => {
        variant.variantIndex = index + 1;
      });
    }
    round.selectedVariantIndex = round.variants.length;
    round.status = 'running';
    round.updatedAt = Date.now();
    persistNow();
    return round.roundId;
  }

  function getPrompt(roundId?: string) {
    return roundId ? rounds.get(roundId)?.prompt || '' : '';
  }

  function currentVariant(roundId: string) {
    const round = rounds.get(roundId);
    return round ? selectedVariant(round) : undefined;
  }

  function bindRun(roundId: string, runId: string) {
    const round = rounds.get(roundId);
    const variant = round && selectedVariant(round);
    if (!round || !variant) return;
    variant.runId = runId;
    variant.lastCursor = 0;
    variant.status = 'running';
    round.status = 'running';
    variant.updatedAt = Date.now();
    round.updatedAt = variant.updatedAt;
    persistNow();
  }

  function recoverableRun() {
    const round = [...sortedRounds(rounds)].reverse().find((candidate) => {
      const variant = selectedVariant(candidate);
      return candidate.status === 'running' && Boolean(variant?.runId);
    });
    const variant = round && selectedVariant(round);
    return round && variant?.runId
      ? {
          inputAccepted: variant.inputAccepted,
          lastCursor: variant.lastCursor,
          roundId: round.roundId,
          runId: variant.runId,
          startedAt: variant.createdAt,
        }
      : undefined;
  }

  function isInputAccepted(roundId: string) {
    const round = rounds.get(roundId);
    return Boolean(round && selectedVariant(round)?.inputAccepted);
  }

  function updateCursor(roundId: string, cursor: number) {
    const round = rounds.get(roundId);
    const variant = round && selectedVariant(round);
    if (!round || !variant || !Number.isSafeInteger(cursor) || cursor <= 0) {
      return;
    }
    variant.lastCursor = Math.max(variant.lastCursor, cursor);
    variant.updatedAt = Date.now();
    round.updatedAt = variant.updatedAt;
    schedulePersist();
  }

  function resetRuntimeEvents(roundId: string) {
    const round = rounds.get(roundId);
    const variant = round && selectedVariant(round);
    if (!round || !variant) return;
    variant.runtimeEvents = [];
    variant.lastCursor = 0;
    variant.status = 'running';
    round.status = 'running';
    liveProjectionStates.delete(`${roundId}:${variant.variantIndex}`);
    persistNow();
  }

  function recordEvent(roundId: string, event: AgentTryoutRuntimeEvent) {
    const round = rounds.get(roundId);
    const variant = round && selectedVariant(round);
    if (!round || !variant) return undefined;
    appendRuntimeEvent(variant, event);
    const inputAcceptedEvent =
      event.type === EventType.CUSTOM &&
      event.name === easyFlowAguiCustomEvent.inputAccepted;
    if (inputAcceptedEvent) {
      variant.inputAccepted = true;
    }
    if (event.type === EventType.RUN_FINISHED) {
      variant.status = 'completed';
      round.status = 'completed';
    } else if (event.type === EventType.RUN_ERROR) {
      variant.status = 'error';
      round.status = 'error';
    }
    variant.updatedAt = Date.now();
    round.updatedAt = variant.updatedAt;
    if (
      event.type === EventType.RUN_FINISHED ||
      event.type === EventType.RUN_ERROR ||
      inputAcceptedEvent
    ) {
      persistNow();
    } else {
      schedulePersist();
    }
    return event;
  }

  function projectEvent(
    items: ChatTimelineItem[],
    roundId: string,
    event: AgentTryoutRuntimeEvent,
    onInputAccepted?: (
      payload: Record<string, unknown>,
    ) => Promise<void> | void,
  ) {
    const round = rounds.get(roundId);
    const variant = round && selectedVariant(round);
    if (!round || !variant) return;
    const key = `${roundId}:${variant.variantIndex}`;
    let state = liveProjectionStates.get(key);
    if (!state) {
      state = createAguiTimelineProjectionState(variant.createdAt);
      liveProjectionStates.set(key, state);
    }
    applyAguiEventToTimeline(
      items,
      event,
      { onInputAccepted, roundId, startedAt: variant.createdAt },
      state,
    );
  }

  function completeRound(roundId: string) {
    const round = rounds.get(roundId);
    const variant = round && selectedVariant(round);
    if (!round || !variant || variant.status === 'error') {
      persistNow();
      return;
    }
    variant.status = 'completed';
    round.status = 'completed';
    variant.updatedAt = Date.now();
    round.updatedAt = variant.updatedAt;
    persistNow();
  }

  function failRound(roundId: string) {
    const round = rounds.get(roundId);
    const variant = round && selectedVariant(round);
    if (!round || !variant) {
      persistNow();
      return;
    }
    variant.status = 'error';
    round.status = 'error';
    variant.updatedAt = Date.now();
    round.updatedAt = variant.updatedAt;
    persistNow();
  }

  function buildTimelineItems() {
    const items: ChatTimelineItem[] = [];
    for (const round of sortedRounds(rounds)) {
      ChatTimelineBuilder.appendUserMessage(items, round.prompt, {
        documents: round.documents,
        id: `user-${round.roundId}`,
        images: round.images,
        roundId: round.roundId,
      });
      const variant = selectedVariant(round);
      if (!variant) continue;
      const state = createAguiTimelineProjectionState(variant.createdAt);
      ChatTimelineBuilder.ensureAssistantTurn(items, {
        id: `turn-${round.roundId}`,
        roundId: round.roundId,
        turnStartedAt: variant.createdAt,
      });
      for (const event of variant.runtimeEvents) {
        applyAguiEventToTimeline(
          items,
          event,
          {
            finishedAt: variant.updatedAt,
            roundId: round.roundId,
            startedAt: variant.createdAt,
          },
          state,
        );
      }
      if (variant.status !== 'running') {
        ChatTimelineBuilder.finalize(items, {
          roundCompleted: variant.status === 'completed',
          roundId: round.roundId,
          turnFinishedAt: variant.updatedAt,
          turnStartedAt: variant.createdAt,
          turnSucceeded: variant.status === 'completed',
        });
        markRoundCompleted(items, round, variant);
      }
    }
    return items;
  }

  function selectVariant(roundId: string, direction: 'next' | 'previous') {
    const round = rounds.get(roundId);
    if (!round) return;
    const next =
      direction === 'previous'
        ? round.selectedVariantIndex - 1
        : round.selectedVariantIndex + 1;
    if (next < 1 || next > round.variants.length) return;
    round.selectedVariantIndex = next;
    round.updatedAt = Date.now();
    persistNow();
  }

  function canSwitch(
    item: ChatTimelineMessageItem,
    direction: 'next' | 'previous',
  ) {
    const current = Number(item.selectedVariantIndex || item.variantIndex || 1);
    const total = Number(item.variantCount || 1);
    return direction === 'previous' ? current > 1 : current < total;
  }

  return {
    bindRun,
    buildTimelineItems,
    canSwitch,
    clear,
    completeRound,
    copyText: visibleText,
    createRound,
    currentVariant,
    failRound,
    getPrompt,
    isInputAccepted,
    projectEvent,
    recoverableRun,
    recordEvent,
    resetRuntimeEvents,
    regenerateRound,
    selectVariant,
    updateCursor,
    flush: persistNow,
  };
}

export type {
  AgentTryoutRawRound,
  AgentTryoutRawVariant,
  AgentTryoutRuntimeEvent,
};
