/**
 * ============================================================
 * 이메일 스레드 영속 상태 — `.astra/email_state.json` (v2.2.271)
 *
 * /email 이 "조회 도구"에서 "관리 도구"가 되기 위한 핵심: 워처가 주기적으로
 * 관측한 스레드를 여기 누적하므로, 수집 창(최근 N일) 밖으로 벗어난 미회신
 * 건도 사라지지 않고 계속 추적된다 (GTD Waiting-For 리스트의 코드화).
 *
 * 상태 머신:
 *   waiting  — 마지막 발화가 상대방 (회신 대기). waitingSince 부터 나이 계산.
 *   answered — 마지막 발화가 나 (공 넘김).
 *   done     — 사용자가 완료 처리. **새 수신이 오면 waiting 으로 자동 재개**.
 *   muted    — 사용자가 무시 처리. 새 수신이 와도 조용 (뉴스레터·잡음 주제).
 *
 * 알림은 매수시그널과 같은 전이 방식 — waitingSince 기준 1회만 (alertedFor 로 중복 차단).
 * 순수 로직(sync/overdue)과 파일 IO 를 분리해 테스트 가능하게 유지.
 * ============================================================
 */

import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import { logError, logInfo } from '../../utils';
import type { ThreadGroup } from './emailBrief';

export type ThreadLifecycle = 'waiting' | 'answered' | 'done' | 'muted';

export interface ThreadState {
    /** normalizeSubject 키. */
    topic: string;
    subject: string;
    participants: string[];
    /** 처음 관측한 시각 (ISO). */
    firstSeen: string;
    /** 스레드의 마지막 메시지 시각 ('YYYY-MM-DD HH:mm'). */
    lastActivity: string;
    state: ThreadLifecycle;
    /** 현재 회신 대기가 시작된 시각 — waiting 일 때만. */
    waitingSince?: string;
    /** 이 waitingSince 에 대해 알림을 이미 보냈는지 (중복 차단). */
    alertedFor?: string;
    automated: boolean;
    /** CC 로만 수신 — 회신 대상 아님 (알림·답할 목록에서 제외). */
    ccOnly?: boolean;
    hasUnread: boolean;
    snippet: string;
    inCount: number;
    outCount: number;
    /** 최근 관측 시 참조번호 (오래된 스레드는 비어 있을 수 있음). */
    refs: string[];
    latestEntryId?: string;
}

export interface EmailStateFile {
    updatedAt: string;
    threads: ThreadState[];
}

// ── 파일 IO (stocksStore 패턴 — atomic write) ────────────────────────────────
const STATE_REL = '.astra/email_state.json';

export function getEmailStatePath(): string | null {
    const folders = vscode.workspace.workspaceFolders;
    if (!folders || folders.length === 0) return null;
    return path.join(folders[0].uri.fsPath, STATE_REL);
}

export function readEmailState(): EmailStateFile {
    const p = getEmailStatePath();
    if (!p || !fs.existsSync(p)) return { updatedAt: '', threads: [] };
    try {
        const parsed = JSON.parse(fs.readFileSync(p, 'utf-8'));
        if (parsed && Array.isArray(parsed.threads)) return parsed as EmailStateFile;
        return { updatedAt: '', threads: [] };
    } catch (e: any) {
        logError('email_state.json 읽기 실패 — 빈 상태로 시작.', { error: e?.message ?? String(e) });
        return { updatedAt: '', threads: [] };
    }
}

export function writeEmailState(state: EmailStateFile): boolean {
    const p = getEmailStatePath();
    if (!p) return false;
    try {
        const dir = path.dirname(p);
        if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
        const tmp = `${p}.tmp-${process.pid}-${Date.now()}`;
        fs.writeFileSync(tmp, JSON.stringify(state, null, 2), 'utf-8');
        fs.renameSync(tmp, p);
        return true;
    } catch (e: any) {
        logError('email_state.json 쓰기 실패.', { error: e?.message ?? String(e) });
        return false;
    }
}

// ── 순수 로직 ────────────────────────────────────────────────────────────────

/** 관측 창을 벗어난 오래된 항목 정리 — 60일 무활동, 또는 done/muted 30일. */
const KEEP_ACTIVE_DAYS = 60;
const KEEP_CLOSED_DAYS = 30;

function daysBetween(isoOrYmd: string, now: Date): number {
    const t = new Date(isoOrYmd.replace(' ', 'T')).getTime();
    if (!Number.isFinite(t)) return 0;
    return (now.getTime() - t) / 86_400_000;
}

