import type {
  SessionTreeEntry,
  SessionTreeSnapshot,
  SubagentEntryProjection,
  SessionTreeSubagentResultMetadata,
  SubagentEvidenceSummaryProjection,
  SubagentProcessEventProjection,
  SubagentTaskPanelProjection,
  SubagentToolExecutionProjection,
} from './types.js';

interface SubagentResultRecord {
  task?: Record<string, unknown>;
  result: Record<string, unknown>;
}

type SubagentProjectionInputSource = 'subagent_result' | 'tool_message' | 'message_metadata' | 'entry_metadata';

interface SubagentResultCollection {
  records: SubagentResultRecord[];
  selectedSource: SubagentProjectionInputSource;
  inputSources: SubagentProjectionInputSource[];
  ignoredSources: SubagentProjectionInputSource[];
  rawResultCount: number;
  dedupedResultCount: number;
  skippedResultCount: number;
  fallbackUsed: boolean;
}

interface SessionTreeSubagentResultContentProjection {
  results?: unknown[];
}

interface NormalizedSubagentResultMetadata {
  finalResults: unknown[];
  evidenceSummaries: unknown[];
  processEvents: unknown[];
}

const SUBAGENT_PROJECTION_LIMITS = {
  evidenceSummaries: 8,
  processEvents: 50,
  taskProcessEvents: 50,
  toolExecutions: 24,
} as const;

export function projectSubagentSnapshot(snapshot: SessionTreeSnapshot): SessionTreeSnapshot {
  const projectedEntries = projectSubagentEntries(snapshot.entries);
  const projectedById = new Map(projectedEntries.map(entry => [entry.id, entry]));
  return {
    ...snapshot,
    entries: projectedEntries,
    activePath: snapshot.activePath.map(entry => projectedById.get(entry.id) ?? entry),
  };
}

export function projectSubagentEntries(entries: SessionTreeEntry[]): SessionTreeEntry[] {
  let changed = false;
  const projected = entries.map(entry => {
    const next = projectSubagentEntry(entry);
    if (next !== entry) {
      changed = true;
    }
    return next;
  });
  return changed ? projected : entries;
}

export function projectSubagentEntry(entry: SessionTreeEntry): SessionTreeEntry {
  const projection = buildSubagentProjectionForEntry(entry);
  if (!projection) {
    return entry;
  }

  return {
    ...entry,
    metadata: {
      ...(entry.metadata ?? {}),
      subagentProjection: projection,
    },
  } as SessionTreeEntry;
}

export function buildSubagentProjectionForEntry(entry: SessionTreeEntry): SubagentEntryProjection | null {
  const collection = getSubagentResultRecords(entry);
  if (!collection || collection.records.length === 0) {
    return null;
  }
  const results = collection.records;

  const metadata = getNormalizedSubagentResultMetadata(entry);
  const metadataSummaries = toEvidenceSummaryArray(metadata.evidenceSummaries);
  const metadataEvents = toProcessEventArray(metadata.processEvents);
  const taskPanels = results
    .map((item, index) => toSubagentTaskPanel(item.result, index, item.task))
    .filter((item): item is SubagentTaskPanelProjection => Boolean(item))
    .sort((a, b) => (a.sortOrder ?? Number.MAX_SAFE_INTEGER) - (b.sortOrder ?? Number.MAX_SAFE_INTEGER));
  const resultSummaries = taskPanels.flatMap(panel => panel.evidenceSummaries);
  const resultEvents = results.flatMap(item => {
    const resultPayload = getRecord(item.result.resultPayload);
    return toProcessEventArray(resultPayload?.processEvents);
  });
  const rawEvidenceSummaries = [...metadataSummaries, ...resultSummaries];
  const rawProcessEvents = [...metadataEvents, ...resultEvents];

  return {
    version: 1,
    source: 'backend-projection',
    replay: {
      limits: SUBAGENT_PROJECTION_LIMITS,
      truncated: {
        evidenceSummaries: rawEvidenceSummaries.length > SUBAGENT_PROJECTION_LIMITS.evidenceSummaries,
        processEvents: rawProcessEvents.length > SUBAGENT_PROJECTION_LIMITS.processEvents,
        taskPanels: false,
        toolExecutions: taskPanels.some(panel => panel.toolExecutionsTruncated),
      },
    },
    evidenceSummaries: rawEvidenceSummaries.slice(0, SUBAGENT_PROJECTION_LIMITS.evidenceSummaries),
    processEvents: rawProcessEvents.slice(0, SUBAGENT_PROJECTION_LIMITS.processEvents),
    taskPanels,
    diagnostics: {
      projectionPath: 'canonical-backend-projection',
      selectedSource: collection.selectedSource,
      inputSources: collection.inputSources,
      ignoredSources: collection.ignoredSources,
      rawResultCount: collection.rawResultCount,
      dedupedResultCount: collection.dedupedResultCount,
      projectedTaskCount: taskPanels.length,
      skippedResultCount: collection.skippedResultCount,
      fallbackUsed: collection.fallbackUsed,
    },
  };
}

