/**
 * System handlers — /memory · /glossary · /help (인프라·관리·발견).
 *
 * v2.2.200 에서 slashRouter.ts 에서 분리. 4인 팀 운영 도메인 아닌
 * "ASTRA 자체 인프라" 슬래시 명령. teamops handlers 와 같은 패턴.
 */

import * as fs from 'fs';
import * as path from 'path';
import { registerSlashCommand, chunk, listSlashCommands } from '../datacollect/slashRouter';
import { getConfig } from '../../config';
import {
    MemoryManager,
    distillStaleEpisodes,
    getLastDistillationRun,
    recordDistillationRun,
    type DistillationArchiveMode,
} from '../../memory';
import {
    getGlossaryFilePath,
    GLOSSARY_TEMPLATE,
    clearGlossaryCache,
} from '../../retrieval/terminologyBlock';
import { clearTermValidatorCache } from '../../agent/termValidator';

// ─── /memory — 메모리 라이프사이클 관리 ─────────────────────────────────

function _formatDate(epoch: number | undefined): string {
    if (!epoch) return '-';
    return new Date(epoch).toISOString().slice(0, 10);
}

function _memoryOverview(view: any): void {
    const cfg = getConfig();
    const brainPath = cfg.localBrainPath;
    if (!brainPath) { chunk(view, '\n❌ Brain path 미설정.\n'); return; }

    const mgr = new MemoryManager(brainPath, {
        longTermMaxEntries: cfg.memoryLongTermFiles ?? 100,
        episodicMaxEpisodes: 50,
    });
    const lt = mgr.getLongTermMemory();
    const ep = mgr.getEpisodicMemory();
    const allLt = lt.getAllEntries({ includeExpired: true });
    const activeLt = lt.getAllEntries();
    const expiredLt = allLt.length - activeLt.length;
    const allEpisodes = ep.loadAllEpisodes();
    const stalePromoted = allEpisodes.filter((e) => e.promoted).length;
    const staleCandidates = ep.findStaleEpisodes(cfg.distillationAgeThresholdDays).length;
    const last = getLastDistillationRun(brainPath);

    chunk(view, '\n🧠 **/memory — 메모리 라이프사이클**\n');
    chunk(view, `\n## 📊 현재 상태\n`);
    chunk(view, `- **LongTerm**: 활성 ${activeLt.length}개${expiredLt > 0 ? ` (만료 ${expiredLt}개 숨김)` : ''}\n`);

    const catCounts = new Map<string, number>();
    for (const e of activeLt) catCounts.set(e.category, (catCounts.get(e.category) || 0) + 1);
    if (catCounts.size > 0) {
        const parts = [...catCounts.entries()].map(([c, n]) => `${c} ${n}`).join(' · ');
        chunk(view, `  - 카테고리: ${parts}\n`);
    }

    chunk(view, `- **Episodic**: 전체 ${allEpisodes.length}개 (승급 ${stalePromoted} · 미승급 stale 후보 ${staleCandidates})\n`);

    chunk(view, `\n## 🔄 Distillation\n`);
    chunk(view, `- 임계: **${cfg.distillationAgeThresholdDays}일** 이상 stale episode → LongTerm 'episode-digest' 승급\n`);
    chunk(view, `- 자동 트리거 간격: ${cfg.distillationIntervalDays}일\n`);
    chunk(view, `- Archive 모드: \`${cfg.distillationArchiveMode}\`\n`);
    if (last) {
        const ago = Math.floor((Date.now() - last.timestamp) / (24 * 60 * 60 * 1000));
        chunk(view, `- 마지막 실행: ${_formatDate(last.timestamp)} (${ago}일 전) — 승급 ${last.report?.promotedCount ?? 0}개\n`);
    } else {
        chunk(view, `- 마지막 실행: _없음_ — \`/memory distill\` 로 첫 실행\n`);
    }

    const upcoming = activeLt
        .filter((e) => e.expiresAt && (e.expiresAt - Date.now()) <= 30 * 24 * 60 * 60 * 1000)
        .sort((a, b) => (a.expiresAt || 0) - (b.expiresAt || 0));
    if (upcoming.length > 0) {
        chunk(view, `\n## ⏰ 30일 내 만료 LongTerm (${upcoming.length}개)\n`);
        for (const e of upcoming.slice(0, 5)) {
            const days = Math.ceil(((e.expiresAt || 0) - Date.now()) / (24 * 60 * 60 * 1000));
            chunk(view, `- D-${days} [${e.category}] \`${e.id.slice(0, 8)}\` ${e.content.slice(0, 80)}\n`);
        }
        if (upcoming.length > 5) chunk(view, `- _…+${upcoming.length - 5}개_\n`);
    }

    chunk(view, `\n_명령어:_ \`/memory distill\` · \`/memory expire <id> <YYYY-MM-DD>\` · \`/memory list-expiring [days]\` · \`/memory help\`\n`);
}

