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

interface AgentTryoutRuntimeEvent {
  createdAt: number;
  domain: string;
  payload: Record<string, unknown>;
  type: string;
}

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

interface AgentTryoutRawVariant {
  createdAt: number;
  runtimeEvents: AgentTryoutRuntimeEvent[];
  status: AgentTryoutRoundStatus;
  updatedAt: number;
  variantIndex: number;
}

interface AgentTryoutRawRound {
  createdAt: number;
  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 = 1;
const MAX_ROUNDS = 50;
const MAX_VARIANTS = 10;
const STORAGE_PREFIX = 'easyflow:agent-tryout-raw-rounds';
const PERSIST_DEBOUNCE_MS = 300;
const memorySessions = new Map<string, AgentTryoutRawRound[]>();

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

function asText(value: unknown) {
  return value === null || value === undefined ? '' : String(value);
}

function asRecord(value: unknown): Record<string, unknown> {
  return value && typeof value === 'object' && !Array.isArray(value)
    ? (value as Record<string, unknown>)
    : {};
}

function asBoolean(value: unknown) {
  if (typeof value === 'boolean') {
    return value;
  }
  if (typeof value === 'string') {
    return value.toLowerCase() === 'true';
  }
  return Boolean(value);
}

function normalizeToolName(value: unknown) {
  return asText(value).trim().toLowerCase();
}

function isHiddenToolName(value: unknown) {
  const normalizedName = normalizeToolName(value);
  return (
    normalizedName === 'retrieve_knowledge' ||
    normalizedName === 'context_reload' ||
    normalizedName === '__fragment__'
  );
}

function clone<T>(value: T): T {
  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,
    runtimeEvents: [],
    status: 'running',
    updatedAt: now,
    variantIndex,
  };
}

function normalizeRuntimeEvent(
  value: any,
): AgentTryoutRuntimeEvent | undefined {
  if (!value || typeof value !== 'object') {
    return undefined;
  }
  const domain = asText(value.domain).toUpperCase();
  const type = asText(value.type).toUpperCase();
  if (!domain || !type) {
    return undefined;
  }
  return {
    createdAt: Number(value.createdAt || Date.now()),
    domain,
    payload: asRecord(value.payload),
    type,
  };
}

function normalizeVariant(value: any, index: number) {
  if (!value || typeof value !== 'object') {
    return createVariant(index);
  }
  const runtimeEvents = Array.isArray(value.runtimeEvents)
    ? value.runtimeEvents
        .map((item: any) => normalizeRuntimeEvent(item))
        .filter(
          (
            item: AgentTryoutRuntimeEvent | undefined,
          ): item is AgentTryoutRuntimeEvent => Boolean(item),
        )
    : [];
  return {
    createdAt: Number(value.createdAt || Date.now()),
    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 = asText(value.prompt);
  const roundId = asText(value.roundId);
  const images = Array.isArray(value.images) ? value.images.slice(0, 5) : [];
  if ((!prompt && images.length === 0) || !roundId) {
    return undefined;
  }
  const variants = Array.isArray(value.variants)
    ? value.variants
        .slice(-MAX_VARIANTS)
        .map((item: any, 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()),
    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) {
    return memoryRecords.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 => Boolean(item))
      : [];
    memorySessions.set(
      key,
      rounds.map((item) => clone(item)),
    );
    return rounds;
  } catch {
    return [];
  }
}

function persistSession(
  mode: string,
  sessionId: string,
  rounds: AgentTryoutRawRound[],
) {
  const key = storageKey(mode, sessionId);
  const snapshot: AgentTryoutRawSessionRecord = {
    rounds: rounds.slice(-MAX_ROUNDS).map((item) => clone(item)),
    sessionId,
    version: STORAGE_VERSION,
  };
  memorySessions.set(
    key,
    snapshot.rounds.map((item) => clone(item)),
  );
  const storage = safeSessionStorage();
  if (!storage) {
    return;
  }
  try {
    storage.setItem(key, JSON.stringify(snapshot));
  } catch {
    // 试运行缓存失败不影响当前聊天主流程。
  }
}

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

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

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

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 findRoundResponseRange(items: ChatTimelineItem[], roundId: string) {
  const userIndex = items.findIndex(
    (item) => isUserMessage(item) && item.roundId === roundId,
  );
  if (userIndex < 0) {
    return undefined;
  }
  const nextUserIndex = items.findIndex(
    (item, index) => index > userIndex && isUserMessage(item),
  );
  return {
    end: nextUserIndex >= 0 ? nextUserIndex : items.length,
    start: userIndex + 1,
  };
}

