/**
 * /meet 액션 아이템의 "확신 게이트" 등록 시스템.
 *
 * 정책 (사용자 정의):
 *  - 확정(합의+기한) → 자동 등록.
 *  - 진행미정 / 기한미정 / 조건부 → 등록 보류 + 사용자에게 질문, `/meet confirm` 답변으로 등록 완결.
 *  - 반복(예: 매주 목요일) → 반복 등록하지 않고 *첫 1회만* 등록 (까먹음 방지).
 *  - 과거 날짜(옛 녹취) → 과거 날짜 그대로 등록 + "과거자료·완료확인 필요" 표기.
 *  - 같은 녹취록 재실행 → 녹취 해시 레지스트리로 이중 등록 차단.
 *
 * 저장 (워크스페이스):
 *  - `.astra/meet_pending.json`    — 보류 항목 (confirm 대기)
 *  - `.astra/meet_registered.json` — 녹취해시 → 등록된 작업 키 (idempotency)
 *
 * 조건부 task 규칙 (Google Calendar 에 의존성 개념이 없어 설계한 자체 규칙):
 *  - `N=ok`        → Google Tasks 에 *날짜 없이* 등록. 제목 `[조건부] …`, 노트에 `■ 선행 조건` 명시.
 *                    (날짜 없는 task 는 Tasks 목록에 상시 노출 — 조건 충족 시 사용자가 날짜 부여)
 *  - `N=날짜`      → 그 날짜를 "조건 확인일"로 등록. 제목 `[조건부 확인] …` — 그날 선행 조건
 *                    충족 여부를 점검하라는 리마인더.
 *  → 어느 쪽이든 의존 대상(선행작업)이 제목/노트에 명시되어 한눈에 인지된다.
 */
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import { createCalendarEvent } from '../../calendar/calendarApi';
import { createTask } from '../../calendar/tasksApi';
import { resolveTaskDate, toYmd, addBusinessDays } from './calendarHelpers';
import { logInfo } from '../../../utils';

// ── 타입 ────────────────────────────────────────────────────────────────────
export type ActionRow = { owner: string; work: string; detail: string; deliverable: string; due: string; status: string; source?: string };

export type HoldKind = 'undecided' | 'nodate' | 'conditional';
export interface PendingItem {
    idx: number;                 // 사용자 답변용 번호 (1-base)
    owner: string;
    work: string;
    detail: string;
    deliverable: string;         // 산출물 (없으면 "확인 필요")
    due: string;
    source?: string;             // 근거 타임스탬프 [mm:ss] (있으면 노트에 표기)
    kind: HoldKind;
    condition?: string;          // kind=conditional 의 선행작업
    suggestedDate: string;       // ok 답변 시 사용할 제안 날짜 (YYYY-MM-DD)
}
export interface PendingFile {
    createdAt: string;
    meetTitle: string;
    meetingDateYmd: string;
    transcriptHash: string;
    items: PendingItem[];
}

// ── 워크스페이스 파일 경로 ──────────────────────────────────────────────────
function wsFile(rel: string): string | null {
    const ws = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
    if (!ws) return null;
    return path.join(ws, '.astra', rel);
}
function readJson<T>(rel: string): T | null {
    const f = wsFile(rel);
    if (!f || !fs.existsSync(f)) return null;
    try { return JSON.parse(fs.readFileSync(f, 'utf8')) as T; } catch { return null; }
}
function writeJson(rel: string, data: unknown): boolean {
    const f = wsFile(rel);
    if (!f) return false;
    try {
        fs.mkdirSync(path.dirname(f), { recursive: true });
        fs.writeFileSync(f, JSON.stringify(data, null, 2), 'utf8');
        return true;
    } catch { return false; }
}

// ── 중복 방지 레지스트리 ────────────────────────────────────────────────────
const REGISTRY_REL = 'meet_registered.json';
const PENDING_REL = 'meet_pending.json';
type Registry = Record<string, { keys: string[]; meetTitle: string; at: string }>;

