import { watch, promises as fs } from 'node:fs';
import path from 'node:path';
import { sendFileChanged } from './session-output.js';
const watchedFiles = new Map();
const WATCH_DEBOUNCE_MS = 300;
function toDisplayPath(targetPath) {
    return String(targetPath || '').replace(/\\/g, '/');
}
function ensureInsideWorkspace(workspaceRoot, targetPath) {
    const resolvedTargetPath = path.resolve(targetPath);
    const relativePath = path.relative(workspaceRoot, resolvedTargetPath);
    if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
        throw new Error(`Target path is outside workspace: ${resolvedTargetPath}`);
    }
    return resolvedTargetPath;
}
async function resolveWatchTarget(params) {
    const requestedWorkspaceRoot = path.resolve(String(params.workspacePath || '').trim());
    const workspaceRoot = await fs.realpath(requestedWorkspaceRoot);
    const requestedFilePath = path.resolve(String(params.filePath || '').trim());
    const requestedRelativePath = path.relative(requestedWorkspaceRoot, requestedFilePath);
    if (requestedRelativePath.startsWith('..') || path.isAbsolute(requestedRelativePath)) {
        throw new Error(`Target path is outside workspace: ${requestedFilePath}`);
    }
    const filePath = ensureInsideWorkspace(workspaceRoot, path.join(workspaceRoot, requestedRelativePath));
    const realFilePath = await fs.realpath(filePath);
    ensureInsideWorkspace(workspaceRoot, realFilePath);
    const stat = await fs.stat(realFilePath);
    if (!stat.isFile()) {
        throw new Error(`Path is not a file: ${realFilePath}`);
    }
    return { workspaceRoot, filePath: realFilePath };
}
function watchKey(workspaceRoot, filePath) {
    return `${toDisplayPath(workspaceRoot)}::${toDisplayPath(filePath)}`;
}
function closeWatchRecord(key, record) {
    if (record.timer) {
        clearTimeout(record.timer);
        record.timer = null;
    }
    record.watcher.close();
    watchedFiles.delete(key);
}
async function resolveWatchKey(params) {
    const requestedWorkspaceRoot = path.resolve(String(params.workspacePath || '').trim());
    const workspaceRoot = await fs.realpath(requestedWorkspaceRoot);
    const requestedFilePath = path.resolve(String(params.filePath || '').trim());
    const requestedRelativePath = path.relative(requestedWorkspaceRoot, requestedFilePath);
    if (requestedRelativePath.startsWith('..') || path.isAbsolute(requestedRelativePath)) {
        throw new Error(`Target path is outside workspace: ${requestedFilePath}`);
    }
    const filePath = ensureInsideWorkspace(workspaceRoot, path.join(workspaceRoot, requestedRelativePath));
    return watchKey(workspaceRoot, filePath);
}
async function notifyFileChanged(record, eventType) {
    let size;
    let mtimeMs;
    try {
        const stat = await fs.stat(record.filePath);
        size = stat.size;
        mtimeMs = stat.mtimeMs;
    }
    catch {
        // Deletions or transient writes still notify the frontend so it can show a refresh error if needed.
    }
    await sendFileChanged({
        workspacePath: toDisplayPath(record.workspacePath),
        filePath: toDisplayPath(record.filePath),
        eventType,
        size,
        mtimeMs,
    });
}
function scheduleFileChanged(record, eventType) {
    if (record.timer) {
        clearTimeout(record.timer);
    }
    record.timer = setTimeout(() => {
        record.timer = null;
        notifyFileChanged(record, eventType).catch(() => undefined);
    }, WATCH_DEBOUNCE_MS);
}
/**
 * 开始监听工作区内单个文件，重复监听同一文件会复用已有 watcher。
 */
export async function watchWorkspaceFile(params) {
    const { workspaceRoot, filePath } = await resolveWatchTarget(params);
    const key = watchKey(workspaceRoot, filePath);
    const existingRecord = watchedFiles.get(key);
    if (existingRecord) {
        existingRecord.refCount += 1;
    }
    else {
        const record = {
            watcher: watch(filePath, (eventType) => scheduleFileChanged(record, String(eventType || 'change'))),
            workspacePath: workspaceRoot,
            filePath,
            timer: null,
            refCount: 1,
        };
        record.watcher.on('error', () => closeWatchRecord(key, record));
        watchedFiles.set(key, record);
    }
    return { ok: true, workspacePath: toDisplayPath(workspaceRoot), filePath: toDisplayPath(filePath) };
}
/**
 * 停止监听工作区内单个文件；未监听时保持幂等。
 */
export async function unwatchWorkspaceFile(params) {
    const key = await resolveWatchKey(params);
    const record = watchedFiles.get(key);
    if (record) {
        record.refCount -= 1;
        if (record.refCount <= 0) {
            closeWatchRecord(key, record);
        }
    }
    return { ok: true };
}
export function getWorkspaceWatchCountForTests() {
    return watchedFiles.size;
}
