/**
 * ============================================================
 *  src/ui/hooks/useInputHistory.ts —— 输入历史 ring buffer + 持久化
 * ------------------------------------------------------------
 *  - ring buffer 上限 50 条
 *  - 持久化 ~/.minimal-agent/input-history.txt（一行一条 UTF-8）
 *    转义 `\\` → `\\\\`，`\n` → `\\n`，避免多行输入被换行符撕碎
 *  - API：push / prev / next / search / resetCursor
 *  - 核心逻辑抽到 createInputHistoryStore，方便测试 DI 注入 fs
 *    hook 自身只是 useRef + useEffect 包一下，渲染零依赖
 * ============================================================
 */
import { useEffect, useMemo, useRef } from 'react';
import { mkdir as fsMkdir, readFile as fsReadFile, writeFile as fsWriteFile } from 'node:fs/promises';
import { homedir } from 'node:os';
import { dirname, join } from 'node:path';
const MAX_ITEMS = 50;
const WRITE_DEBOUNCE_MS = 500;
/* ------------------------------------------------------------ *
 *  转义：换行 + 反斜杠
 * ------------------------------------------------------------ */
export function encodeHistoryLine(text) {
    return text.replace(/\\/g, '\\\\').replace(/\n/g, '\\n');
}
export function decodeHistoryLine(line) {
    // 状态机：遇到 \ 看下一个字符决定 \\ → \  或  \n → 真换行
    let out = '';
    let i = 0;
    while (i < line.length) {
        const ch = line[i];
        if (ch === '\\' && i + 1 < line.length) {
            const nxt = line[i + 1];
            if (nxt === '\\') {
                out += '\\';
                i += 2;
                continue;
            }
            if (nxt === 'n') {
                out += '\n';
                i += 2;
                continue;
            }
        }
        out += ch;
        i += 1;
    }
    return out;
}
export function createInputHistoryStore(opts = {}) {
    const fs = opts.fs ?? {
        readFile: (p) => fsReadFile(p, 'utf8'),
        writeFile: (p, d) => fsWriteFile(p, d, 'utf8'),
        mkdir: (p, o) => fsMkdir(p, o).then(() => undefined),
    };
    const historyPath = opts.historyPath ?? join(homedir(), '.minimal-agent', 'input-history.txt');
    const debounceMs = opts.writeDebounceMs ?? WRITE_DEBOUNCE_MS;
    let buffer = [];
    let cursor = 0;
    let writeTimer = null;
    let pendingWrite = null;
    async function doWrite() {
        try {
            await fs.mkdir(dirname(historyPath), { recursive: true });
            const data = buffer.map(encodeHistoryLine).join('\n');
            await fs.writeFile(historyPath, data);
        }
        catch {
            // 持久化失败不影响 UI；下次 push 还会再试
        }
    }
    function scheduleWrite() {
        if (debounceMs <= 0) {
            pendingWrite = doWrite();
            return;
        }
        if (writeTimer)
            clearTimeout(writeTimer);
        writeTimer = setTimeout(() => {
            writeTimer = null;
            pendingWrite = doWrite();
        }, debounceMs);
    }
    return {
        async load() {
            try {
                const raw = await fs.readFile(historyPath);
                if (!raw)
                    return;
                const lines = raw.split('\n').filter((l) => l.length > 0);
                buffer = lines.slice(-MAX_ITEMS).map(decodeHistoryLine);
                cursor = buffer.length;
            }
            catch {
                // 文件不存在 / 读不出 → 用空 buffer 起步
            }
        },
        push(text) {
            if (!text)
                return;
            // 连续重复去重：跟最后一条相同就忽略
            if (buffer.length > 0 && buffer[buffer.length - 1] === text) {
                cursor = buffer.length;
                return;
            }
            buffer.push(text);
            while (buffer.length > MAX_ITEMS)
                buffer.shift();
            cursor = buffer.length;
            scheduleWrite();
        },
        prev() {
            if (buffer.length === 0)
                return null;
            if (cursor <= 0)
                return null;
            cursor -= 1;
            return buffer[cursor] ?? null;
        },
        next() {
            if (buffer.length === 0)
                return null;
            if (cursor >= buffer.length)
                return null;
            cursor += 1;
            if (cursor >= buffer.length) {
                // 已走到 "新输入" 位（buffer 之后），返 null 让 UI 清空
                return null;
            }
            return buffer[cursor] ?? null;
        },
        search(query) {
            if (!query)
                return [];
            const hits = [];
            // 反向遍历，最近优先；同字符串只收一次
            const seen = new Set();
            for (let i = buffer.length - 1; i >= 0; i -= 1) {
                const item = buffer[i];
                if (item.includes(query) && !seen.has(item)) {
                    hits.push(item);
                    seen.add(item);
                }
            }
            return hits;
        },
        resetCursor() {
            cursor = buffer.length;
        },
        async flush() {
            if (writeTimer) {
                clearTimeout(writeTimer);
                writeTimer = null;
                pendingWrite = doWrite();
            }
            if (pendingWrite)
                await pendingWrite;
        },
        _snapshot() {
            return { buffer: [...buffer], cursor };
        },
    };
}
/* ------------------------------------------------------------ *
 *  React hook 门面：store 用 useRef 持有，启动时 useEffect 拉盘
 * ------------------------------------------------------------ */
export function useInputHistory(opts) {
    // 同一组件实例共享同一 store；options 变化不重建（输入历史 hook 不需要重建语义）
    const storeRef = useRef(null);
    if (storeRef.current === null) {
        storeRef.current = createInputHistoryStore(opts);
    }
    useEffect(() => {
        // 异步拉盘，不 await；UI 在拉盘前按 ↑ 只是空操作
        storeRef.current?.load();
    }, []);
    // 暴露稳定门面，绑定到内部 store
    return useMemo(() => {
        return {
            push: (t) => storeRef.current.push(t),
            prev: () => storeRef.current.prev(),
            next: () => storeRef.current.next(),
            search: (q) => storeRef.current.search(q),
            resetCursor: () => storeRef.current.resetCursor(),
        };
    }, []);
}