function assistantSegmentIndex(items: ChatTimelineItem[], roundId: string) {
  return items.filter(
    (item) => isAssistantMessage(item) && item.roundId === roundId,
  ).length;
}

function nextAssistantId(
  items: ChatTimelineItem[],
  roundId: string,
  variantIndex: number,
) {
  const last = items[items.length - 1];
  if (
    last &&
    isAssistantMessage(last) &&
    last.roundId === roundId &&
    last.status !== 'done'
  ) {
    return last.id;
  }
  return `assistant-${roundId}-${variantIndex}-${assistantSegmentIndex(items, roundId) + 1}`;
}

function normalizeAssistantPartIds(
  items: ChatTimelineItem[],
  roundId: string,
  variantIndex: number,
) {
  const segment = assistantSegmentIndex(items, roundId);
  const latest = [...items]
    .reverse()
    .find(
      (item): item is ChatTimelineMessageItem =>
        isAssistantMessage(item) && item.roundId === roundId,
    );
  if (!latest) {
    return;
  }
  latest.id = `assistant-${roundId}-${variantIndex}-${segment}`;
  latest.parts.forEach((part, index) => {
    part.id = `${part.type}-${roundId}-${variantIndex}-${segment}-${index + 1}`;
  });
}

function normalizeLatestItemId(
  items: ChatTimelineItem[],
  prefix: string,
  roundId: string,
  variantIndex: number,
) {
  const last = items[items.length - 1];
  if (!last) {
    return;
  }
  last.id = `${prefix}-${roundId}-${variantIndex}`;
}

function markRoundCompleted(
  items: ChatTimelineItem[],
  roundId: string,
  variant: AgentTryoutRawVariant,
  variantCount: number,
  selectedVariantIndex: number,
) {
  const range = findRoundResponseRange(items, roundId);
  const source = range ? items.slice(range.start, range.end) : items;
  const latest = [...source]
    .reverse()
    .find((item): item is ChatTimelineMessageItem => isAssistantMessage(item));
  if (!latest) {
    return;
  }
  latest.roundCompleted = true;
  latest.status = latest.status === 'error' ? 'error' : 'done';
  latest.regenerable = true;
  latest.switchable = variantCount > 1;
  latest.variantCount = variantCount;
  latest.variantIndex = variant.variantIndex;
  latest.selectedVariantIndex = selectedVariantIndex;
}

function normalizeKnowledgeItems(payload: Record<string, unknown>) {
  const source =
    payload.items ||
    payload.hits ||
    payload.documents ||
    payload.knowledgeResults ||
    [];
  if (!Array.isArray(source)) {
    return [];
  }
  const topLevelKnowledgeType = asText(payload.knowledgeType);
  const topLevelFaqCollection =
    payload.faqCollection === undefined
      ? topLevelKnowledgeType.toUpperCase() === 'FAQ'
      : asBoolean(payload.faqCollection);
  return source.map((item: any) => {
    const metadata = asRecord(item.metadata);
    const sourceFileName = asText(
      item.sourceFileName ?? metadata.sourceFileName,
    );
    const documentName = asText(
      item.documentName ?? item.documentTitle ?? item.title,
    );
    const chunkId = asText(item.chunkId ?? metadata.chunkId ?? item.id);
    const documentId = asText(
      item.documentId ?? metadata.documentId ?? item.id,
    );
    return {
      ...item,
      id: asText(item.id || chunkId || documentId),
      knowledgeId: asText(item.knowledgeId ?? payload.knowledgeId),
      knowledgeName: asText(item.knowledgeName ?? payload.knowledgeName),
      knowledgeType: asText(item.knowledgeType ?? payload.knowledgeType),
      faqCollection:
        item.faqCollection === undefined
          ? topLevelFaqCollection
          : asBoolean(item.faqCollection),
      documentId,
      documentName,
      chunkId,
      score: item.score ?? item.similarity,
      source: item.source,
      sourceFileName,
      sourceUri: asText(item.sourceUri ?? metadata.sourceUri),
      metadata,
      chunkContent: asText(
        item.chunkContent ?? item.content ?? item.text ?? item.summary,
      ),
      content: asText(item.content ?? item.text ?? item.summary),
      title: documentName || sourceFileName || item.source,
    } satisfies ChatTimelineKnowledgeHit;
  });
}

function statusKeyForProjection(
  payload: Record<string, unknown>,
  roundId: string,
  variantIndex: number,
  fallback = 'status',
) {
  const statusKey = asText(payload.statusKey) || fallback;
  return `${statusKey}:${roundId}:${variantIndex}`;
}

