/**
 * mtime-keyed file cache utility — 파일을 *parse 결과* 까지 캐싱.
 *
 * 배경: terminologyBlock.ts 와 termValidator.ts 가 같은 글로서리 파일에 *별도*
 * 캐시 2개를 보유. "함께 무효화" 가 사람이 손으로 보장하는 invariant — 한쪽만
 * 잊고 invalidate 안 하면 stale read 위험.
 *
 * 이 유틸은:
 *   - 같은 파일을 캐싱하는 모든 consumer 가 같은 cache 인스턴스 사용
 *   - mtime 자동 체크 — 파일이 바뀌면 자동 재read
 *   - `invalidate(filePath)` 가 한 번 호출되면 그 파일의 *모든 parse 결과* 무효화
 *
 * 사용:
 *   const cache = createMtimeFileCache<MyParsed>('terminology', (raw) => parseMine(raw));
 *   const data = cache.read('/path/to/file.md');  // 캐시 hit or read+parse
 *   cache.invalidate('/path/to/file.md');         // 캐시 비움
 */

import * as fs from 'fs';

interface CacheEntry<T> {
    mtimeMs: number;
    parsed: T;
}

export interface MtimeFileCache<T> {
    /** 파일 read + parse + 캐시. mtime 같으면 캐시 hit. 파일 없으면 null. */
    read(filePath: string): T | null;
    /** 특정 파일 캐시 무효화. */
    invalidate(filePath: string): void;
    /** 모든 캐시 비움. */
    clear(): void;
    /** 디버그용 — 현재 캐시 사이즈. */
    size(): number;
}

/**
 * 새 mtime-keyed 캐시 생성. `name` 은 디버그용 라벨 (consumer 식별).
 * `parse` 는 raw 파일 본문 → 파싱 결과. throw 시 read() 가 null 반환.
 */
export function createMtimeFileCache<T>(
    _name: string,
    parse: (raw: string, filePath: string) => T,
): MtimeFileCache<T> {
    const cache = new Map<string, CacheEntry<T>>();

    return {
        read(filePath: string): T | null {
            try {
                if (!fs.existsSync(filePath)) return null;
                const stat = fs.statSync(filePath);
                const cached = cache.get(filePath);
                if (cached && cached.mtimeMs === stat.mtimeMs) return cached.parsed;
                const raw = fs.readFileSync(filePath, 'utf-8');
                const parsed = parse(raw, filePath);
                cache.set(filePath, { mtimeMs: stat.mtimeMs, parsed });
                return parsed;
            } catch {
                return null;
            }
        },
        invalidate(filePath: string): void {
            cache.delete(filePath);
        },
        clear(): void {
            cache.clear();
        },
        size(): number {
            return cache.size;
        },
    };
}