/**
 * 이번 관측(groupThreads 결과)을 저장된 상태에 병합한다.
 *  - 새 주제 → 생성. 기존 주제 → 활동/방향 갱신.
 *  - done 이었는데 **새 수신**이 오면 waiting 재개 (놓침 방지). muted 는 계속 조용.
 *  - 방향이 in→out 으로 바뀌면 answered + waitingSince/alertedFor 해제.
 *  - 관측되지 않은 기존 스레드는 그대로 유지 (수집 창 밖 미회신 보존 — 이 함수의 존재 이유).
 */
export function syncThreads(store: EmailStateFile, observed: ThreadGroup[], now: Date = new Date()): EmailStateFile {
    const byTopic = new Map(store.threads.map(t => [t.topic, t]));

    for (const g of observed) {
        const prev = byTopic.get(g.topic);
        if (!prev) {
            byTopic.set(g.topic, {
                topic: g.topic,
                subject: g.subject,
                participants: g.participants,
                firstSeen: now.toISOString(),
                lastActivity: g.lastDate,
                state: g.automated ? 'answered' : g.lastDirection === 'in' ? 'waiting' : 'answered',
                waitingSince: !g.automated && g.lastDirection === 'in' ? g.lastDate : undefined,
                automated: g.automated,
                ccOnly: g.ccOnly,
                hasUnread: g.hasUnread,
                snippet: g.snippet,
                inCount: g.inCount,
                outCount: g.outCount,
                refs: g.refs,
                latestEntryId: g.latestEntryId,
            });
            continue;
        }

        const hasNewActivity = g.lastDate.localeCompare(prev.lastActivity) > 0;
        prev.subject = g.subject;
        prev.participants = g.participants;
        prev.hasUnread = g.hasUnread;
        prev.snippet = g.snippet || prev.snippet;
        prev.inCount = g.inCount;
        prev.outCount = g.outCount;
        prev.refs = g.refs;
        prev.latestEntryId = g.latestEntryId || prev.latestEntryId;
        prev.automated = g.automated;
        prev.ccOnly = g.ccOnly;

        if (!hasNewActivity) continue;
        prev.lastActivity = g.lastDate;

        if (prev.state === 'muted') continue; // 무시 주제는 영구 조용
        if (g.automated) { prev.state = 'answered'; prev.waitingSince = undefined; continue; }

        if (g.lastDirection === 'out') {
            // 내가 답했음 — 공 넘김. 대기·알림 상태 리셋.
            prev.state = 'answered';
            prev.waitingSince = undefined;
            prev.alertedFor = undefined;
        } else {
            // 새 수신 — done 이었어도 다시 회신 대기로 재개.
            prev.state = 'waiting';
            prev.waitingSince = g.lastDate;
            prev.alertedFor = undefined;
        }
    }

    // 정리 — 오래 무활동/닫힌 항목 제거.
    const threads = [...byTopic.values()].filter(t => {
        const age = daysBetween(t.lastActivity, now);
        if (t.state === 'done' || t.state === 'muted') return age <= KEEP_CLOSED_DAYS || t.state === 'muted';
        return age <= KEEP_ACTIVE_DAYS;
    });

    return { updatedAt: now.toISOString(), threads };
}

/** 회신 대기 나이(시간). waiting 이 아니면 0. */
export function waitingHours(t: ThreadState, now: Date = new Date()): number {
    if (t.state !== 'waiting' || !t.waitingSince) return 0;
    const start = new Date(t.waitingSince.replace(' ', 'T')).getTime();
    if (!Number.isFinite(start)) return 0;
    return Math.max(0, (now.getTime() - start) / 3_600_000);
}

/**
 * 임계 초과 미회신 감지 — 사람 발신 waiting 스레드 중 thresholdHours 를 넘겼고
 * 아직 이 대기 기간에 알림 안 보낸 것. 반환된 스레드의 alertedFor 를 제자리
 * 마킹하므로 호출자는 반환 후 writeEmailState 로 저장해야 중복 알림이 없다.
 */
export function detectOverdue(store: EmailStateFile, thresholdHours: number, now: Date = new Date()): ThreadState[] {
    if (thresholdHours <= 0) return [];
    const overdue: ThreadState[] = [];
    for (const t of store.threads) {
        if (t.state !== 'waiting' || t.automated || !t.waitingSince) continue;
        if (t.alertedFor === t.waitingSince) continue;
        if (waitingHours(t, now) >= thresholdHours) {
            t.alertedFor = t.waitingSince;
            overdue.push(t);
        }
    }
    return overdue;
}