function toSubagentTaskPanel(
  value: unknown,
  index: number,
  taskValue?: Record<string, unknown>
): SubagentTaskPanelProjection | null {
  const record = getRecord(value);
  if (!record) {
    return null;
  }

  const task = taskValue ?? {};
  const resultPayload = getRecord(record.resultPayload) ?? {};
  const evidenceSummaries = [
    ...toEvidenceSummaryArray(resultPayload.evidenceSummary ? [resultPayload.evidenceSummary] : []),
    ...toEvidenceSummaryArray(resultPayload.evidenceSummaries),
  ].slice(0, SUBAGENT_PROJECTION_LIMITS.evidenceSummaries);
  const rawProcessEvents = toProcessEventArray(resultPayload.processEvents)
    .slice(0, SUBAGENT_PROJECTION_LIMITS.taskProcessEvents);
  const toolExecutionProjection = buildSubagentToolExecutionProjection(rawProcessEvents);
  const processEvents = rawProcessEvents.filter((_, eventIndex) => {
    return !toolExecutionProjection.consumedEventIndexes.has(eventIndex);
  });
  const sortOrder = typeof record.sortOrder === 'number'
    ? record.sortOrder
    : typeof task.sortOrder === 'number'
      ? task.sortOrder
      : index;
  const summary = typeof record.summary === 'string'
    ? record.summary
    : typeof task.summary === 'string'
      ? task.summary
      : undefined;
  const goal = typeof record.goal === 'string'
    ? record.goal
    : typeof task.goal === 'string'
      ? task.goal
      : undefined;
  const name = typeof record.name === 'string'
    ? record.name
    : typeof task.name === 'string'
      ? task.name
      : typeof task.agentName === 'string'
        ? task.agentName
        : undefined;
  const error = typeof record.error === 'string' ? record.error : undefined;
  const firstRunId = processEvents.find(event => typeof event.runId === 'string' && event.runId.trim())?.runId;
  const title = goal?.trim() || name?.trim() || summary?.trim() || `任务 #${sortOrder + 1}`;

  return {
    key: String(record.id ?? task.id ?? `${sortOrder}:${title}`),
    title,
    status: typeof record.status === 'string' ? record.status : undefined,
    sortOrder,
    runId: firstRunId,
    summary,
    error,
    evidenceSummaries,
    processEvents,
    toolExecutions: toolExecutionProjection.executions,
    toolExecutionsTruncated: toolExecutionProjection.truncated,
    result: record,
  };
}

function buildSubagentToolExecutionProjection(events: SubagentProcessEventProjection[]): {
  executions: SubagentToolExecutionProjection[];
  truncated: boolean;
  consumedEventIndexes: Set<number>;
} {
  const executions = new Map<string, SubagentToolExecutionProjection>();
  const orderedKeys: string[] = [];
  const consumedEventIndexes = new Set<number>();

  for (const [index, event] of events.entries()) {
    if (event.type === 'run_error') {
      const matched = matchExecutionForRunError(executions, orderedKeys, event);
      if (matched) {
        matched.error = typeof event.error === 'string' ? event.error : matched.error;
        matched.finishedAt = typeof event.timestamp === 'string' ? event.timestamp : matched.finishedAt;
        matched.status = 'completed';
        consumedEventIndexes.add(index);
      }
      continue;
    }

    if (event.type !== 'tool_call' && event.type !== 'proxy_tool_call' && event.type !== 'tool_result') {
      continue;
    }
    consumedEventIndexes.add(index);

    const key = String(event.toolCallId || `${event.runId || 'run'}:${event.name || event.label || 'tool'}:${index}`);
    if (!executions.has(key)) {
      executions.set(key, {
        key,
        toolCallId: typeof event.toolCallId === 'string' ? event.toolCallId : undefined,
        name: typeof event.name === 'string' ? event.name : undefined,
        label: typeof event.label === 'string' ? event.label : undefined,
        kind: event.type === 'proxy_tool_call' ? 'proxy_tool_call' : 'tool_call',
        status: event.type === 'tool_result' ? 'completed' : 'running',
        runId: typeof event.runId === 'string' ? event.runId : undefined,
        startedAt: typeof event.timestamp === 'string' ? event.timestamp : undefined,
      });
      orderedKeys.push(key);
    }

    const execution = executions.get(key)!;
    if (event.type === 'tool_call' || event.type === 'proxy_tool_call') {
      execution.kind = event.type === 'proxy_tool_call' ? 'proxy_tool_call' : 'tool_call';
      execution.name = execution.name || (typeof event.name === 'string' ? event.name : undefined);
      execution.label = execution.label || (typeof event.label === 'string' ? event.label : undefined);
      execution.args = event.args;
      execution.startedAt = execution.startedAt || (typeof event.timestamp === 'string' ? event.timestamp : undefined);
      continue;
    }

    execution.status = 'completed';
    execution.result = event.result;
    execution.error = typeof event.error === 'string' ? event.error : execution.error;
    execution.finishedAt = typeof event.timestamp === 'string' ? event.timestamp : execution.finishedAt;
  }

  return {
    executions: orderedKeys.map(key => executions.get(key)!).slice(0, SUBAGENT_PROJECTION_LIMITS.toolExecutions),
    truncated: orderedKeys.length > SUBAGENT_PROJECTION_LIMITS.toolExecutions,
    consumedEventIndexes,
  };
}

