import { expandTilde } from "../utils/path.js";
import { toCheckoutError } from "./checkout-git-utils.js";
const CHECKOUT_DIFF_WATCH_DEBOUNCE_MS = 150;
export class CheckoutDiffManager {
    constructor(options) {
        this.targets = new Map();
        this.workspaceGitService = options.workspaceGitService;
    }
    async subscribe(params, listener) {
        const cwd = params.cwd;
        const compare = this.normalizeCompare(params.compare);
        const target = await this.ensureTarget(cwd, compare);
        target.listeners.add(listener);
        const initial = target.latestPayload ??
            (await this.computeCheckoutDiffSnapshot(target.cwd, target.compare, {
                diffCwd: target.diffCwd,
            }));
        target.latestPayload = initial;
        target.latestFingerprint = JSON.stringify(initial);
        return {
            initial,
            unsubscribe: () => {
                this.removeListener(target.key, listener);
            },
        };
    }
    scheduleRefreshForCwd(cwd) {
        const resolvedCwd = expandTilde(cwd);
        for (const target of this.targets.values()) {
            if (target.cwd !== resolvedCwd && target.diffCwd !== resolvedCwd) {
                continue;
            }
            this.scheduleTargetRefresh(target);
        }
    }
    getMetrics() {
        let checkoutDiffSubscriptionCount = 0;
        for (const target of this.targets.values()) {
            checkoutDiffSubscriptionCount += target.listeners.size;
        }
        return {
            checkoutDiffTargetCount: this.targets.size,
            checkoutDiffSubscriptionCount,
            checkoutDiffWatcherCount: 0,
            checkoutDiffFallbackRefreshTargetCount: 0,
        };
    }
    dispose() {
        for (const target of this.targets.values()) {
            this.closeTarget(target);
        }
        this.targets.clear();
    }
    normalizeCompare(compare) {
        const ignoreWhitespace = compare.ignoreWhitespace === true;
        if (compare.mode === "uncommitted") {
            return { mode: "uncommitted", ignoreWhitespace };
        }
        if (compare.mode === "staged") {
            return { mode: "staged", ignoreWhitespace };
        }
        if (compare.mode === "unstaged") {
            return { mode: "unstaged", ignoreWhitespace };
        }
        const trimmedBaseRef = compare.baseRef?.trim();
        return trimmedBaseRef
            ? { mode: "base", baseRef: trimmedBaseRef, ignoreWhitespace }
            : { mode: "base", ignoreWhitespace };
    }
    buildTargetKey(cwd, compare) {
        return JSON.stringify([
            cwd,
            compare.mode,
            compare.mode === "base" ? (compare.baseRef ?? "") : "",
            compare.ignoreWhitespace === true,
        ]);
    }
    closeTarget(target) {
        if (target.debounceTimer) {
            clearTimeout(target.debounceTimer);
            target.debounceTimer = null;
        }
        target.workingTreeWatchUnsubscribe?.();
        target.workingTreeWatchUnsubscribe = null;
        target.listeners.clear();
    }
    removeListener(targetKey, listener) {
        const target = this.targets.get(targetKey);
        if (!target) {
            return;
        }
        target.listeners.delete(listener);
        if (target.listeners.size > 0) {
            return;
        }
        this.closeTarget(target);
        this.targets.delete(targetKey);
    }
    scheduleTargetRefresh(target) {
        if (target.debounceTimer) {
            clearTimeout(target.debounceTimer);
        }
        target.debounceTimer = setTimeout(() => {
            target.debounceTimer = null;
            void this.refreshTarget(target);
        }, CHECKOUT_DIFF_WATCH_DEBOUNCE_MS);
    }
    async computeCheckoutDiffSnapshot(cwd, compare, options) {
        const diffCwd = options?.diffCwd ?? cwd;
        try {
            const diffResult = await this.workspaceGitService.getCheckoutDiff(diffCwd, {
                mode: compare.mode,
                baseRef: compare.baseRef,
                ignoreWhitespace: compare.ignoreWhitespace,
                includeStructured: true,
            }, options?.force
                ? { force: true, reason: options.reason ?? "checkout-diff-refresh" }
                : undefined);
            const files = [...(diffResult.structured ?? [])];
            files.sort((a, b) => {
                if (a.path === b.path)
                    return 0;
                return a.path < b.path ? -1 : 1;
            });
            return {
                cwd,
                files,
                error: null,
            };
        }
        catch (error) {
            return {
                cwd,
                files: [],
                error: toCheckoutError(error),
            };
        }
    }
    async refreshTarget(target) {
        if (target.refreshPromise) {
            target.refreshQueued = true;
            return;
        }
        target.refreshPromise = (async () => {
            do {
                target.refreshQueued = false;
                const snapshot = await this.computeCheckoutDiffSnapshot(target.cwd, target.compare, {
                    diffCwd: target.diffCwd,
                    force: true,
                    reason: "working-tree-watch",
                });
                target.latestPayload = snapshot;
                const fingerprint = JSON.stringify(snapshot);
                if (fingerprint !== target.latestFingerprint) {
                    target.latestFingerprint = fingerprint;
                    for (const listener of target.listeners) {
                        listener(snapshot);
                    }
                }
            } while (target.refreshQueued);
        })();
        try {
            await target.refreshPromise;
        }
        finally {
            target.refreshPromise = null;
        }
    }
    async ensureTarget(cwd, compare) {
        const targetKey = this.buildTargetKey(cwd, compare);
        const existing = this.targets.get(targetKey);
        if (existing) {
            return existing;
        }
        const target = {
            key: targetKey,
            cwd,
            diffCwd: cwd,
            compare,
            listeners: new Set(),
            workingTreeWatchUnsubscribe: null,
            debounceTimer: null,
            refreshPromise: null,
            refreshQueued: false,
            latestPayload: null,
            latestFingerprint: null,
        };
        const { repoRoot, unsubscribe } = await this.workspaceGitService.requestWorkingTreeWatch(cwd, () => this.scheduleTargetRefresh(target));
        target.diffCwd = repoRoot ?? cwd;
        target.workingTreeWatchUnsubscribe = unsubscribe;
        this.targets.set(targetKey, target);
        return target;
    }
}
//# sourceMappingURL=checkout-diff-manager.js.map