// ── 위키화 완료 레지스트리 (v2.2.275) ─────────────────────────────────────────
// `/email scan` 의 자동 위키화가 같은 스레드를 두 번 위키화하지 않게 하는 영속
// 기록. email_state 의 60일 정리와 무관하게 유지되어, 오래된 스레드를 재스캔해도
// 중복 문서가 생기지 않는다. 회차 상한에 걸린 나머지는 다음 실행에서 이어서.
const WIKIFIED_REL = '.astra/email_wikified.json';

function wikifiedPath(): string | null {
    const folders = vscode.workspace.workspaceFolders;
    if (!folders || folders.length === 0) return null;
    return path.join(folders[0].uri.fsPath, WIKIFIED_REL);
}

/** 레지스트리 값 — v2.2.280부터 저장 경로 포함 (파일 삭제 감지용). 구형은 날짜 문자열. */
type WikifiedEntry = string | { at: string; path?: string };

/**
 * 완료 topic 집합 (순수 로직 — 테스트 export). verify 모드에서는 경로가 기록된
 * 항목의 **파일이 실제로 존재할 때만** 완료로 인정 — 사용자가 위키 문서를 지우면
 * 다음 스캔이 자동으로 다시 위키화한다. 경로 없는 구형 항목은 검증 불가라 완료 유지
 * (`/email scan reset` 으로 초기화).
 */
export function wikifiedTopicSet(
    topics: Record<string, WikifiedEntry>,
    exists: (p: string) => boolean,
    verify: boolean,
): Set<string> {
    const out = new Set<string>();
    for (const [topic, entry] of Object.entries(topics || {})) {
        if (verify && entry && typeof entry === 'object' && entry.path && !exists(entry.path)) continue;
        out.add(topic);
    }
    return out;
}

export function readWikifiedTopics(verifyFiles = false): Set<string> {
    const p = wikifiedPath();
    if (!p || !fs.existsSync(p)) return new Set();
    try {
        const parsed = JSON.parse(fs.readFileSync(p, 'utf-8'));
        return wikifiedTopicSet(parsed?.topics || {}, f => { try { return fs.existsSync(f); } catch { return false; } }, verifyFiles);
    } catch { return new Set(); }
}

export function markWikified(topic: string, filePath?: string): void {
    const p = wikifiedPath();
    if (!p) return;
    try {
        let data: { topics: Record<string, WikifiedEntry> } = { topics: {} };
        if (fs.existsSync(p)) {
            try { data = JSON.parse(fs.readFileSync(p, 'utf-8')); } catch { /* 손상 시 새로 시작 */ }
            if (!data || typeof data !== 'object' || !data.topics) data = { topics: {} };
        }
        data.topics[topic] = { at: new Date().toISOString(), path: filePath };
        const tmp = `${p}.tmp-${process.pid}`;
        fs.writeFileSync(tmp, JSON.stringify(data, null, 2), 'utf-8');
        fs.renameSync(tmp, p);
    } catch (e: any) {
        logError('email_wikified.json 기록 실패.', { error: e?.message ?? String(e) });
    }
}

/** 완료 기록 전체 초기화 — `/email scan reset`. 다음 스캔이 처음부터 다시 위키화. */
export function clearWikified(): boolean {
    const p = wikifiedPath();
    if (!p) return false;
    try {
        if (fs.existsSync(p)) fs.rmSync(p);
        logInfo('email_wikified.json 초기화.');
        return true;
    } catch (e: any) {
        logError('email_wikified.json 초기화 실패.', { error: e?.message ?? String(e) });
        return false;
    }
}

/** 사용자 트리아지 — done/muted 처리. topic 은 normalizeSubject 키. */
export function setThreadState(store: EmailStateFile, topic: string, state: 'done' | 'muted' | 'waiting'): ThreadState | null {
    const t = store.threads.find(x => x.topic === topic);
    if (!t) return null;
    t.state = state;
    if (state !== 'waiting') { t.waitingSince = undefined; t.alertedFor = undefined; }
    else if (!t.waitingSince) t.waitingSince = t.lastActivity;
    logInfo('Email thread 상태 변경.', { topic, state });
    return t;
}