async function runMemory(arg: string, view: any): Promise<boolean> {
    const trimmed = arg.trim();
    if (!trimmed) { _memoryOverview(view); return true; }

    const parts = trimmed.split(/\s+/);
    const sub = parts[0].toLowerCase();
    const cfg = getConfig();
    const brainPath = cfg.localBrainPath;
    if (!brainPath) { chunk(view, '\n❌ Brain path 미설정.\n'); return true; }

    if (sub === 'help' || sub === '?') {
        chunk(view, [
            '\n🧠 **/memory — 메모리 라이프사이클**',
            '',
            '사용법:',
            '  `/memory`                                — 현재 상태 (LongTerm/Episodic 카운트, distillation, 만료 임박)',
            '  `/memory distill [age_days]`             — Stale episodes → LongTerm digest 승급 (기본 30일 임계)',
            '  `/memory expire <id-prefix> <YYYY-MM-DD>` — LongTerm entry 에 만료일 설정',
            '  `/memory list-expiring [days]`           — N일 내 만료 LongTerm 목록 (기본 30)',
            '  `/memory list-promoted`                  — 승급된 episodes (digest 형태로 LongTerm 에 살아 있음)',
            '',
            'Temporal Markers: LongTerm entry 의 expiresAt < now 이면 검색에서 자동 제외.',
            'Distillation Loop: 자동 트리거는 세션 종료 시 (interval 기준), 수동은 `/memory distill`.',
            '저장: `{brainPath}/memory/long_term.json` + `{brainPath}/memory/episodes/*.json`.\n',
        ].join('\n'));
        return true;
    }

    if (sub === 'distill') {
        const ageOverride = parts[1] ? parseInt(parts[1], 10) : undefined;
        const age = Number.isFinite(ageOverride) && (ageOverride as number) > 0
            ? (ageOverride as number)
            : cfg.distillationAgeThresholdDays;
        chunk(view, `\n🔄 Distillation 시작 — ${age}일 이상 stale episodes 대상...\n`);
        const mgr = new MemoryManager(brainPath);
        const report = distillStaleEpisodes(mgr.getEpisodicMemory(), mgr.getLongTermMemory(), brainPath, {
            ageThresholdDays: age,
            archiveMode: cfg.distillationArchiveMode as DistillationArchiveMode,
        });
        recordDistillationRun(brainPath, report);
        chunk(view, `\n✅ **Distillation 완료** (${report.durationMs}ms)\n`);
        chunk(view, `- 후보 ${report.candidateCount}개 → 승급 ${report.promotedCount}개`);
        chunk(view, cfg.distillationArchiveMode === 'archive-file' ? ` · 아카이브 ${report.archivedCount}개\n` : '\n');
        if (report.skipped.length > 0) {
            chunk(view, `- ⚠️ 스킵 ${report.skipped.length}개:\n`);
            for (const s of report.skipped.slice(0, 3)) chunk(view, `  - \`${s.episodeId.slice(0, 8)}\`: ${s.reason}\n`);
        }
        if (report.longTermDigestIds.length > 0) {
            chunk(view, `\n_생성된 digest IDs (앞 8자):_ ${report.longTermDigestIds.slice(0, 5).map((id) => `\`${id.slice(0, 8)}\``).join(' · ')}\n`);
        }
        return true;
    }

    if (sub === 'expire') {
        const idPrefix = parts[1];
        const dateStr = parts[2];
        if (!idPrefix || !dateStr) {
            chunk(view, '\n❌ 사용법: `/memory expire <id-prefix> <YYYY-MM-DD>`\n예: `/memory expire a3f7d2c9 2026-09-30`\n');
            return true;
        }
        if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
            chunk(view, `\n❌ 날짜 형식: YYYY-MM-DD (입력: "${dateStr}")\n`);
            return true;
        }
        const expiresAt = Date.parse(dateStr + 'T23:59:59');
        if (!Number.isFinite(expiresAt)) { chunk(view, `\n❌ 날짜 파싱 실패: "${dateStr}"\n`); return true; }
        const mgr = new MemoryManager(brainPath);
        const updated = mgr.getLongTermMemory().setExpiration(idPrefix, expiresAt);
        if (!updated) { chunk(view, `\n❌ "${idPrefix}" prefix 매치 entry 없음.\n`); return true; }
        chunk(view, `\n⏰ **${updated.category}** \`${updated.id.slice(0, 8)}\` 만료일 설정 → ${dateStr}\n`);
        chunk(view, `> ${updated.content.slice(0, 120)}\n`);
        return true;
    }

    if (sub === 'list-expiring') {
        const days = parts[1] ? parseInt(parts[1], 10) : 30;
        const window = Number.isFinite(days) && days > 0 ? days : 30;
        const mgr = new MemoryManager(brainPath);
        const all = mgr.getLongTermMemory().getAllEntries();
        const horizon = Date.now() + window * 24 * 60 * 60 * 1000;
        const upcoming = all
            .filter((e) => e.expiresAt && e.expiresAt <= horizon)
            .sort((a, b) => (a.expiresAt || 0) - (b.expiresAt || 0));
        if (upcoming.length === 0) { chunk(view, `\nℹ️ ${window}일 내 만료 예정 entry 없음.\n`); return true; }
        chunk(view, `\n⏰ **${window}일 내 만료 LongTerm (${upcoming.length}개)**\n\n`);
        for (const e of upcoming) {
            const d = Math.ceil(((e.expiresAt || 0) - Date.now()) / (24 * 60 * 60 * 1000));
            chunk(view, `- D-${d} [${e.category}] \`${e.id.slice(0, 8)}\` ${e.content.slice(0, 100)}\n`);
        }
        return true;
    }

    if (sub === 'list-promoted') {
        const mgr = new MemoryManager(brainPath);
        const promoted = mgr.getEpisodicMemory().loadAllEpisodes().filter((e) => e.promoted);
        if (promoted.length === 0) { chunk(view, '\nℹ️ 승급된 episode 없음. `/memory distill` 로 첫 승급.\n'); return true; }
        chunk(view, `\n📚 **승급된 episodes (${promoted.length}개)** — LongTerm 에 digest 형태로 살아 있음\n\n`);
        for (const e of promoted.slice(0, 15)) {
            const date = (new Date(e.timestamp)).toISOString().slice(0, 10);
            chunk(view, `- \`${date}\` ${e.title} → digest \`${(e.promotedToLongTermId || '').slice(0, 8)}\`\n`);
        }
        if (promoted.length > 15) chunk(view, `- _…+${promoted.length - 15}개_\n`);
        return true;
    }

    chunk(view, `\n❌ 알 수 없는 서브명령: "${sub}". \`/memory help\` 참조.\n`);
    return true;
}