function getSubagentResultRecords(entry: SessionTreeEntry): SubagentResultCollection | null {
  if (entry.type === 'subagent_result') {
    const metadata = getNormalizedSubagentResultMetadata(entry);
    const sourceResults = metadata.finalResults.length > 0
      ? metadata.finalResults
      : Array.isArray(((entry.content || {}) as SessionTreeSubagentResultContentProjection).results)
        ? (((entry.content || {}) as SessionTreeSubagentResultContentProjection).results as unknown[])
        : [];
    const content = (entry.content || {}) as SessionTreeSubagentResultContentProjection;
    const results = sourceResults.length > 0 ? sourceResults : (Array.isArray(content.results) ? content.results : []);
    const records = results
      .filter((item): item is Record<string, unknown> => Boolean(item) && typeof item === 'object' && !Array.isArray(item))
      .map(result => ({ result }));
    return {
      records,
      selectedSource: 'subagent_result',
      inputSources: ['subagent_result'],
      ignoredSources: [],
      rawResultCount: records.length,
      dedupedResultCount: records.length,
      skippedResultCount: 0,
      fallbackUsed: false,
    };
  }

  if (entry.type !== 'message') {
    return null;
  }

  const payloads: Array<{ source: SubagentProjectionInputSource; payload: { taskResults: unknown[] } }> = [];
  if (entry.message.role === 'tool') {
    const payload = parseDelegatedToolPayload(entry.message.content);
    if (payload) {
      payloads.push({ source: 'tool_message', payload });
    }
  }

  const messageMetadataPayloads = getDelegatedToolPayloadsFromMessageMetadata(entry.message.metadata);
  const entryMetadataPayloads = getDelegatedToolPayloadsFromMessageMetadata(entry.metadata);
  payloads.push(...messageMetadataPayloads.map(payload => ({ source: 'message_metadata' as const, payload })));
  if (messageMetadataPayloads.length === 0) {
    payloads.push(...entryMetadataPayloads.map(payload => ({ source: 'entry_metadata' as const, payload })));
  }

  if (payloads.length === 0) {
    return null;
  }

  const inputSources = Array.from(new Set([
    ...(entry.message.role === 'tool' && payloads.some(item => item.source === 'tool_message') ? ['tool_message' as const] : []),
    ...(messageMetadataPayloads.length > 0 ? ['message_metadata' as const] : []),
    ...(entryMetadataPayloads.length > 0 ? ['entry_metadata' as const] : []),
  ]));
  const selectedSource = payloads[0].source;
  const ignoredSources = inputSources.filter(source => source !== selectedSource);
  const rawItems = payloads
    .filter(item => item.source === selectedSource)
    .flatMap(item => item.payload.taskResults);
  const records: Array<SubagentResultRecord | null> = rawItems
    .map(item => {
      const record = getRecord(item);
      if (!record) {
        return null;
      }
      const task = getRecord(record.task) ?? undefined;
      const result = getRecord(record.result) ?? undefined;
      return result ? { task, result } : null;
    });
  const validRecords = records.filter((item): item is SubagentResultRecord => Boolean(item));
  const dedupedRecords = dedupeSubagentResultRecords(validRecords);
  return {
    records: dedupedRecords,
    selectedSource,
    inputSources,
    ignoredSources,
    rawResultCount: rawItems.length,
    dedupedResultCount: dedupedRecords.length,
    skippedResultCount: rawItems.length - validRecords.length,
    fallbackUsed: selectedSource === 'entry_metadata',
  };
}

function getSubagentProjectionMetadata(entry: SessionTreeEntry): SessionTreeSubagentResultMetadata {
  return (entry.metadata ?? {}) as SessionTreeSubagentResultMetadata;
}

