import { describe, expect, it } from 'vitest';
import type { AssignmentProposal } from '@pac/types';
import {
  applyIncomingSheet,
  findActiveSheet,
  type Block,
  type ChatMessage,
  type DraftState,
} from './chat-blocks';

/**
 * 草稿状态机的回归 —— 这段逻辑的失败形态**全都是静默的**：
 * 改错了卡片主管看不到反应、两张 active 同时能点确认就是分错一批人。
 * 端到端点一遍代价高且不稳定，所以抽成纯函数在这里锁住。
 */

const sheet = (n: number) => ({ placed: n } as unknown as AssignmentProposal);

function msg(id: string, ...sheets: Array<[string, DraftState]>): ChatMessage {
  return {
    id,
    role: 'assistant',
    blocks: sheets.map(([requestId, state]): Block => ({
      kind: 'assignment_sheet',
      requestId,
      sheet: sheet(1),
      state,
    })),
  };
}

/** 摊平成 [requestId, state] 便于断言 */
function states(ms: ChatMessage[]): Array<[string, DraftState]> {
  return ms.flatMap((m) =>
    m.blocks
      .filter((b): b is Extract<Block, { kind: 'assignment_sheet' }> => b.kind === 'assignment_sheet')
      .map((b) => [b.requestId, b.state] as [string, DraftState]),
  );
}

describe('applyIncomingSheet', () => {
  it('第一张草稿挂到宿主消息下，状态是 active', () => {
    const out = applyIncomingSheet([msg('m1')], {
      requestId: 'r1',
      sheet: sheet(10),
      hostMessageId: 'm1',
    });
    expect(states(out)).toEqual([['r1', 'active']]);
  });

  it('🔴 新草稿到达时，把**上一轮消息里**的 active 作废（跨消息扫描）', () => {
    const prev = [msg('m1', ['r1', 'active']), msg('m2')];
    const out = applyIncomingSheet(prev, {
      requestId: 'r2',
      sheet: sheet(20),
      hostMessageId: 'm2',
    });
    expect(states(out)).toEqual([
      ['r1', 'superseded'],
      ['r2', 'active'],
    ]);
  });

  it('🔴 终态不许被回退：confirmed / cancelled 不受作废扫描影响', () => {
    const prev = [msg('m1', ['r1', 'confirmed'], ['r2', 'cancelled']), msg('m2')];
    const out = applyIncomingSheet(prev, {
      requestId: 'r3',
      sheet: sheet(30),
      hostMessageId: 'm2',
    });
    expect(states(out)).toEqual([
      ['r1', 'confirmed'],
      ['r2', 'cancelled'],
      ['r3', 'active'],
    ]);
  });

  it('同一 requestId 重推：原地换内容、回到 active、⛔ 不堆第二张卡', () => {
    const withEdits: ChatMessage = {
      id: 'm1',
      role: 'assistant',
      blocks: [
        {
          kind: 'assignment_sheet',
          requestId: 'r1',
          sheet: sheet(1),
          state: 'active',
          edits: [{ seq: 1, ops: [] }],
        },
      ],
    };
    const out = applyIncomingSheet([withEdits], {
      requestId: 'r1',
      sheet: sheet(99),
      hostMessageId: 'm1',
    });
    expect(states(out)).toEqual([['r1', 'active']]);
    const b = out[0]!.blocks[0] as Extract<Block, { kind: 'assignment_sheet' }>;
    expect(b.sheet).toEqual(sheet(99));
    // ⚠️ 旧编辑指令必须清空 —— 对新名单没有意义
    expect(b.edits).toEqual([]);
  });

  it('任一时刻最多一张 active（连出三版）', () => {
    let ms: ChatMessage[] = [msg('m1')];
    for (const r of ['r1', 'r2', 'r3']) {
      ms = applyIncomingSheet(ms, { requestId: r, sheet: sheet(1), hostMessageId: 'm1' });
    }
    expect(states(ms).filter(([, s]) => s === 'active')).toHaveLength(1);
    expect(states(ms)).toEqual([
      ['r1', 'superseded'],
      ['r2', 'superseded'],
      ['r3', 'active'],
    ]);
  });
});

describe('findActiveSheet', () => {
  it('🔴 找的是 active，不是「最后一张」—— 这正是 2026-08-12 改掉的坑', () => {
    // 顺序上 r2 在后，但它已作废；助手的修改必须落到 r1 上
    const ms = [msg('m1', ['r1', 'active']), msg('m2', ['r2', 'superseded'])];
    const at = findActiveSheet(ms);
    expect(at).toEqual({ messageIndex: 0, blockIndex: 0 });
  });

  it('一张 active 都没有时返回 null（⛔ 不许退化成改最后一张）', () => {
    const ms = [msg('m1', ['r1', 'confirmed']), msg('m2', ['r2', 'superseded'])];
    expect(findActiveSheet(ms)).toBeNull();
  });

  it('没有确认单时返回 null', () => {
    expect(findActiveSheet([msg('m1')])).toBeNull();
  });
});
