import {
  buildSubagentProjectionForEntry,
  projectSubagentEntries,
} from '../src/modules/netaclaw/session-tree/subagent_projection.js';
import type { SessionTreeEntry } from '../src/modules/netaclaw/session-tree/types.js';

describe('session tree subagent projection', () => {
  it('projects subagent_result entries into canonical panels, evidence, and process events', () => {
    const entry: SessionTreeEntry = {
      id: 'entry-result',
      parentId: 'parent',
      timestamp: '2026-04-22T10:00:00.000Z',
      type: 'subagent_result',
      content: {
        batchId: 'batch-1',
        status: 'completed',
        parentEntryId: 'parent',
        results: [
          {
            id: 11,
            sortOrder: 0,
            status: 'completed',
            goal: '检查桌面图片',
            summary: '共有 3 张图片',
            resultPayload: {
              evidenceSummary: {
                kind: 'file-count',
                title: '图片文件统计',
                summary: '根据工具结果统计，图片文件共 3 个。',
                files: ['111.jpg', '11123.jpg', '20260417-185140.jpg'],
                count: 3,
              },
              processEvents: [
                {
                  type: 'run_start',
                  runId: 'subagent-11',
                  timestamp: '2026-04-22T10:00:01.000Z',
                },
                {
                  type: 'tool_call',
                  runId: 'subagent-11',
                  timestamp: '2026-04-22T10:00:02.000Z',
                  toolCallId: 'call-images',
                  name: 'bash',
                  label: '执行命令',
                  args: { command: 'find ~/Desktop -maxdepth 1 -type f -iname "*.jpg"' },
                },
                {
                  type: 'tool_result',
                  runId: 'subagent-11',
                  timestamp: '2026-04-22T10:00:03.000Z',
                  toolCallId: 'call-images',
                  name: 'bash',
                  label: '执行命令',
                  result: '111.jpg\n11123.jpg\n20260417-185140.jpg',
                },
              ],
            },
          },
        ],
      },
      metadata: {},
    };

    const projection = buildSubagentProjectionForEntry(entry);

    expect(projection).toBeTruthy();
    expect(projection).toMatchObject({
      version: 1,
      source: 'backend-projection',
      diagnostics: {
        projectionPath: 'canonical-backend-projection',
        selectedSource: 'subagent_result',
        inputSources: ['subagent_result'],
        rawResultCount: 1,
        dedupedResultCount: 1,
        projectedTaskCount: 1,
        skippedResultCount: 0,
        fallbackUsed: false,
      },
    });
    expect(projection?.taskPanels).toHaveLength(1);
    expect(projection?.taskPanels[0]).toMatchObject({
      key: '11',
      title: '检查桌面图片',
      status: 'completed',
      sortOrder: 0,
      summary: '共有 3 张图片',
    });
    expect(projection?.taskPanels[0].evidenceSummaries).toHaveLength(1);
    expect(projection?.taskPanels[0].toolExecutions).toHaveLength(1);
    expect(projection?.taskPanels[0].toolExecutions[0]).toMatchObject({
      toolCallId: 'call-images',
      status: 'completed',
      result: '111.jpg\n11123.jpg\n20260417-185140.jpg',
    });
    expect(projection?.evidenceSummaries).toHaveLength(1);
    expect(projection?.processEvents).toHaveLength(3);
  });

  it('prefers persisted subagent_result metadata.finalResults when present', () => {
    const entry: SessionTreeEntry = {
      id: 'entry-result-metadata-final-results',
      parentId: 'parent',
      timestamp: '2026-04-22T10:30:00.000Z',
      type: 'subagent_result',
      content: {
        batchId: 'batch-1',
        status: 'completed',
        parentEntryId: 'parent',
        results: [
          {
            id: 11,
            sortOrder: 0,
            status: 'completed',
            goal: '旧 content 任务',
            summary: '来自 content.results',
            resultPayload: {},
          },
        ],
      },
      metadata: {
        finalResults: [
          {
            id: 22,
            sortOrder: 1,
            status: 'completed',
            goal: '正式 metadata 任务',
            summary: '来自 metadata.finalResults',
            resultPayload: {},
          },
        ],
      },
    };

    const projection = buildSubagentProjectionForEntry(entry);

    expect(projection?.taskPanels).toHaveLength(1);
    expect(projection?.taskPanels[0]).toMatchObject({
      key: '22',
      title: '正式 metadata 任务',
      summary: '来自 metadata.finalResults',
      sortOrder: 1,
    });
    expect(projection?.diagnostics).toMatchObject({
      projectionPath: 'canonical-backend-projection',
      selectedSource: 'subagent_result',
      rawResultCount: 1,
      dedupedResultCount: 1,
      projectedTaskCount: 1,
      fallbackUsed: false,
    });
  });

  it('reads subagent_result top-level metadata evidence and process events through the canonical metadata path', () => {
    const entry: SessionTreeEntry = {
      id: 'entry-result-metadata-replay',
      parentId: 'parent',
      timestamp: '2026-04-22T10:40:00.000Z',
      type: 'subagent_result',
      content: {
        batchId: 'batch-metadata-replay',
        status: 'completed',
        parentEntryId: 'parent',
        results: [],
      },
      metadata: {
        finalResults: [
          {
            id: 51,
            sortOrder: 0,
            status: 'completed',
            goal: 'metadata replay task',
            summary: 'metadata result summary',
            resultPayload: {},
          },
        ],
        evidenceSummaries: [
          {
            kind: 'tool-preview',
            title: 'Metadata Evidence',
            summary: 'metadata evidence summary',
          },
        ],
        processEvents: [
          {
            type: 'run_start',
            runId: 'subagent-51',
            timestamp: '2026-04-22T10:40:01.000Z',
          },
        ],
      },
    };

    const projection = buildSubagentProjectionForEntry(entry);

    expect(projection?.taskPanels).toHaveLength(1);
    expect(projection?.taskPanels[0]).toMatchObject({
      key: '51',
      title: 'metadata replay task',
      summary: 'metadata result summary',
    });
    expect(projection?.evidenceSummaries).toEqual([
      expect.objectContaining({
        title: 'Metadata Evidence',
        summary: 'metadata evidence summary',
      }),
    ]);
    expect(projection?.processEvents).toEqual([
      expect.objectContaining({
        type: 'run_start',
        runId: 'subagent-51',
      }),
    ]);
  });

  it('projects assistant delegate_parallel metadata and ignores stale entry metadata when message metadata exists', () => {
    const freshPayload = {
      delegationMode: 'session-subagent',
      taskResults: [
        {
          task: { sortOrder: 0, goal: 'message metadata task' },
          result: {
            id: 21,
            status: 'completed',
            summary: 'from message metadata',
            resultPayload: {},
          },
        },
      ],
    };
    const stalePayload = {
      delegationMode: 'session-subagent',
      taskResults: [
        {
          task: { sortOrder: 1, goal: 'entry metadata task' },
          result: {
            id: 22,
            status: 'completed',
            summary: 'from entry metadata',
            resultPayload: {},
          },
        },
      ],
    };
    const entry: SessionTreeEntry = {
      id: 'entry-message',
      parentId: 'parent',
      timestamp: '2026-04-22T11:00:00.000Z',
      type: 'message',
      message: {
        role: 'assistant',
        content: 'done',
        metadata: {
          skillExecutions: [
            {
              name: 'delegate_parallel',
              result: freshPayload,
            },
          ],
        },
      },
      metadata: {
        skillExecutions: [
          {
            name: 'delegate_parallel',
            result: stalePayload,
          },
        ],
      },
    };

    const projection = buildSubagentProjectionForEntry(entry);

    expect(projection?.taskPanels).toHaveLength(1);
    expect(projection?.taskPanels[0]).toMatchObject({
      key: '21',
      title: 'message metadata task',
      summary: 'from message metadata',
    });
    expect(projection?.diagnostics).toMatchObject({
      projectionPath: 'canonical-backend-projection',
      selectedSource: 'message_metadata',
      inputSources: ['message_metadata', 'entry_metadata'],
      rawResultCount: 1,
      dedupedResultCount: 1,
      projectedTaskCount: 1,
      skippedResultCount: 0,
      fallbackUsed: false,
      ignoredSources: ['entry_metadata'],
    });
  });

  it('exposes projection diagnostics for fallback and dedupe decisions', () => {
    const payload = {
      delegationMode: 'session-subagent',
      taskResults: [
        {
          task: { sortOrder: 0, goal: 'fallback task' },
          result: {
            id: 41,
            status: 'completed',
            summary: 'first',
            resultPayload: {},
          },
        },
        {
          task: { sortOrder: 0, goal: 'fallback task duplicate' },
          result: {
            id: 41,
            status: 'completed',
            summary: 'duplicate',
            resultPayload: {},
          },
        },
        {
          task: { sortOrder: 2, goal: 'invalid task' },
          result: null,
        },
      ],
    };
    const entry: SessionTreeEntry = {
      id: 'entry-fallback-diagnostics',
      parentId: 'parent',
      timestamp: '2026-04-22T11:30:00.000Z',
      type: 'message',
      message: {
        role: 'assistant',
        content: 'done',
        metadata: {},
      },
      metadata: {
        skillExecutions: [
          {
            name: 'delegate_parallel',
            result: payload,
          },
        ],
      },
    };

    const projection = buildSubagentProjectionForEntry(entry);

    expect(projection?.taskPanels).toHaveLength(1);
    expect(projection?.diagnostics).toMatchObject({
      projectionPath: 'canonical-backend-projection',
      selectedSource: 'entry_metadata',
      inputSources: ['entry_metadata'],
      rawResultCount: 3,
      dedupedResultCount: 1,
      projectedTaskCount: 1,
      skippedResultCount: 1,
      fallbackUsed: true,
      ignoredSources: [],
    });
  });

  it('returns entries with metadata.subagentProjection without mutating originals', () => {
    const entry: SessionTreeEntry = {
      id: 'entry-result',
      parentId: 'parent',
      timestamp: '2026-04-22T12:00:00.000Z',
      type: 'subagent_result',
      content: {
        batchId: 'batch-2',
        status: 'completed',
        parentEntryId: 'parent',
        results: [
          {
            id: 31,
            status: 'completed',
            summary: 'done',
            resultPayload: {},
          },
        ],
      },
    };

    const projected = projectSubagentEntries([entry]);

    expect(projected).not.toBe([entry]);
    expect(projected[0]).not.toBe(entry);
    expect(entry.metadata).toBeUndefined();
    expect(projected[0].metadata?.subagentProjection).toMatchObject({
      version: 1,
      source: 'backend-projection',
      taskPanels: [
        {
          key: '31',
          summary: 'done',
        },
      ],
    });
  });

  it('exposes replay limits and truncation state when projection data is clipped', () => {
    const entry: SessionTreeEntry = {
      id: 'entry-clipped-result',
      parentId: 'parent',
      timestamp: '2026-04-22T13:00:00.000Z',
      type: 'subagent_result',
      content: {
        batchId: 'batch-clipped',
        status: 'completed',
        parentEntryId: 'parent',
        results: Array.from({ length: 10 }, (_, index) => ({
          id: `result-${index}`,
          sortOrder: index,
          status: 'completed',
          summary: `summary ${index}`,
          resultPayload: {
            evidenceSummary: {
              kind: 'tool-preview',
              title: `Evidence ${index}`,
              summary: `Evidence summary ${index}`,
            },
            processEvents: Array.from({ length: index === 0 ? 55 : 0 }, (_event, eventIndex) => ({
              type: 'log',
              runId: 'subagent-clipped',
              text: `event ${eventIndex}`,
            })),
          },
        })),
      },
    };

    const projection = buildSubagentProjectionForEntry(entry);

    expect(projection).toMatchObject({
      replay: {
        limits: {
          evidenceSummaries: 8,
          processEvents: 50,
          taskProcessEvents: 50,
          toolExecutions: 24,
        },
        truncated: {
          evidenceSummaries: true,
          processEvents: true,
          taskPanels: false,
          toolExecutions: false,
        },
      },
    });
    expect(projection?.evidenceSummaries).toHaveLength(8);
    expect(projection?.processEvents).toHaveLength(50);
    expect(projection?.taskPanels[0].processEvents).toHaveLength(50);
  });

  it('marks tool execution truncation only when raw executions exceed the limit', () => {
    const buildEntry = (toolCallCount: number): SessionTreeEntry => ({
      id: `entry-tool-executions-${toolCallCount}`,
      parentId: 'parent',
      timestamp: '2026-04-22T14:00:00.000Z',
      type: 'subagent_result',
      content: {
        batchId: `batch-tool-executions-${toolCallCount}`,
        status: 'completed',
        parentEntryId: 'parent',
        results: [
          {
            id: `result-tool-executions-${toolCallCount}`,
            status: 'completed',
            summary: 'tool execution count',
            resultPayload: {
              processEvents: Array.from({ length: toolCallCount }, (_event, index) => ({
                type: 'tool_call',
                runId: 'subagent-tools',
                toolCallId: `call-${index}`,
                name: 'bash',
              })),
            },
          },
        ],
      },
    });

    const exactlyAtLimit = buildSubagentProjectionForEntry(buildEntry(24));
    const aboveLimit = buildSubagentProjectionForEntry(buildEntry(25));

    expect(exactlyAtLimit?.taskPanels[0].toolExecutions).toHaveLength(24);
    expect(exactlyAtLimit?.replay.truncated.toolExecutions).toBe(false);
    expect(aboveLimit?.taskPanels[0].toolExecutions).toHaveLength(24);
    expect(aboveLimit?.replay.truncated.toolExecutions).toBe(true);
  });
});