function projectEventToTimeline(
  items: ChatTimelineItem[],
  event: AgentTryoutRuntimeEvent,
  roundId: string,
  variantIndex: number,
) {
  const { domain, payload, type } = event;
  if (domain === 'LLM' && type === 'MESSAGE') {
    ChatTimelineBuilder.appendMessageDelta(items, payload.delta, {
      id: nextAssistantId(items, roundId, variantIndex),
      roundId,
    });
    normalizeAssistantPartIds(items, roundId, variantIndex);
    return;
  }
  if (domain === 'LLM' && type === 'THINKING') {
    const text = asText(payload.reasoning ?? payload.delta ?? payload.text);
    ChatTimelineBuilder.appendThinkingDelta(items, text, {
      id: nextAssistantId(items, roundId, variantIndex),
      roundId,
    });
    normalizeAssistantPartIds(items, roundId, variantIndex);
    return;
  }
  if (domain === 'TOOL' && type === 'FORM_REQUEST') {
    ChatTimelineBuilder.appendToolApproval(items, {
      expiresAt: asText(payload.expiresAt),
      input: payload.input,
      metadata: payload.metadata,
      requestId: asText(payload.requestId),
      resumeToken: asText(payload.resumeToken),
      toolCallId: asText(
        payload.toolCallId ?? payload.tool_call_id ?? payload.id,
      ),
      toolDisplayName: asText(payload.toolDisplayName),
      toolName: asText(payload.toolName),
      toolType: asText(payload.toolType),
    });
    if (items[items.length - 1]?.type === 'tool') {
      normalizeLatestItemId(items, 'tool-approval', roundId, variantIndex);
    }
    return;
  }
  if (domain === 'TOOL' && type === 'FORM_APPROVING') {
    ChatTimelineBuilder.markToolApproving(items, {
      requestId: asText(payload.requestId),
      resumeToken: asText(payload.resumeToken),
      toolCallId: asText(
        payload.toolCallId ?? payload.tool_call_id ?? payload.id,
      ),
    });
    return;
  }
  if (domain === 'TOOL' && type === 'FORM_REJECTED') {
    ChatTimelineBuilder.markToolRejected(items, {
      reason: asText(payload.reason),
      requestId: asText(payload.requestId),
      resumeToken: asText(payload.resumeToken),
      toolCallId: asText(
        payload.toolCallId ?? payload.tool_call_id ?? payload.id,
      ),
    });
    return;
  }
  if (domain === 'TOOL' && (type === 'TOOL_CALL' || type === 'TOOL_RESULT')) {
    const rawToolName = asText(payload.toolName ?? payload.name);
    const normalizedToolName = normalizeToolName(rawToolName);
    if (!normalizedToolName && type === 'TOOL_CALL') {
      return;
    }
    const displayToolName = asText(
      payload.toolDisplayName ?? rawToolName ?? '工具',
    );
    const asyncTool = payload.asyncTool === true;
    ChatTimelineBuilder.upsertToolCall(items, {
      input: payload.input ?? payload.toolInput,
      output: asyncTool
        ? (payload.summary ??
          payload.label ??
          payload.output ??
          payload.result ??
          payload.text)
        : (payload.output ?? payload.result ?? payload.text),
      status: asyncTool
        ? asyncToolTimelineStatus(payload)
        : type === 'TOOL_RESULT'
          ? 'success'
          : 'running',
      statusKey: statusKeyForProjection(
        payload,
        roundId,
        variantIndex,
        'knowledge-retrieval',
      ),
      toolCallId: asText(
        payload.toolCallId ??
          payload.taskId ??
          payload.tool_call_id ??
          payload.id,
      ),
      toolName: asyncTool
        ? displayToolName
        : isHiddenToolName(rawToolName)
          ? rawToolName
          : displayToolName,
    });
    return;
  }
  if (domain === 'BUSINESS' && type === 'CITATIONS') {
    const itemsToAppend = normalizeKnowledgeItems(payload);
    if (itemsToAppend.length > 0) {
      ChatTimelineBuilder.appendKnowledge(items, itemsToAppend);
      if (items[items.length - 1]?.type === 'knowledge') {
        normalizeLatestItemId(items, 'knowledge', roundId, variantIndex);
      }
    }
    return;
  }
  if (domain === 'BUSINESS' && type === 'STATUS') {
    if (asText(payload.statusKey) === 'memory-compression') {
      ChatTimelineBuilder.upsertMemoryCompressionStatus(items, {
        compressed:
          typeof payload.compressed === 'boolean'
            ? payload.compressed
            : undefined,
        label: asText(payload.label),
        phase: asText(payload.phase),
        status: asText(payload.status),
        statusKey: statusKeyForProjection(payload, roundId, variantIndex),
      });
      return;
    }
    if (asText(payload.statusKey) === 'knowledge-retrieval') {
      ChatTimelineBuilder.upsertKnowledgeRetrievalStatus(
        items,
        asText(payload.status) === 'running' ? 'running' : 'done',
        statusKeyForProjection(payload, roundId, variantIndex),
      );
    }
    return;
  }
  if (type === 'ERROR' || domain === 'ERROR') {
    ChatTimelineBuilder.appendError(
      items,
      payload.message ?? payload.error ?? '试运行失败',
    );
    normalizeLatestItemId(items, 'error', roundId, variantIndex);
  }
}