// ─── /glossary — Terminology Dictionary 사용자 진입점 ───────────────────

async function runGlossary(arg: string, view: any): Promise<boolean> {
    const trimmed = arg.trim().toLowerCase();
    const cfg = getConfig();
    const fp = getGlossaryFilePath(cfg.glossaryPath || '.astra/glossary.md');

    if (trimmed === 'help' || trimmed === '?') {
        chunk(view, [
            '\n📖 **/glossary — Terminology Dictionary**',
            '',
            '사용법:',
            '  `/glossary`             — 상태 (파일 존재/크기, system prompt 주입 여부)',
            '  `/glossary path`        — 글로서리 파일 절대 경로',
            '  `/glossary init`        — 권장 템플릿으로 글로서리 파일 생성 (이미 있으면 덮어쓰지 않음)',
            '  `/glossary reload`      — 캐시 비우기 (편집 직후 즉시 반영 강제)',
            '',
            '편집 방법: 위 path 의 markdown 파일을 *VS Code 에서 직접 편집*.',
            '효과: 다음 채팅 turn 부터 ASTRA 시스템 프롬프트의 [TERMINOLOGY DICTIONARY] 블록에 자동 주입.',
            '글로서리 본문은 자유 markdown — H2/H3 섹션 구분 권장.\n',
        ].join('\n'));
        return true;
    }

    if (!fp) { chunk(view, '\n❌ Workspace 폴더 없음 — 글로서리 사용 불가.\n'); return true; }

    if (trimmed === 'path') { chunk(view, `\n📂 \`${fp}\`\n`); return true; }

    if (trimmed === 'init') {
        if (fs.existsSync(fp)) {
            chunk(view, `\nℹ️ 이미 존재: \`${fp}\` — 덮어쓰지 않음. 새로 시작하려면 파일 직접 삭제 후 재실행.\n`);
            return true;
        }
        try {
            fs.mkdirSync(path.dirname(fp), { recursive: true });
            fs.writeFileSync(fp, GLOSSARY_TEMPLATE, 'utf-8');
            clearGlossaryCache();
            chunk(view, `\n✅ 글로서리 초기화 — \`${fp}\`\n`);
            chunk(view, '편집 후 다음 채팅 turn 부터 자동 반영. 즉시 반영 강제는 `/glossary reload`.\n');
        } catch (e: any) {
            chunk(view, `\n❌ 생성 실패: ${e?.message || String(e)}\n`);
        }
        return true;
    }

    if (trimmed === 'reload') {
        clearGlossaryCache();
        clearTermValidatorCache();
        chunk(view, '\n🔄 글로서리 캐시 비움 (system prompt + Term Validator 모두). 다음 채팅 turn 에 파일 재읽기.\n');
        return true;
    }

    chunk(view, '\n📖 **/glossary — Terminology Dictionary 상태**\n');
    chunk(view, `\n- 경로: \`${fp}\`\n`);
    if (!fs.existsSync(fp)) {
        chunk(view, `- ❌ 파일 없음 — \`/glossary init\` 로 권장 템플릿 생성 가능\n`);
        chunk(view, '- system prompt 주입: ⊘ (no-op)\n');
        return true;
    }
    try {
        const stat = fs.statSync(fp);
        const raw = fs.readFileSync(fp, 'utf-8');
        const len = raw.length;
        const cap = cfg.glossaryMaxBodyLength ?? 4000;
        const truncated = len > cap;
        const mtime = new Date(stat.mtimeMs).toISOString().slice(0, 10);
        chunk(view, `- ✅ 파일 ${len}자 (${truncated ? `cap ${cap} 초과 — 잘림` : `cap ${cap} 이내`}) · 마지막 편집 ${mtime}\n`);
        chunk(view, `- Enabled: ${cfg.glossaryEnabled !== false ? '✓' : '✗'}\n`);
        chunk(view, `- system prompt 주입: ${cfg.glossaryEnabled !== false && len > 0 ? '✓ 매 turn 자동' : '⊘'}\n`);
        const preview = raw.split('\n').slice(0, 5).join('\n');
        chunk(view, `\n### 미리보기 (첫 5줄)\n\`\`\`\n${preview}\n\`\`\`\n`);
    } catch (e: any) {
        chunk(view, `- ⚠️ 읽기 실패: ${e?.message || String(e)}\n`);
    }
    return true;
}

