/**
 * v2.2.271 — 이메일 영속 상태 머신 (syncThreads / detectOverdue / setThreadState).
 * "명령 안 쳐도 f/u 되는" 핵심 로직: 창 밖 미회신 보존, done 자동 재개, 알림 중복 차단.
 */
import { syncThreads, detectOverdue, setThreadState, waitingHours, type EmailStateFile } from '../src/features/email/emailState';
import type { ThreadGroup } from '../src/features/email/emailBrief';

const NOW = new Date('2026-07-03T12:00:00');

const group = (o: Partial<ThreadGroup>): ThreadGroup => ({
    topic: '계약 검토', subject: '계약 검토', participants: ['김철수'],
    inCount: 1, outCount: 0, lastDirection: 'in', lastDate: '2026-07-03 10:00',
    snippet: '검토 부탁드립니다', hasUnread: true, automated: false, refs: ['O1'],
    timeline: [],
    ccOnly: false,
    ...o,
});
const empty = (): EmailStateFile => ({ updatedAt: '', threads: [] });

describe('syncThreads — 관측 병합', () => {
    test('새 수신 스레드 → waiting + waitingSince 기록', () => {
        const s = syncThreads(empty(), [group({})], NOW);
        expect(s.threads[0].state).toBe('waiting');
        expect(s.threads[0].waitingSince).toBe('2026-07-03 10:00');
    });

    test('관측 안 된 기존 스레드는 보존 — 수집 창 밖 미회신이 사라지지 않음', () => {
        let s = syncThreads(empty(), [group({ topic: '옛건', subject: '옛건', lastDate: '2026-06-20 09:00' })], NOW);
        s = syncThreads(s, [group({})], NOW); // 다음 사이클엔 '옛건' 미관측
        expect(s.threads.map(t => t.topic).sort()).toEqual(['계약 검토', '옛건']);
        expect(s.threads.find(t => t.topic === '옛건')!.state).toBe('waiting');
    });

    test('내가 답하면 answered + 대기·알림 리셋', () => {
        let s = syncThreads(empty(), [group({})], NOW);
        s.threads[0].alertedFor = s.threads[0].waitingSince;
        s = syncThreads(s, [group({ lastDirection: 'out', outCount: 1, lastDate: '2026-07-03 11:00' })], NOW);
        expect(s.threads[0].state).toBe('answered');
        expect(s.threads[0].waitingSince).toBeUndefined();
        expect(s.threads[0].alertedFor).toBeUndefined();
    });

    test('done 스레드에 새 수신 → waiting 자동 재개 (놓침 방지)', () => {
        let s = syncThreads(empty(), [group({})], NOW);
        setThreadState(s, '계약 검토', 'done');
        s = syncThreads(s, [group({ lastDate: '2026-07-03 11:30', inCount: 2 })], NOW);
        expect(s.threads[0].state).toBe('waiting');
        expect(s.threads[0].waitingSince).toBe('2026-07-03 11:30');
    });

    test('muted 스레드는 새 수신에도 조용', () => {
        let s = syncThreads(empty(), [group({})], NOW);
        setThreadState(s, '계약 검토', 'muted');
        s = syncThreads(s, [group({ lastDate: '2026-07-03 11:30' })], NOW);
        expect(s.threads[0].state).toBe('muted');
    });

    test('60일 무활동 스레드는 정리', () => {
        let s = syncThreads(empty(), [group({ topic: '고대', subject: '고대', lastDate: '2026-04-01 09:00' })], NOW);
        s = syncThreads(s, [], NOW);
        expect(s.threads).toHaveLength(0);
    });
});

describe('detectOverdue — 임계 초과 1회 알림', () => {
    test('24시간 초과 → 감지 + alertedFor 마킹, 재호출 시 중복 없음', () => {
        const s = syncThreads(empty(), [group({ lastDate: '2026-07-02 10:00' })], NOW); // 26시간 전
        const first = detectOverdue(s, 24, NOW);
        expect(first).toHaveLength(1);
        expect(detectOverdue(s, 24, NOW)).toHaveLength(0); // 같은 대기 기간엔 1회만
    });

    test('임계 미만·자동발신·answered 는 제외', () => {
        const s = syncThreads(empty(), [
            group({ topic: '방금', subject: '방금', lastDate: '2026-07-03 11:00' }),
            group({ topic: '뉴스', subject: '뉴스', automated: true, lastDate: '2026-07-01 10:00' }),
            group({ topic: '답함', subject: '답함', lastDirection: 'out', lastDate: '2026-07-01 10:00' }),
        ], NOW);
        expect(detectOverdue(s, 24, NOW)).toHaveLength(0);
    });

    test('새 수신으로 대기 리셋되면 다시 알림 가능', () => {
        let s = syncThreads(empty(), [group({ lastDate: '2026-07-01 10:00' })], NOW);
        detectOverdue(s, 24, NOW); // 1차 알림
        s = syncThreads(s, [group({ lastDate: '2026-07-02 09:00', inCount: 2 })], NOW); // 새 수신 (그래도 27시간 전)
        expect(detectOverdue(s, 24, NOW)).toHaveLength(1); // 새 대기 기간 → 재알림
    });

    test('임계 0 = 끔', () => {
        const s = syncThreads(empty(), [group({ lastDate: '2026-07-01 10:00' })], NOW);
        expect(detectOverdue(s, 0, NOW)).toHaveLength(0);
    });
});

describe('waitingHours', () => {
    test('26시간 전 수신 → ~26', () => {
        const s = syncThreads(empty(), [group({ lastDate: '2026-07-02 10:00' })], NOW);
        expect(Math.round(waitingHours(s.threads[0], NOW))).toBe(26);
    });
});

describe('wikifiedTopicSet — 파일 삭제 감지 (v2.2.280)', () => {
    const { wikifiedTopicSet } = require('../src/features/email/emailState');
    const topics = {
        '구형항목': '2026-07-01T00:00:00Z',                          // 경로 없는 legacy
        '살아있음': { at: '2026-07-03', path: 'E:/wiki/a.md' },
        '삭제됨': { at: '2026-07-03', path: 'E:/wiki/deleted.md' },
    };
    const exists = (p: string) => p === 'E:/wiki/a.md';

    test('verify=true → 파일 지워진 항목은 완료에서 제외 (재위키화 대상)', () => {
        const s = wikifiedTopicSet(topics, exists, true);
        expect(s.has('살아있음')).toBe(true);
        expect(s.has('삭제됨')).toBe(false);
        expect(s.has('구형항목')).toBe(true); // 경로 없어 검증 불가 — 완료 유지 (reset 으로 해소)
    });

    test('verify=false → 기록 그대로', () => {
        expect(wikifiedTopicSet(topics, exists, false).size).toBe(3);
    });
});