function getNormalizedSubagentResultMetadata(entry: SessionTreeEntry): NormalizedSubagentResultMetadata {
  const metadata = getSubagentProjectionMetadata(entry);
  return {
    finalResults: Array.isArray(metadata.finalResults) ? metadata.finalResults : [],
    evidenceSummaries: Array.isArray(metadata.evidenceSummaries) ? metadata.evidenceSummaries : [],
    processEvents: Array.isArray(metadata.processEvents) ? metadata.processEvents : [],
  };
}

function getDelegatedToolPayloadsFromMessageMetadata(metadata: unknown): Array<{ taskResults: unknown[] }> {
  const parsed = parseJsonLikeRecord(metadata);
  if (!parsed || !Array.isArray(parsed.skillExecutions)) {
    return [];
  }

  return parsed.skillExecutions.flatMap(item => {
    const record = getRecord(item);
    if (!record) {
      return [];
    }
    const name = typeof record.name === 'string'
      ? record.name
      : typeof record.toolName === 'string'
        ? record.toolName
        : '';
    if (name !== 'delegate_parallel' && name !== 'delegate_task') {
      return [];
    }
    const rawResult = getRecord(record.rawResult) ?? {};
    const candidates = [
      record.result,
      record.rawResult,
      rawResult.data,
      rawResult.json,
      rawResult.text,
    ];
    return candidates
      .map(candidate => parseDelegatedToolPayload(candidate))
      .filter((payload): payload is { taskResults: unknown[] } => Boolean(payload));
  });
}

function parseDelegatedToolPayload(value: unknown): { taskResults: unknown[] } | null {
  const parsed = parseJsonLikeRecord(value);
  if (!parsed) {
    return null;
  }
  if (parsed.delegationMode !== 'session-subagent') {
    return null;
  }
  if (!Array.isArray(parsed.taskResults)) {
    return null;
  }
  return parsed as { taskResults: unknown[] };
}

function parseJsonLikeRecord(value: unknown): Record<string, unknown> | null {
  const record = getRecord(value);
  if (record) {
    return record;
  }
  if (typeof value !== 'string' || !value.trim()) {
    return null;
  }
  try {
    return getRecord(JSON.parse(value));
  } catch {
    return null;
  }
}

function toEvidenceSummaryArray(value: unknown): SubagentEvidenceSummaryProjection[] {
  if (!Array.isArray(value)) {
    return [];
  }

  return value.filter((item): item is SubagentEvidenceSummaryProjection => {
    const record = getRecord(item);
    return Boolean(record)
      && typeof record!.kind === 'string'
      && typeof record!.title === 'string'
      && typeof record!.summary === 'string';
  });
}

function toProcessEventArray(value: unknown): SubagentProcessEventProjection[] {
  if (!Array.isArray(value)) {
    return [];
  }

  return value
    .filter(item => Boolean(getRecord(item)))
    .map(item => ({ ...(item as Record<string, unknown>) } as SubagentProcessEventProjection))
    .filter(item => typeof item.type === 'string' && item.type.trim().length > 0);
}

function dedupeSubagentResultRecords(records: SubagentResultRecord[]): SubagentResultRecord[] {
  const seen = new Set<string>();
  const unique: SubagentResultRecord[] = [];

  for (const record of records) {
    const key = subagentResultRecordKey(record);
    if (seen.has(key)) {
      continue;
    }
    seen.add(key);
    unique.push(record);
  }

  return unique;
}

function subagentResultRecordKey(record: SubagentResultRecord): string {
  const resultId = record.result.id;
  if (typeof resultId === 'string' || typeof resultId === 'number') {
    return `result:${resultId}`;
  }

  const task = record.task ?? {};
  const sortOrder = typeof record.result.sortOrder === 'number'
    ? record.result.sortOrder
    : typeof task.sortOrder === 'number'
      ? task.sortOrder
      : '';
  const goal = typeof record.result.goal === 'string'
    ? record.result.goal
    : typeof task.goal === 'string'
      ? task.goal
      : '';
  const summary = typeof record.result.summary === 'string' ? record.result.summary : '';
  return `task:${sortOrder}:${goal}:${summary}`;
}

function matchExecutionForRunError(
  executions: Map<string, SubagentToolExecutionProjection>,
  orderedKeys: string[],
  event: SubagentProcessEventProjection
): SubagentToolExecutionProjection | undefined {
  if (typeof event.toolCallId === 'string' && event.toolCallId.trim()) {
    return executions.get(event.toolCallId);
  }

  for (let index = orderedKeys.length - 1; index >= 0; index -= 1) {
    const execution = executions.get(orderedKeys[index]);
    if (!execution) {
      continue;
    }
    if (execution.status === 'completed') {
      continue;
    }
    if (event.runId && execution.runId && execution.runId !== event.runId) {
      continue;
    }
    return execution;
  }

  return undefined;
}

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