// ─── /help — 카테고리별 슬래시 명령 브라우저 ─────────────────────────────

interface HelpCategory {
    title: string;
    emoji: string;
    match: (name: string) => boolean;
    blurb?: string;
}

const HELP_CATEGORIES: HelpCategory[] = [
    {
        title: '일과 리듬 (Daily Cycle)',
        emoji: '☀️',
        match: (n) => ['/morning', '/evening', '/weekly', '/cohort', '/standup'].includes(n),
        blurb: '아침 (`/morning`) · 저녁 (`/evening`) · 주말 (`/weekly`) · 분기 (`/cohort`) · 팀 공유 (`/standup`)',
    },
    {
        title: '4인 팀 운영 트래커',
        emoji: '🏢',
        match: (n) => ['/runway', '/customers', '/hire'].includes(n),
        blurb: '재무 (`/runway`) · 매출 (`/customers`) · 채용 (`/hire`) — 모두 event-sourced `.astra/*.jsonl`',
    },
    {
        title: '작업·블로커·1:1',
        emoji: '📋',
        match: (n) => ['/task', '/blocked', '/onesie', '/decisions'].includes(n),
        blurb: 'Google Tasks 등록 (`/task`) · 지연 분석 (`/blocked`) · 멤버 1:1 카드 (`/onesie`) · ADR (`/decisions`)',
    },
    {
        title: '외부 출력·기록',
        emoji: '✉️',
        match: (n) => ['/draft', '/feedback'].includes(n),
        blurb: 'email/slack/blog/newsletter 초안 (`/draft`) · 고객 피드백 누적+분석 (`/feedback`)',
    },
    {
        title: '리서치·분석',
        emoji: '🔬',
        match: (n) => ['/benchmark', '/youtube', '/blog', '/wikify', '/meet'].includes(n),
        blurb: 'Datacollect bridge 통합 — 웹 벤치마크 / YouTube 4-렌즈 / Blog 파이프라인 / Wikify / 회의록 (NotebookLM Deep Research 는 로컬 Datacollect 앱으로 분리)',
    },
    {
        title: '시스템·메모리',
        emoji: '⚙️',
        match: (n) => ['/memory', '/glossary'].includes(n),
        blurb: 'Temporal Markers + Distillation (`/memory`) · 용어집 (`/glossary`)',
    },
    {
        title: '주식·외부',
        emoji: '📈',
        match: (n) => ['/stocks'].includes(n),
        blurb: 'Yahoo 가격 + Sheets 동기화 + 텔레그램 보고서 + LLM judge',
    },
];