function asyncToolTimelineStatus(
  payload: Record<string, unknown>,
): ChatTimelineToolStatus {
  const status = asText(payload.status).toUpperCase();
  if (status === 'SUCCEEDED') return 'success';
  if (status === 'FAILED' || status === 'TIMEOUT' || status === 'CANCELLED') {
    return 'error';
  }
  return 'running';
}

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>();
  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() {
    const key = storageKey(options.mode, options.sessionId);
    memorySessions.set(
      key,
      [...rounds.values()].map((item) => clone(item)),
    );
    if (persistTimer) {
      return;
    }
    persistTimer = setTimeout(() => {
      persistNow();
    }, PERSIST_DEBOUNCE_MS);
  }

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

  function createRound(prompt: string, images: ChatImageAttachment[] = []) {
    const now = Date.now();
    const roundId = createRoundId();
    rounds.set(roundId, {
      createdAt: now,
      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;
    }
    const nextVariantIndex = Math.min(round.variants.length + 1, MAX_VARIANTS);
    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 = nextVariantIndex;
    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 recordEvent(
    roundId: string,
    event: {
      domain: string;
      payload?: Record<string, unknown>;
      type: string;
    },
  ) {
    const round = rounds.get(roundId);
    const variant = round && selectedVariant(round);
    if (!round || !variant) {
      return;
    }
    const runtimeEvent: AgentTryoutRuntimeEvent = {
      createdAt: Date.now(),
      domain: event.domain.toUpperCase(),
      payload: event.payload || {},
      type: event.type.toUpperCase(),
    };
    variant.runtimeEvents.push(runtimeEvent);
    if (runtimeEvent.domain === 'SYSTEM' && runtimeEvent.type === 'DONE') {
      variant.status = 'completed';
      round.status = 'completed';
    }
    if (runtimeEvent.type === 'ERROR' || runtimeEvent.domain === 'ERROR') {
      variant.status = 'error';
      round.status = 'error';
    }
    variant.updatedAt = Date.now();
    round.updatedAt = variant.updatedAt;
    if (
      (runtimeEvent.domain === 'SYSTEM' && runtimeEvent.type === 'DONE') ||
      runtimeEvent.type === 'ERROR' ||
      runtimeEvent.domain === 'ERROR'
    ) {
      persistNow();
      return;
    }
    schedulePersist();
  }

  function completeRound(roundId: string) {
    const round = rounds.get(roundId);
    const variant = round && selectedVariant(round);
    if (!round || !variant) {
      return;
    }
    if (variant.status === 'error' || round.status === 'error') {
      persistNow();
      return;
    }
    variant.status = 'completed';
    round.status = 'completed';
    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, {
        id: `user-${round.roundId}`,
        images: round.images,
        roundId: round.roundId,
      });
      const variant = selectedVariant(round);
      if (!variant) {
        continue;
      }
      for (const event of variant.runtimeEvents) {
        projectEventToTimeline(
          items,
          event,
          round.roundId,
          variant.variantIndex,
        );
      }
      if (variant.status === 'completed' || variant.status === 'error') {
        ChatTimelineBuilder.finalize(items);
        markRoundCompleted(
          items,
          round.roundId,
          variant,
          round.variants.length,
          round.selectedVariantIndex,
        );
      }
    }
    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;
  }

  function copyText(item: ChatTimelineMessageItem) {
    return visibleText(item);
  }

  return {
    buildTimelineItems,
    canSwitch,
    clear,
    completeRound,
    copyText,
    createRound,
    currentVariant,
    getPrompt,
    recordEvent,
    regenerateRound,
    selectVariant,
    flush: persistNow,
  };
}

export type {
  AgentTryoutRawRound,
  AgentTryoutRawVariant,
  AgentTryoutRuntimeEvent,
};