export function transcriptHash(raw: string): string {
    return crypto.createHash('sha256').update(raw, 'utf8').digest('hex').slice(0, 16);
}
export function taskKey(work: string): string {
    return (work || '').normalize('NFC').toLowerCase().replace(/[\s\-_.,:;'"()[\]]/g, '');
}
export function loadRegisteredKeys(hash: string): Set<string> {
    const reg = readJson<Registry>(REGISTRY_REL) || {};
    return new Set(reg[hash]?.keys || []);
}
export function markRegistered(hash: string, meetTitle: string, keys: string[]): void {
    const reg = readJson<Registry>(REGISTRY_REL) || {};
    const cur = reg[hash] || { keys: [], meetTitle, at: new Date().toISOString() };
    const set = new Set(cur.keys);
    keys.forEach(k => set.add(k));
    reg[hash] = { keys: [...set], meetTitle, at: new Date().toISOString() };
    writeJson(REGISTRY_REL, reg);
}

// ── Pending 저장 ────────────────────────────────────────────────────────────
export function savePending(p: PendingFile): boolean { return writeJson(PENDING_REL, p); }
export function loadPending(): PendingFile | null { return readJson<PendingFile>(PENDING_REL); }
export function clearPending(): void { const f = wsFile(PENDING_REL); if (f && fs.existsSync(f)) try { fs.rmSync(f); } catch { /* noop */ } }

// ── 상태 분류 ───────────────────────────────────────────────────────────────
export type Classified =
    | { route: 'auto'; date: string; pastNote: boolean; recurNote?: string }
    | { route: 'hold'; kind: HoldKind; condition?: string; suggestedDate: string };

const WEEKDAYS = ['일', '월', '화', '수', '목', '금', '토'];
export function nextWeekday(from: Date, korDay: string): Date | null {
    const target = WEEKDAYS.indexOf(korDay);
    if (target < 0) return null;
    const d = new Date(from);
    const diff = (target - d.getDay() + 7) % 7 || 7; // 오늘이 그 요일이면 다음 주
    d.setDate(d.getDate() + diff);
    return d;
}

/** 액션 한 행을 등록 경로로 분류한다. */
export function classifyAction(row: ActionRow, meetingDate: Date, today: Date, dateFallbackToday = false): Classified {
    const status = (row.status || '').trim();
    const suggested = () => {
        const r = resolveTaskDate(row.due, meetingDate, today);
        return r.tentative ? toYmd(addBusinessDays(today, 5)) : r.date;
    };
    // 기한이 해석되면 그 날짜, 해석 불가면 '작성일(오늘)'로 자동 등록.
    // (g1nation.meetTaskDateFallback='today' — 확정·기한미정이 매번 0건 보류되는 문제 해소)
    const autoWithDue = (): Classified => {
        const r = resolveTaskDate(row.due, meetingDate, today);
        if (!r.tentative) return { route: 'auto', date: r.date, pastNote: r.date < toYmd(today) };
        return { route: 'auto', date: toYmd(today), pastNote: false };
    };

    if (/^진행\s*미정/.test(status)) {
        return { route: 'hold', kind: 'undecided', suggestedDate: suggested() };
    }
    if (/^기한\s*미정/.test(status)) {
        if (dateFallbackToday) return autoWithDue();
        return { route: 'hold', kind: 'nodate', suggestedDate: suggested() };
    }
    const cond = status.match(/^조건부\s*[:：]?\s*(.*)$/);
    if (cond) {
        return { route: 'hold', kind: 'conditional', condition: cond[1].trim() || '(선행작업 미표기 — 회의록 참조)', suggestedDate: suggested() };
    }
    const recur = status.match(/^반복\s*[:：]?\s*(.*)$/);
    if (recur) {
        // 반복은 첫 1회만 등록. 주기 텍스트에서 요일 추출 → 다음 해당 요일.
        const cycle = recur[1].trim();
        const dayM = (cycle + ' ' + row.due).match(/([월화수목금토일])요일?/);
        const first = dayM ? nextWeekday(today, dayM[1]) : null;
        if (first) {
            return { route: 'auto', date: toYmd(first), pastNote: false, recurNote: cycle || row.due };
        }
        // 요일을 못 찾으면 기한미정으로 보류 (추측 등록 금지)
        return { route: 'hold', kind: 'nodate', suggestedDate: suggested() };
    }

    // 확정(또는 상태 누락 구표): 기한이 해석되면 자동, 해석 불가면 작성일 폴백 or 보류.
    const r = resolveTaskDate(row.due, meetingDate, today);
    if (r.tentative) {
        if (dateFallbackToday) return { route: 'auto', date: toYmd(today), pastNote: false };
        return { route: 'hold', kind: 'nodate', suggestedDate: toYmd(addBusinessDays(today, 5)) };
    }
    const past = r.date < toYmd(today);
    return { route: 'auto', date: r.date, pastNote: past };
}

// ── 공용 등록기 ─────────────────────────────────────────────────────────────
export interface RegisterOpts {
    title: string;
    date?: string;               // 없으면 Tasks 에 날짜 없는 task (조건부 ok)
    notes: string;
    useTasks: boolean;
    useCalendar: boolean;
}
export async function registerAction(
    context: vscode.ExtensionContext,
    opts: RegisterOpts,
): Promise<{ successes: string[]; failures: string[] }> {
    const successes: string[] = [];
    const failures: string[] = [];
    if (opts.useTasks) {
        const r = await createTask(context, { title: opts.title, due: opts.date, notes: opts.notes });
        if (r.ok) successes.push('Tasks'); else failures.push(`Tasks: ${r.error}`);
    }
    if (opts.useCalendar) {
        if (!opts.date) {
            failures.push('Calendar: 날짜 없는 등록은 Tasks 만 가능 (조건부는 확인일을 지정하면 Calendar 에도 등록)');
        } else {
            const r = await createCalendarEvent(context, { title: opts.title, start: opts.date, allDay: true, description: opts.notes });
            if (r.ok) successes.push('Calendar'); else failures.push(`Calendar: ${r.error}`);
        }
    }
    return { successes, failures };
}

/** 등록 노트 공통 빌더. */
export function buildNotes(p: { detail: string; meetTitle: string; owner: string; deliverable?: string; dueRaw: string; dateLabel: string; source?: string; extra?: string[] }): string {
    const detailLine = p.detail?.trim() || '(회의록 본문 참조 — 액션 한 줄에 상세가 녹아 있음)';
    const deliverable = p.deliverable?.trim();
    const source = p.source?.trim();
    return [
        '■ 작업 상세', detailLine, '',
        ...(deliverable ? ['■ 산출물', deliverable, ''] : []),
        ...(p.extra && p.extra.length ? [...p.extra, ''] : []),
        '■ 맥락',
        `· 회의록: ${p.meetTitle}`,
        `· 담당: ${p.owner || '(미지정)'}`,
        `· 기한: ${p.dueRaw?.trim() || '(미표기)'} → ${p.dateLabel}`,
        ...(source ? [`· 근거: 녹취 ${source}`] : []),
        '', '— Astra /meet 등록',
    ].join('\n');
}

/**
 * [액션 상세 확장 파싱] buildTaskDetailPrompt 응답("[n] 설명…")을 액션별 설명 맵으로.
 * 번호(1-based)를 tasks 인덱스에 대응시키고, 설명 텍스트를 taskKey(work)로 키잉한다.
 * 형식이 깨진 응답은 매핑 안 된 항목을 그냥 비워 둔다(폴백: buildNotes 가 placeholder).
 */
export function parseTaskDetails(raw: string, tasks: { work: string }[]): Map<string, string> {
    const map = new Map<string, string>();
    if (!raw || !raw.trim()) return map;
    const re = /\[(\d+)\]\s*([\s\S]*?)(?=\n\s*\[\d+\]\s|$)/g;
    let m: RegExpExecArray | null;
    while ((m = re.exec(raw)) !== null) {
        const idx = Number(m[1]) - 1;
        const desc = m[2].trim().replace(/\s*\n\s*/g, ' ').trim();
        if (idx >= 0 && idx < tasks.length && desc) {
            map.set(taskKey(tasks[idx].work), desc);
        }
    }
    return map;
}

// ── /meet confirm 답변 파싱 ─────────────────────────────────────────────────
export type ConfirmDecision = { idx: number; action: 'ok' | 'skip' | 'date'; date?: string };

/** `1=6/20 2=ok 3=skip 4=2026-07-01` → 결정 목록. 잘못된 토큰은 errors 로. */
export function parseConfirmArgs(arg: string, todayYear: number): { decisions: ConfirmDecision[]; errors: string[] } {
    const decisions: ConfirmDecision[] = [];
    const errors: string[] = [];
    for (const tok of arg.split(/\s+/).map(t => t.trim()).filter(Boolean)) {
        const m = tok.match(/^(\d+)\s*=\s*(.+)$/);
        if (!m) { errors.push(`형식 오류: \`${tok}\` (예: 1=6/20, 2=ok, 3=skip)`); continue; }
        const idx = Number(m[1]);
        const val = m[2].trim().toLowerCase();
        if (val === 'ok' || val === '등록' || val === 'yes' || val === 'y') { decisions.push({ idx, action: 'ok' }); continue; }
        if (val === 'skip' || val === '취소' || val === 'no' || val === 'n' || val === '제외') { decisions.push({ idx, action: 'skip' }); continue; }
        const date = normalizeDate(val, todayYear);
        if (date) { decisions.push({ idx, action: 'date', date }); continue; }
        errors.push(`날짜 해석 불가: \`${tok}\` (YYYY-MM-DD, M/D, M월D일 지원)`);
    }
    return { decisions, errors };
}

export function normalizeDate(raw: string, todayYear: number): string | null {
    const iso = raw.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
    if (iso) return `${iso[1]}-${iso[2].padStart(2, '0')}-${iso[3].padStart(2, '0')}`;
    const slash = raw.match(/^(\d{1,2})[\/.](\d{1,2})$/);
    if (slash) return `${todayYear}-${slash[1].padStart(2, '0')}-${slash[2].padStart(2, '0')}`;
    const kor = raw.match(/^(\d{1,2})월\s*(\d{1,2})일?$/);
    if (kor) return `${todayYear}-${kor[1].padStart(2, '0')}-${kor[2].padStart(2, '0')}`;
    return null;
}

/** 보류 목록을 사용자 질문 텍스트로 렌더. */
export function renderPendingQuestion(p: PendingFile): string {
    const lines: string[] = [];
    lines.push(`\n⏸️ **등록 보류 ${p.items.length}건 — 확신이 없어 등록 전에 확인이 필요합니다** (회의: ${p.meetTitle})\n`);
    for (const it of p.items) {
        const why = it.kind === 'undecided' ? '진행 여부 미확정'
            : it.kind === 'nodate' ? '기한 미정'
            : `조건부 — 선행: ${it.condition}`;
        lines.push(`  ${it.idx}. **${it.work}** — ${why}${it.owner ? ` (담당: ${it.owner})` : ''}`);
        if (it.kind !== 'conditional') lines.push(`     · ok 시 제안 날짜: ${it.suggestedDate}`);
    }
    lines.push('');
    lines.push('답변 방법: `/meet confirm 1=6/20 2=ok 3=skip` 처럼 한 줄로.');
    lines.push('  · `날짜`(YYYY-MM-DD, M/D, M월D일) = 그 날짜로 등록 · `ok` = 제안 날짜로 등록 · `skip` = 등록 안 함');
    lines.push('  · 조건부 항목: `ok` = 날짜 없는 Tasks 로 등록([조건부] 표시) · `날짜` = 그날을 조건 확인일로 등록');
    lines.push('보류 목록 다시 보기: `/meet pending`');
    return lines.join('\n');
}

export function logMeetRegistration(event: string, data: Record<string, unknown>): void {
    logInfo(`/meet 등록 게이트: ${event}`, data);
}

// ── confirm 처리 코어 (출력 중립 — 웹뷰·텔레그램 공용) ──────────────────────
// runMeetConfirm(웹뷰)과 텔레그램 인바운드가 같은 로직을 쓰도록 문자열 라인을
// 반환한다. 원격(텔레그램)에서도 보류 답변→등록 완결이 가능해진다 (P5 HITL).
import { readCalendarConfig } from '../../calendar/calendarCache';

export async function processConfirmDecisions(
    context: vscode.ExtensionContext,
    arg: string,
): Promise<{ lines: string[]; remaining: number }> {
    const lines: string[] = [];
    const pend = loadPending();
    if (!pend || !pend.items.length) {
        return { lines: ['ℹ️ 등록 보류 중인 액션 아이템이 없습니다.'], remaining: 0 };
    }
    if (!arg.trim()) {
        return { lines: [renderPendingQuestion(pend)], remaining: pend.items.length };
    }
    const calCfg = readCalendarConfig(context);
    if (!calCfg.refreshToken) {
        return { lines: ['⚠️ Google OAuth(쓰기)가 연결되지 않아 등록할 수 없습니다. (Astra Settings → Google 섹션)'], remaining: pend.items.length };
    }
    const gCfg = vscode.workspace.getConfiguration('g1nation');
    const useTasks = gCfg.get<boolean>('meetUsesTasks', true);
    const useCalendar = gCfg.get<boolean>('meetUsesCalendar', true);

    const { decisions, errors } = parseConfirmArgs(arg, new Date().getFullYear());
    for (const e of errors) lines.push(`⚠️ ${e}`);
    if (!decisions.length) {
        lines.push('해석 가능한 답변이 없습니다. 예: confirm 1=6/20 2=ok 3=skip');
        return { lines, remaining: pend.items.length };
    }

    let registered = 0, skipped = 0, failed = 0;
    const doneIdx = new Set<number>();
    const newKeys: string[] = [];

    for (const d of decisions) {
        const item = pend.items.find(i => i.idx === d.idx);
        if (!item) { lines.push(`⚠️ ${d.idx}번 항목이 보류 목록에 없습니다.`); continue; }

        if (d.action === 'skip') {
            skipped++; doneIdx.add(item.idx);
            lines.push(`⏭️ ${item.idx}. ${item.work} — 등록 안 함`);
            continue;
        }

        const isConditional = item.kind === 'conditional';
        let title: string;
        let date: string | undefined;
        const extra: string[] = [];

        if (isConditional) {
            extra.push(`■ 선행 조건: ${item.condition}`);
            if (d.action === 'ok') {
                title = `[조건부] ${item.work}`;
                date = undefined;
                extra.push('· 선행 조건 충족 후 진행 — 날짜 없는 task 로 등록됨 (충족 시 날짜 부여)');
                if (!useTasks) {
                    failed++;
                    lines.push(`⚠️ ${item.idx}. ${item.work} — 날짜 없는 등록은 Tasks 가 필요합니다. 확인일을 지정하세요: ${item.idx}=날짜`);
                    continue;
                }
            } else {
                title = `[조건부 확인] ${item.work}`;
                date = d.date!;
                extra.push(`· ${date} 는 선행 조건 충족 여부를 점검하는 확인일입니다.`);
            }
        } else {
            title = item.work;
            date = d.action === 'ok' ? item.suggestedDate : d.date!;
        }

        const notes = buildNotes({
            detail: item.detail, meetTitle: pend.meetTitle, owner: item.owner, deliverable: item.deliverable,
            dueRaw: item.due, dateLabel: date || '(날짜 없음 — 조건부)', source: item.source, extra,
        });
        const r = await registerAction(context, {
            title, date, notes,
            useTasks, useCalendar: isConditional && !date ? false : useCalendar,
        });
        if (r.failures.length === 0) {
            registered++; doneIdx.add(item.idx); newKeys.push(taskKey(item.work));
            lines.push(`✅ ${date || '(날짜 없음)'} — ${title} (${r.successes.join(' + ')})`);
        } else {
            failed++;
            if (r.successes.length) { doneIdx.add(item.idx); newKeys.push(taskKey(item.work)); }
            lines.push(`${date || '(날짜 없음)'} — ${title}${r.successes.length ? ` (✅ ${r.successes.join(' + ')})` : ''}`);
            for (const f of r.failures) lines.push(`  ⚠️ ${f}`);
        }
    }

    if (newKeys.length) markRegistered(pend.transcriptHash, pend.meetTitle, newKeys);

    const remaining = pend.items.filter(i => !doneIdx.has(i.idx));
    if (remaining.length === 0) {
        clearPending();
        lines.push(`✅ 보류 항목 처리 완료 — 등록 ${registered}건 · 건너뜀 ${skipped}건${failed ? ` · 실패 ${failed}건` : ''}`);
    } else {
        savePending({ ...pend, items: remaining });
        lines.push(`등록 ${registered}건 · 건너뜀 ${skipped}건${failed ? ` · 실패 ${failed}건` : ''} — 아직 ${remaining.length}건 보류 중 (pending 으로 확인)`);
    }
    return { lines, remaining: remaining.length };
}

// ── 회의 용어집 (반복 회의의 STT 보정 정확도용) ─────────────────────────────
// meetPrompt 는 메타데이터를 "용어집 역할"로 쓴다 — 매번 수동 입력하는 대신,
// 이전 /meet 실행에서 나온 인명(담당)·사용자 입력 메타데이터 용어를 워크스페이스에
// 누적하고 다음 실행 때 자동 주입한다. (.astra/meet_glossary.json)
const GLOSSARY_REL = 'meet_glossary.json';
const GLOSSARY_MAX = 120;
type Glossary = { terms: string[]; updatedAt: string };

export function loadGlossaryTerms(): string[] {
    return (readJson<Glossary>(GLOSSARY_REL)?.terms || []).filter(t => typeof t === 'string' && t.trim());
}

// ── 회사 표준 팀/역할 분류 (화자 정규화용 통제 어휘집) ────────────────────────
// /meet 는 STT 화자번호("참석자 N")를 실명이 아니라 **팀/역할**로 귀속한다.
// 회사의 안정적 팀 구조를 통제 어휘집으로 모든 조각·병합 프롬프트에 주입하면,
// 개인 매핑 추측(오귀속 위험)을 피하면서 일관된 역할 귀속이 가능해진다.
// 설정 `g1nation.meetTeamRoster` 로 회사별 커스터마이즈 가능.
export const DEFAULT_TEAM_ROSTER =
    'PD, 기획, 사업, 클라이언트, 넥서스개발팀(서버), UI, 배경팀, 캐릭터팀, QA, 사운드, 개발PM';

export function loadTeamRoster(): string {
    const v = vscode.workspace.getConfiguration('g1nation').get<string>('meetTeamRoster', '');
    return (v && v.trim()) ? v.trim() : DEFAULT_TEAM_ROSTER;
}

/** 담당자 이름·메타데이터에서 뽑은 용어를 용어집에 누적 (중복 제거, 상한 유지). */
export function updateGlossary(newTerms: string[]): void {
    const cleaned = newTerms
        .map(t => (t || '').trim())
        .filter(t => t.length >= 2 && t.length <= 30 && !/미지정|없음|확인|불명/.test(t));
    if (!cleaned.length) return;
    const cur = loadGlossaryTerms();
    const set = new Set(cur);
    for (const t of cleaned) set.add(t);
    // 상한 초과 시 오래된 것부터 제거 (Set 삽입 순서 = 누적 순서)
    const all = [...set];
    const terms = all.length > GLOSSARY_MAX ? all.slice(all.length - GLOSSARY_MAX) : all;
    writeJson(GLOSSARY_REL, { terms, updatedAt: new Date().toISOString() } satisfies Glossary);
}

/** 사용자 메타데이터 입력에서 용어 후보 추출 — 쉼표/슬래시/공백 구분 토큰 중 고유명사형. */
export function extractGlossaryCandidates(metadata: string): string[] {
    return (metadata || '')
        .split(/[,，/·;\n]+/)
        .map(t => t.replace(/^[\s:：\-–·]+|[\s:：\-–·]+$/g, ''))
        .filter(t => t.length >= 2 && t.length <= 30 && !/^\d+$/.test(t));
}