async function runHelp(arg: string, view: any): Promise<boolean> {
    const trimmed = arg.trim().toLowerCase();
    const allCommands = listSlashCommands();

    if (trimmed && trimmed !== 'help' && trimmed !== '?') {
        const target = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
        const def = allCommands.find((c) => c.name.toLowerCase() === target);
        if (!def) {
            chunk(view, `\n❌ 명령 "${target}" 등록되지 않음. \`/help\` 로 전체 목록.\n`);
            return true;
        }
        chunk(view, `\n📘 **${def.name}**\n\n${def.description || '_(설명 없음)_'}\n\n`);
        chunk(view, `세부 도움말은 \`${def.name} help\` 또는 \`${def.name} ?\` 로 직접 호출 (대부분 명령이 자체 도움말 제공).\n`);
        return true;
    }

    chunk(view, `\n📚 **ASTRA 슬래시 명령 — 전체 ${allCommands.length}개**\n`);
    chunk(view, '_사용: `/<명령>` · 세부 도움말: `/<명령> help` · 특정 명령 검색: `/help <명령>`_\n');

    const usedNames = new Set<string>();
    for (const cat of HELP_CATEGORIES) {
        const matched = allCommands.filter((c) => cat.match(c.name.toLowerCase()));
        if (matched.length === 0) continue;
        chunk(view, `\n## ${cat.emoji} ${cat.title}\n`);
        if (cat.blurb) chunk(view, `_${cat.blurb}_\n\n`);
        for (const c of matched) {
            usedNames.add(c.name.toLowerCase());
            chunk(view, `- \`${c.name}\` — ${c.description || '_(설명 없음)_'}\n`);
        }
    }

    const uncategorized = allCommands.filter((c) => !usedNames.has(c.name.toLowerCase()) && c.name !== '/help');
    if (uncategorized.length > 0) {
        chunk(view, '\n## 🔌 기타·외부 플러그인\n\n');
        for (const c of uncategorized) {
            chunk(view, `- \`${c.name}\` — ${c.description || '_(설명 없음)_'}\n`);
        }
    }

    const cfg = getConfig();
    chunk(view, '\n---\n\n## 🛡️ ASTRA 추론 엔진 (v2.2.183~ 누적)\n');
    chunk(view, '_각 turn 시스템 프롬프트 자동 주입 (casual 모드 제외):_\n\n');
    const engineStatus = [
        { label: 'Intent Clarification', on: cfg.intentClarificationEnabled !== false, key: '[INTENT CLARIFICATION GUIDANCE]', when: '답변 시작 전' },
        { label: 'Terminology Dictionary', on: cfg.glossaryEnabled !== false, key: '[TERMINOLOGY DICTIONARY]', when: '답변 작성 중' },
        { label: 'Conflict Surface', on: cfg.conflictHighlightingEnabled !== false, key: '[CONFLICT WARNINGS]', when: '답변 작성 중' },
        { label: 'Chain-of-Verification (CoVe)', on: cfg.coveEnabled !== false, key: '[VERIFICATION CHECKLIST]', when: '답변 작성 중' },
        { label: 'Citation Trace', on: cfg.citationTraceEnabled !== false, key: '[CITATION TRACE]', when: '답변 끝' },
        { label: 'Post-hoc Self-Check', on: cfg.selfCheckEnabled === true, key: 'footer (별도 LLM 호출)', when: '답변 완료 후' },
    ];
    for (const e of engineStatus) {
        chunk(view, `- ${e.on ? '✓' : '⊘'} **${e.label}** — \`${e.key}\` _(${e.when})_\n`);
    }
    chunk(view, '\n_추가 검색 신호:_ Recency · Actionability · Hierarchical Level · Semantic Re-rank (opt-in)\n');
    chunk(view, '_메모리 관리:_ Temporal Markers (만료일) · Distillation Loop (stale episodes → LongTerm digest)\n');

    return true;
}

// ─── 등록 ─────────────────────────────────────────────────────────────────

registerSlashCommand({ name: '/memory', description: '메모리 라이프사이클 — Temporal Markers (만료일) + Distillation Loop (stale episodes → LongTerm digest)', handler: runMemory });
registerSlashCommand({ name: '/glossary', description: 'Terminology Dictionary — 표준 용어집 (.astra/glossary.md) 상태/생성/리로드. 다음 turn 부터 system prompt 자동 주입', handler: runGlossary });
registerSlashCommand({ name: '/help', description: '슬래시 명령 전체 목록 (카테고리별) + ASTRA 추론 엔진 상태. `/help <명령>` 으로 특정 명령 정보.', handler: runHelp });
