import { watch } from "node:fs";
import { readFile, readdir } from "node:fs/promises";
import { join, resolve } from "node:path";
import { LRUCache } from "lru-cache";
import pLimit from "p-limit";
import { getCheckoutDiff, getCheckoutSnapshotFacts, getCheckoutShortstat, getCheckoutStatus, getPullRequestStatus, hasOriginRemote, listBranchSuggestions, resolveRepositoryDefaultBranch, resolveBranchCheckout, resolveAbsoluteGitDir, } from "../utils/checkout-git.js";
import { createGitHubService, } from "../services/github-service.js";
import { parseGitRevParsePath } from "../utils/git-rev-parse-path.js";
import { runGitCommand } from "../utils/run-git-command.js";
import { resolveGitHubRemote } from "../utils/github-remote.js";
import { listPaseoWorktrees } from "../utils/worktree.js";
import { READ_ONLY_GIT_ENV } from "./checkout-git-utils.js";
import { buildWorkspaceGitMetadataFromSnapshot, } from "./workspace-git-metadata.js";
import { checkoutLiteFromGitSnapshot, normalizeWorkspaceId } from "./workspace-registry-model.js";
const WORKSPACE_GIT_WATCH_DEBOUNCE_MS = 500;
const BACKGROUND_GIT_FETCH_INTERVAL_MS = 180000;
export const WORKSPACE_GIT_SELF_HEAL_INTERVAL_MS = 60000;
const WORKING_TREE_WATCH_FALLBACK_REFRESH_MS = 5000;
// Auxiliary reads may reuse cached values within this window; snapshots do not expire on read.
const WORKSPACE_GIT_AUXILIARY_READ_TTL_MS = 15000;
// Non-forced refresh triggers share this minimum gap to absorb watcher/self-heal bursts; force bypasses it.
const WORKSPACE_GIT_INTERNAL_MIN_GAP_MS = 2000;
// Heavy values (multi-MB highlighted diffs); cap aggressively. Ephemeral worktree cwds would otherwise pile up forever.
const WORKSPACE_GIT_CHECKOUT_DIFF_CACHE_MAX = 64;
// Small values (booleans, short strings, small arrays); generous cap.
const WORKSPACE_GIT_AUXILIARY_CACHE_MAX = 256;
const WORKSPACE_GIT_FACTS_REUSE_TTL_MS = 1000;
const LINUX_WATCH_MAX_DIRS = 5000;
const LINUX_WATCH_REFRESH_COOLDOWN_MS = 2000;
const LINUX_WATCH_IGNORE_TTL_MS = 5 * 60 * 1000;
const linuxWatchReaddirConcurrency = parseInt(process.env.PASEO_LINUX_WATCH_READDIR_CONCURRENCY ?? "16", 10) || 16;
const linuxWatchReaddirLimit = pLimit(linuxWatchReaddirConcurrency);
function buildDefaultWorkspaceGitServiceDeps() {
    return {
        watch,
        readdir,
        getCheckoutSnapshotFacts,
        getCheckoutStatus,
        getCheckoutShortstat,
        getCheckoutDiff,
        getPullRequestStatus,
        resolveBranchCheckout,
        resolveRepositoryDefaultBranch,
        listBranchSuggestions,
        listPaseoWorktrees,
        github: createGitHubService(),
        resolveAbsoluteGitDir,
        hasOriginRemote,
        runGitFetch,
        runGitCommand,
        now: () => new Date(),
    };
}
function resolveWorkspaceGitServiceDeps(deps) {
    return { ...buildDefaultWorkspaceGitServiceDeps(), ...deps };
}
export class WorkspaceGitServiceImpl {
    constructor(options) {
        this.snapshotUpdatedListeners = new Set();
        this.workspaceTargets = new Map();
        this.repoTargets = new Map();
        this.workingTreeWatchTargets = new Map();
        this.workingTreeWatchSetups = new Map();
        this.linuxIgnoredDirsCache = new Map();
        this.branchValidationCache = new LRUCache({ max: WORKSPACE_GIT_AUXILIARY_CACHE_MAX });
        this.localBranchCache = new LRUCache({ max: WORKSPACE_GIT_AUXILIARY_CACHE_MAX });
        this.branchSuggestionsCache = new LRUCache({ max: WORKSPACE_GIT_AUXILIARY_CACHE_MAX });
        this.stashListCache = new LRUCache({ max: WORKSPACE_GIT_AUXILIARY_CACHE_MAX });
        this.worktreeListCache = new LRUCache({ max: WORKSPACE_GIT_AUXILIARY_CACHE_MAX });
        this.defaultBranchCache = new LRUCache({ max: WORKSPACE_GIT_AUXILIARY_CACHE_MAX });
        this.checkoutDiffCache = new LRUCache({ max: WORKSPACE_GIT_CHECKOUT_DIFF_CACHE_MAX });
        this.logger = options.logger.child({ module: "workspace-git-service" });
        this.paseoHome = options.paseoHome;
        this.worktreesRoot = options.worktreesRoot;
        this.deps = resolveWorkspaceGitServiceDeps(options.deps);
    }
    registerWorkspace(params, listener) {
        const cwd = normalizeWorkspaceId(params.cwd);
        const target = this.ensureWorkspaceTarget(cwd);
        target.listeners.add(listener);
        if (target.listeners.size === 1) {
            this.startWorkspaceSubscriptionTimers(target);
        }
        if (!target.latestSnapshot) {
            this.scheduleInitialWorkspaceRefresh(target);
        }
        this.scheduleWorkspaceObservationSetup(target);
        return {
            unsubscribe: () => {
                this.removeWorkspaceListener(cwd, listener);
            },
        };
    }
    onSnapshotUpdated(listener) {
        this.snapshotUpdatedListeners.add(listener);
        return {
            unsubscribe: () => {
                this.snapshotUpdatedListeners.delete(listener);
            },
        };
    }
    async getSnapshot(cwd, options) {
        cwd = normalizeWorkspaceId(cwd);
        const request = this.normalizeRefreshRequest(options, "getSnapshot", true);
        const target = this.ensureWorkspaceTarget(cwd);
        if (!request.force && target.latestSnapshot) {
            return target.latestSnapshot;
        }
        return this.requestWorkspaceSnapshot(target, request);
    }
    async getCheckout(cwd) {
        const normalizedCwd = normalizeWorkspaceId(cwd);
        try {
            const status = await this.deps.getCheckoutStatus(normalizedCwd, {
                paseoHome: this.paseoHome,
                worktreesRoot: this.worktreesRoot,
                logger: this.logger,
            });
            if (!status.isGit) {
                return checkoutLiteFromGitSnapshot(normalizedCwd, {
                    isGit: false,
                    currentBranch: null,
                    remoteUrl: null,
                    repoRoot: null,
                    isPaseoOwnedWorktree: false,
                    mainRepoRoot: null,
                });
            }
            return checkoutLiteFromGitSnapshot(normalizedCwd, {
                isGit: true,
                currentBranch: status.currentBranch,
                remoteUrl: status.remoteUrl,
                repoRoot: status.repoRoot,
                isPaseoOwnedWorktree: status.isPaseoOwnedWorktree,
                mainRepoRoot: status.mainRepoRoot,
            });
        }
        catch {
            return checkoutLiteFromGitSnapshot(normalizedCwd, {
                isGit: false,
                currentBranch: null,
                remoteUrl: null,
                repoRoot: null,
                isPaseoOwnedWorktree: false,
                mainRepoRoot: null,
            });
        }
    }
    peekSnapshot(cwd) {
        cwd = normalizeWorkspaceId(cwd);
        return this.workspaceTargets.get(cwd)?.latestSnapshot ?? null;
    }
    getCheckoutDiff(cwd, options, readOptions) {
        const normalizedCwd = normalizeWorkspaceId(cwd);
        const normalizedOptions = this.normalizeCheckoutDiffOptions(options);
        const key = this.buildCheckoutDiffCacheKey(normalizedCwd, normalizedOptions);
        return this.readAuxiliaryCache(this.checkoutDiffCache, key, readOptions, () => this.deps.getCheckoutDiff(normalizedCwd, normalizedOptions, {
            paseoHome: this.paseoHome,
            worktreesRoot: this.worktreesRoot,
        }));
    }
    normalizeCheckoutDiffOptions(options) {
        return {
            mode: options.mode,
            ...(options.mode === "base" && options.baseRef !== undefined
                ? { baseRef: options.baseRef }
                : {}),
            ...(options.ignoreWhitespace === true ? { ignoreWhitespace: true } : {}),
            ...(options.includeStructured === true ? { includeStructured: true } : {}),
        };
    }
    buildCheckoutDiffCacheKey(cwd, options) {
        // Diff content varies by compare signature. Keep the cache per exact diff read shape so
        // hot diff panes coalesce while base refs and rendering options never share stale patches.
        return JSON.stringify([
            "checkout-diff",
            cwd,
            options.mode,
            options.mode === "base" ? (options.baseRef ?? null) : null,
            options.ignoreWhitespace === true,
            options.includeStructured === true,
        ]);
    }
    validateBranchRef(cwd, ref, options) {
        const normalizedCwd = normalizeWorkspaceId(cwd);
        const normalizedRef = ref.trim();
        const key = JSON.stringify(["branch-validation", normalizedCwd, normalizedRef]);
        return this.readAuxiliaryCache(this.branchValidationCache, key, options, () => this.deps.resolveBranchCheckout(normalizedCwd, normalizedRef));
    }
    hasLocalBranch(cwd, branch, options) {
        const normalizedCwd = normalizeWorkspaceId(cwd);
        const normalizedBranch = branch.trim();
        const ref = `refs/heads/${normalizedBranch}`;
        const key = JSON.stringify(["local-branch", normalizedCwd, ref]);
        return this.readAuxiliaryCache(this.localBranchCache, key, options, async () => {
            const result = await this.deps.runGitCommand(["rev-parse", "--verify", "--quiet", ref], {
                cwd: normalizedCwd,
                envOverlay: READ_ONLY_GIT_ENV,
                acceptExitCodes: [0, 1],
            });
            return result.exitCode === 0;
        });
    }
    suggestBranchesForCwd(cwd, options, readOptions) {
        const normalizedCwd = normalizeWorkspaceId(cwd);
        const query = options?.query ?? "";
        const limit = options?.limit;
        const key = JSON.stringify(["branch-suggestions", normalizedCwd, query, limit ?? null]);
        return this.readAuxiliaryCache(this.branchSuggestionsCache, key, readOptions, () => this.deps.listBranchSuggestions(normalizedCwd, options));
    }
    listStashes(cwd, options, readOptions) {
        const normalizedCwd = normalizeWorkspaceId(cwd);
        const paseoOnly = options?.paseoOnly !== false;
        const key = JSON.stringify(["stashes", normalizedCwd, paseoOnly]);
        return this.readAuxiliaryCache(this.stashListCache, key, readOptions, async () => {
            const { stdout } = await this.deps.runGitCommand(["stash", "list", "--format=%gd%x00%s"], {
                cwd: normalizedCwd,
                envOverlay: READ_ONLY_GIT_ENV,
            });
            return parseWorkspaceGitStashList(stdout, { paseoOnly });
        });
    }
    async listWorktrees(cwdOrRepoRoot, options) {
        const repoRoot = await this.resolveRepoRoot(cwdOrRepoRoot, options);
        const key = JSON.stringify(["worktrees", repoRoot]);
        return this.readAuxiliaryCache(this.worktreeListCache, key, options, () => this.deps.listPaseoWorktrees({
            cwd: repoRoot,
            paseoHome: this.paseoHome,
            worktreesRoot: this.worktreesRoot,
        }));
    }
    async resolveRepoRoot(cwd, options) {
        const snapshot = await this.getSnapshot(cwd, options);
        if (!snapshot.git.isGit) {
            throw new Error("Create worktree requires a git repository");
        }
        return snapshot.git.isPaseoOwnedWorktree
            ? (snapshot.git.mainRepoRoot ?? snapshot.git.repoRoot ?? normalizeWorkspaceId(cwd))
            : (snapshot.git.repoRoot ?? normalizeWorkspaceId(cwd));
    }
    async resolveDefaultBranch(cwdOrRepoRoot, options) {
        const cwd = normalizeWorkspaceId(cwdOrRepoRoot);
        const key = JSON.stringify(["default-branch", cwd]);
        return this.readAuxiliaryCache(this.defaultBranchCache, key, options, async () => {
            const defaultBranch = await this.deps.resolveRepositoryDefaultBranch(cwd);
            if (!defaultBranch) {
                throw new Error("Unable to resolve repository default branch");
            }
            return defaultBranch;
        });
    }
    async getWorkspaceGitMetadata(cwd, options) {
        const snapshot = await this.getSnapshot(cwd, options);
        const directoryName = options?.directoryName ?? normalizeWorkspaceId(cwd).split(/[\\/]/).findLast(Boolean) ?? cwd;
        return buildWorkspaceGitMetadataFromSnapshot({
            cwd: normalizeWorkspaceId(cwd),
            directoryName,
            isGit: snapshot.git.isGit,
            repoRoot: snapshot.git.repoRoot,
            mainRepoRoot: snapshot.git.mainRepoRoot,
            currentBranch: snapshot.git.currentBranch,
            remoteUrl: snapshot.git.remoteUrl,
        });
    }
    async resolveRepoRemoteUrl(cwd, options) {
        const snapshot = await this.getSnapshot(cwd, options);
        return snapshot.git.remoteUrl;
    }
    async refresh(cwd, _options) {
        cwd = normalizeWorkspaceId(cwd);
        const target = this.ensureWorkspaceTarget(cwd);
        await this.refreshWorkspaceTarget(target, {
            force: false,
            includeGitHub: false,
            reason: "refresh",
            notify: true,
        });
        this.scheduleWorkspaceObservationSetup(target);
    }
    async requestWorkingTreeWatch(cwd, onChange) {
        cwd = normalizeWorkspaceId(cwd);
        const target = await this.ensureWorkingTreeWatchTarget(cwd);
        target.listeners.add(onChange);
        return {
            repoRoot: target.repoRoot,
            unsubscribe: () => {
                this.removeWorkingTreeWatchListener(cwd, onChange);
            },
        };
    }
    scheduleRefreshForCwd(cwd) {
        cwd = normalizeWorkspaceId(cwd);
        const target = this.workspaceTargets.get(cwd);
        if (target) {
            this.scheduleWorkspaceRefresh(target);
        }
    }
    dispose() {
        for (const target of this.workspaceTargets.values()) {
            this.closeWorkspaceTarget(target);
        }
        this.workspaceTargets.clear();
        for (const target of this.repoTargets.values()) {
            this.closeRepoTarget(target);
        }
        this.repoTargets.clear();
        for (const target of this.workingTreeWatchTargets.values()) {
            this.closeWorkingTreeWatchTarget(target);
        }
        this.workingTreeWatchTargets.clear();
        this.workingTreeWatchSetups.clear();
        this.snapshotUpdatedListeners.clear();
    }
    ensureWorkspaceTarget(cwd) {
        const existingTarget = this.workspaceTargets.get(cwd);
        if (existingTarget) {
            return existingTarget;
        }
        return this.createWorkspaceTarget(cwd);
    }
    readAuxiliaryCache(cache, key, options, load) {
        if (options?.force && !options.reason) {
            throw new Error("WorkspaceGitService forced read requires a reason");
        }
        const entry = this.ensureAuxiliaryCacheEntry(cache, key);
        const nowMs = this.deps.now().getTime();
        if (!options?.force && entry.value !== null && entry.loadedAtMs !== null) {
            const ageMs = nowMs - entry.loadedAtMs;
            if (ageMs <= WORKSPACE_GIT_AUXILIARY_READ_TTL_MS) {
                return Promise.resolve(entry.value);
            }
            if (entry.lastShellOutAtMs !== null &&
                nowMs - entry.lastShellOutAtMs < WORKSPACE_GIT_INTERNAL_MIN_GAP_MS) {
                return Promise.resolve(entry.value);
            }
        }
        if (entry.inFlight) {
            return entry.inFlight;
        }
        entry.lastShellOutAtMs = nowMs;
        entry.inFlight = load()
            .then((value) => {
            entry.value = value;
            entry.loadedAtMs = this.deps.now().getTime();
            return value;
        })
            .finally(() => {
            entry.inFlight = null;
        });
        return entry.inFlight;
    }
    ensureAuxiliaryCacheEntry(cache, key) {
        const existing = cache.get(key);
        if (existing) {
            return existing;
        }
        const entry = {
            value: null,
            loadedAtMs: null,
            lastShellOutAtMs: null,
            inFlight: null,
        };
        cache.set(key, entry);
        return entry;
    }
    async ensureWorkingTreeWatchTarget(cwd) {
        const existingTarget = this.workingTreeWatchTargets.get(cwd);
        if (existingTarget) {
            return existingTarget;
        }
        const existingSetup = this.workingTreeWatchSetups.get(cwd);
        if (existingSetup) {
            return existingSetup;
        }
        const setup = this.createWorkingTreeWatchTarget(cwd).finally(() => {
            this.workingTreeWatchSetups.delete(cwd);
        });
        this.workingTreeWatchSetups.set(cwd, setup);
        return setup;
    }
    createWorkspaceTarget(cwd) {
        const target = {
            cwd,
            listeners: new Set(),
            watchers: [],
            debounceTimer: null,
            selfHealTimer: null,
            githubPollSubscription: null,
            githubPollKey: null,
            refreshState: { status: "idle" },
            latestGit: null,
            latestGitLoadedAtMs: null,
            latestGithub: null,
            latestGithubLoadedAtMs: null,
            latestSnapshot: null,
            latestSnapshotLoadedAtMs: null,
            latestFacts: null,
            latestFactsLoadedAtMs: null,
            factsPromise: null,
            latestFingerprint: null,
            lastShellOutAtMs: null,
            repoGitRoot: null,
            cachedGitHubRemote: null,
            observationSetupPromise: null,
            observationSetupComplete: false,
            closed: false,
        };
        this.workspaceTargets.set(cwd, target);
        return target;
    }
    scheduleInitialWorkspaceRefresh(target) {
        queueMicrotask(() => {
            if (!this.isActiveObservedWorkspaceTarget(target) || target.latestSnapshot) {
                return;
            }
            void this.refreshWorkspaceTarget(target, {
                force: false,
                includeGitHub: true,
                reason: "initial",
                notify: true,
            });
        });
    }
    scheduleWorkspaceObservationSetup(target) {
        if (target.observationSetupComplete ||
            target.observationSetupPromise ||
            !this.isActiveObservedWorkspaceTarget(target)) {
            return;
        }
        target.observationSetupPromise = Promise.resolve()
            .then(() => this.setupWorkspaceObservation(target))
            .catch((error) => {
            this.logger.warn({ err: error, cwd: target.cwd }, "Failed to set up workspace git observation");
        })
            .finally(() => {
            target.observationSetupPromise = null;
        });
    }
    async setupWorkspaceObservation(target) {
        const facts = await this.getFactsForObservation(target);
        const gitDir = facts?.isGit ? facts.absoluteGitDir : null;
        if (!this.isActiveObservedWorkspaceTarget(target)) {
            return;
        }
        if (!gitDir) {
            target.observationSetupComplete = true;
            return;
        }
        const repoGitRoot = facts?.isGit && facts.gitCommonDir
            ? facts.gitCommonDir
            : await this.resolveWorkspaceGitRefsRoot(gitDir);
        if (!this.isActiveObservedWorkspaceTarget(target)) {
            return;
        }
        target.repoGitRoot = repoGitRoot;
        this.startWorkspaceWatchers(target, gitDir, repoGitRoot);
        await this.ensureRepoTarget(target);
        if (this.isActiveObservedWorkspaceTarget(target)) {
            target.observationSetupComplete = true;
        }
    }
    async getFactsForObservation(target) {
        return this.loadCheckoutFacts(target, {
            paseoHome: this.paseoHome,
            logger: this.logger,
            allowRecent: true,
        });
    }
    loadCheckoutFacts(target, options) {
        if (options.allowRecent && target.latestFacts && target.latestFactsLoadedAtMs !== null) {
            const ageMs = this.deps.now().getTime() - target.latestFactsLoadedAtMs;
            if (ageMs < WORKSPACE_GIT_FACTS_REUSE_TTL_MS) {
                return Promise.resolve(target.latestFacts);
            }
        }
        if (target.factsPromise) {
            return target.factsPromise;
        }
        const { allowRecent: _allowRecent, ...context } = options;
        const promise = this.deps
            .getCheckoutSnapshotFacts(target.cwd, context)
            .then((facts) => {
            target.latestFacts = facts;
            target.latestFactsLoadedAtMs = this.deps.now().getTime();
            return facts;
        })
            .finally(() => {
            if (target.factsPromise === promise) {
                target.factsPromise = null;
            }
        });
        target.factsPromise = promise;
        return promise;
    }
    isActiveObservedWorkspaceTarget(target) {
        return (!target.closed &&
            target.listeners.size > 0 &&
            this.workspaceTargets.get(target.cwd) === target);
    }
    async createWorkingTreeWatchTarget(cwd) {
        const repoRoot = await this.resolveCheckoutWatchRoot(cwd);
        const target = {
            cwd,
            repoRoot,
            repoWatchPath: null,
            watchers: [],
            watchedPaths: new Set(),
            fallbackRefreshInterval: null,
            linuxTreeRefreshPromise: null,
            linuxTreeRefreshQueued: false,
            listeners: new Set(),
        };
        const repoWatchPath = repoRoot ?? cwd;
        target.repoWatchPath = repoWatchPath;
        const watchPaths = new Set([repoWatchPath]);
        const gitDir = await this.deps.resolveAbsoluteGitDir(cwd);
        if (gitDir) {
            watchPaths.add(gitDir);
        }
        let hasRecursiveRepoCoverage = false;
        const allowRecursiveRepoWatch = process.platform !== "linux";
        if (process.platform === "linux") {
            hasRecursiveRepoCoverage = await this.ensureLinuxRepoTreeWatchers(target, repoWatchPath);
        }
        for (const watchPath of watchPaths) {
            if (process.platform === "linux" && watchPath === repoWatchPath) {
                continue;
            }
            const shouldTryRecursive = watchPath === repoWatchPath && allowRecursiveRepoWatch;
            const watcherIsRecursive = this.addWorkingTreeWatcher(target, watchPath, shouldTryRecursive);
            if (watchPath === repoWatchPath && watcherIsRecursive) {
                hasRecursiveRepoCoverage = true;
            }
        }
        const missingRepoCoverage = repoRoot === null || !hasRecursiveRepoCoverage;
        if (target.watchers.length === 0 || missingRepoCoverage) {
            target.fallbackRefreshInterval = setInterval(() => {
                this.scheduleWorkspaceRefresh(cwd, {
                    force: true,
                    reason: "working-tree-watch-fallback",
                });
                for (const listener of target.listeners) {
                    listener();
                }
            }, WORKING_TREE_WATCH_FALLBACK_REFRESH_MS);
            this.logger.warn({
                cwd,
                intervalMs: WORKING_TREE_WATCH_FALLBACK_REFRESH_MS,
                reason: target.watchers.length === 0 ? "no_watchers" : "missing_recursive_repo_root_coverage",
            }, "Working tree watchers unavailable; using timed refresh fallback");
        }
        this.workingTreeWatchTargets.set(cwd, target);
        return target;
    }
    async resolveCheckoutWatchRoot(cwd) {
        try {
            const { stdout } = await this.deps.runGitCommand(["rev-parse", "--show-toplevel"], {
                cwd,
                envOverlay: READ_ONLY_GIT_ENV,
            });
            return parseGitRevParsePath(stdout);
        }
        catch {
            return null;
        }
    }
    async resolveWorkspaceGitRefsRoot(gitDir) {
        try {
            const commonDir = (await readFile(join(gitDir, "commondir"), "utf8")).trim();
            if (commonDir.length > 0) {
                return resolve(gitDir, commonDir);
            }
        }
        catch {
            return gitDir;
        }
        return gitDir;
    }
    startWorkspaceWatchers(target, gitDir, repoGitRoot) {
        for (const watchPath of new Set([join(gitDir, "HEAD"), join(repoGitRoot, "refs", "heads")])) {
            let watcher = null;
            try {
                watcher = this.deps.watch(watchPath, { recursive: false }, () => {
                    this.scheduleWorkspaceRefresh(target);
                });
            }
            catch (error) {
                this.logger.warn({ err: error, cwd: target.cwd, watchPath }, "Failed to start workspace git watcher");
            }
            if (!watcher) {
                continue;
            }
            watcher.on("error", (error) => {
                this.logger.warn({ err: error, cwd: target.cwd, watchPath }, "Workspace git watcher error");
            });
            target.watchers.push(watcher);
        }
    }
    async ensureRepoTarget(workspaceTarget) {
        const repoGitRoot = workspaceTarget.repoGitRoot;
        if (!repoGitRoot || !this.isActiveObservedWorkspaceTarget(workspaceTarget)) {
            return;
        }
        const existingTarget = this.repoTargets.get(repoGitRoot);
        if (existingTarget) {
            existingTarget.workspaceKeys.add(workspaceTarget.cwd);
            return;
        }
        const facts = workspaceTarget.latestFacts;
        const hasOrigin = facts?.isGit === true
            ? facts.remoteUrl !== null
            : await this.deps.hasOriginRemote(workspaceTarget.cwd);
        if (!this.isActiveObservedWorkspaceTarget(workspaceTarget)) {
            return;
        }
        if (!hasOrigin) {
            return;
        }
        const targetAfterProbe = this.repoTargets.get(repoGitRoot);
        if (targetAfterProbe) {
            targetAfterProbe.workspaceKeys.add(workspaceTarget.cwd);
            return;
        }
        const repoTarget = {
            repoGitRoot,
            cwd: workspaceTarget.cwd,
            workspaceKeys: new Set([workspaceTarget.cwd]),
            intervalId: setInterval(() => {
                void this.runRepoFetch(repoTarget);
            }, BACKGROUND_GIT_FETCH_INTERVAL_MS),
            fetchInFlight: false,
        };
        this.repoTargets.set(repoGitRoot, repoTarget);
        void this.runRepoFetch(repoTarget);
    }
    scheduleWorkspaceRefresh(targetOrCwd, options) {
        const target = typeof targetOrCwd === "string"
            ? this.workspaceTargets.get(normalizeWorkspaceId(targetOrCwd))
            : targetOrCwd;
        if (!target || target.closed || this.workspaceTargets.get(target.cwd) !== target) {
            return;
        }
        if (target.debounceTimer) {
            clearTimeout(target.debounceTimer);
        }
        target.debounceTimer = setTimeout(() => {
            if (target.closed || this.workspaceTargets.get(target.cwd) !== target) {
                return;
            }
            target.debounceTimer = null;
            void this.refreshWorkspaceTarget(target, {
                force: options?.force === true,
                includeGitHub: false,
                reason: options?.reason ?? "watch",
                notify: true,
            });
        }, WORKSPACE_GIT_WATCH_DEBOUNCE_MS);
    }
    startWorkspaceSubscriptionTimers(target) {
        if (!target.selfHealTimer) {
            target.selfHealTimer = setInterval(() => {
                this.scheduleWorkspaceObservationSetup(target);
                this.refreshWorkspaceTarget(target, {
                    force: false,
                    includeGitHub: false,
                    reason: "self-heal-git",
                    notify: true,
                }).catch((error) => {
                    this.logger.warn({ err: error, cwd: target.cwd, reason: "self-heal-git" }, "Failed to run workspace git self-heal refresh");
                });
            }, WORKSPACE_GIT_SELF_HEAL_INTERVAL_MS);
        }
        this.updateGitHubPollForTarget(target);
    }
    updateGitHubPollForTarget(target) {
        if (target.listeners.size === 0) {
            this.stopGitHubPollForTarget(target);
            return;
        }
        const git = target.latestGit;
        if (!git || !this.deps.github.retainCurrentPullRequestStatusPoll) {
            this.stopGitHubPollForTarget(target);
            return;
        }
        const pollTarget = this.resolveGitHubPollTarget(target);
        const remoteUrl = git.remoteUrl;
        const hasGitHubRemote = target.cachedGitHubRemote?.remoteUrl === remoteUrl &&
            target.cachedGitHubRemote.identity !== null;
        if (!pollTarget || remoteUrl === null || !hasGitHubRemote) {
            this.stopGitHubPollForTarget(target);
            return;
        }
        const pollKey = buildWorkspaceGitHubPollKey(remoteUrl, pollTarget);
        if (target.githubPollKey === pollKey && target.githubPollSubscription) {
            return;
        }
        this.stopGitHubPollForTarget(target);
        target.githubPollKey = pollKey;
        target.githubPollSubscription = this.deps.github.retainCurrentPullRequestStatusPoll({
            cwd: target.cwd,
            headRef: pollTarget.headRef,
            ...(pollTarget.headRepositoryOwner
                ? { headRepositoryOwner: pollTarget.headRepositoryOwner }
                : {}),
            onStatus: (status) => {
                if (!this.isActiveObservedWorkspaceTarget(target)) {
                    return;
                }
                this.rememberGitHubSnapshot(target, buildGitHubSnapshotFromStatus(status), {
                    notify: true,
                });
            },
            onError: (error) => {
                this.logger.warn({
                    err: error,
                    cwd: target.cwd,
                    headRef: pollTarget.headRef,
                    headRepositoryOwner: pollTarget.headRepositoryOwner,
                    reason: "self-heal-github",
                }, "Failed to run GitHub self-heal refresh");
            },
        });
    }
    resolveGitHubPollTarget(target) {
        const git = target.latestGit;
        if (!git?.currentBranch) {
            return null;
        }
        const lookupTarget = target.latestFacts?.isGit && target.latestFacts.currentBranch === git.currentBranch
            ? target.latestFacts.pullRequestLookupTarget
            : null;
        if (lookupTarget) {
            return lookupTarget;
        }
        return { headRef: git.currentBranch };
    }
    stopGitHubPollForTarget(target) {
        target.githubPollSubscription?.unsubscribe();
        target.githubPollSubscription = null;
        target.githubPollKey = null;
    }
    addWorkingTreeWatcher(target, watchPath, shouldTryRecursive) {
        if (target.watchedPaths.has(watchPath)) {
            return false;
        }
        const { cwd } = target;
        const onChange = () => {
            if (process.platform === "linux" && target.repoWatchPath) {
                void this.refreshLinuxRepoTreeWatchers(target);
            }
            this.scheduleWorkspaceRefresh(cwd, {
                force: true,
                reason: "working-tree-watch",
            });
            for (const listener of target.listeners) {
                listener();
            }
        };
        const createWatcher = (recursive) => this.deps.watch(watchPath, { recursive }, () => {
            onChange();
        });
        let watcher = null;
        let watcherIsRecursive = false;
        try {
            if (shouldTryRecursive) {
                watcher = createWatcher(true);
                watcherIsRecursive = true;
            }
            else {
                watcher = createWatcher(false);
            }
        }
        catch (error) {
            if (shouldTryRecursive) {
                try {
                    watcher = createWatcher(false);
                    this.logger.warn({ err: error, watchPath, cwd }, "Working tree recursive watch unavailable; using non-recursive fallback");
                }
                catch (fallbackError) {
                    this.logger.warn({ err: fallbackError, watchPath, cwd }, "Failed to start working tree watcher");
                }
            }
            else {
                this.logger.warn({ err: error, watchPath, cwd }, "Failed to start working tree watcher");
            }
        }
        if (!watcher) {
            return false;
        }
        watcher.on("error", (error) => {
            this.logger.warn({ err: error, watchPath, cwd }, "Working tree watcher error");
        });
        target.watchers.push(watcher);
        target.watchedPaths.add(watchPath);
        return watcherIsRecursive;
    }
    async ensureLinuxRepoTreeWatchers(target, rootPath) {
        const directories = await this.listLinuxWatchDirectories(rootPath);
        let complete = true;
        for (const directory of directories) {
            const watcherWasRecursive = this.addWorkingTreeWatcher(target, directory, false);
            if (!watcherWasRecursive && !target.watchedPaths.has(directory)) {
                complete = false;
            }
        }
        return complete && target.watchedPaths.has(rootPath);
    }
    async refreshLinuxRepoTreeWatchers(target) {
        if (process.platform !== "linux" || !target.repoWatchPath) {
            return;
        }
        const rootPath = target.repoWatchPath;
        if (target.linuxTreeRefreshPromise) {
            target.linuxTreeRefreshQueued = true;
            return;
        }
        target.linuxTreeRefreshPromise = (async () => {
            do {
                target.linuxTreeRefreshQueued = false;
                try {
                    await this.ensureLinuxRepoTreeWatchers(target, rootPath);
                }
                catch (error) {
                    this.logger.warn({
                        err: error,
                        cwd: target.cwd,
                        rootPath,
                    }, "Failed to refresh Linux working tree watchers");
                }
                if (target.linuxTreeRefreshQueued) {
                    await new Promise((r) => setTimeout(r, LINUX_WATCH_REFRESH_COOLDOWN_MS));
                }
            } while (target.linuxTreeRefreshQueued);
        })();
        try {
            await target.linuxTreeRefreshPromise;
        }
        finally {
            target.linuxTreeRefreshPromise = null;
        }
    }
    async listLinuxWatchDirectories(rootPath) {
        const ignored = await this.loadLinuxIgnoredDirs(rootPath);
        const directories = [];
        let currentLevel = [rootPath];
        let capped = false;
        while (currentLevel.length > 0) {
            directories.push(...currentLevel);
            if (directories.length >= LINUX_WATCH_MAX_DIRS) {
                capped = true;
                break;
            }
            const readResults = await Promise.all(currentLevel.map((directory) => linuxWatchReaddirLimit(async () => {
                try {
                    return await this.deps.readdir(directory, { withFileTypes: true });
                }
                catch {
                    return null;
                }
            })));
            const nextLevel = [];
            for (let i = 0; i < currentLevel.length; i += 1) {
                const directory = currentLevel[i];
                const entries = readResults[i];
                if (!directory || !entries)
                    continue;
                for (const entry of entries) {
                    if (!entry.isDirectory() || entry.name === ".git") {
                        continue;
                    }
                    const childPath = join(directory, entry.name);
                    if (ignored.has(childPath)) {
                        continue;
                    }
                    nextLevel.push(childPath);
                }
            }
            currentLevel = nextLevel;
        }
        if (capped) {
            this.logger.warn({ rootPath, limit: LINUX_WATCH_MAX_DIRS, walked: directories.length }, "Linux working tree exceeds watcher cap; skipping deeper directories");
        }
        return directories;
    }
    async loadLinuxIgnoredDirs(rootPath) {
        const cached = this.linuxIgnoredDirsCache.get(rootPath);
        if (cached && Date.now() - cached.ts < LINUX_WATCH_IGNORE_TTL_MS) {
            return cached.ignored;
        }
        const ignored = new Set();
        try {
            const result = await this.deps.runGitCommand(["ls-files", "-o", "-i", "--directory", "--exclude-standard"], { cwd: rootPath, env: READ_ONLY_GIT_ENV });
            for (const raw of result.stdout.split("\n")) {
                if (!raw.endsWith("/")) {
                    continue;
                }
                const rel = raw.replace(/\/+$/, "");
                if (!rel) {
                    continue;
                }
                ignored.add(resolve(rootPath, rel));
            }
        }
        catch (error) {
            this.logger.debug({ err: error, rootPath }, "Failed to load gitignore directories; falling back to name-based skip only");
        }
        this.linuxIgnoredDirsCache.set(rootPath, { ignored, ts: Date.now() });
        return ignored;
    }
    async refreshWorkspaceTarget(target, request) {
        if (target.closed || this.workspaceTargets.get(target.cwd) !== target) {
            return;
        }
        try {
            await this.requestWorkspaceSnapshot(target, request);
        }
        catch (error) {
            this.logger.warn({ err: error, cwd: target.cwd, reason: request.reason }, "Failed to refresh workspace git snapshot");
        }
    }
    requestWorkspaceSnapshot(target, request) {
        if (target.refreshState.status === "in-flight") {
            const needsForcedRefresh = request.force && !target.refreshState.force;
            const needsGitHubRefresh = request.force && request.includeGitHub && !target.refreshState.includeGitHub;
            if (needsForcedRefresh || needsGitHubRefresh) {
                target.refreshState.queued = this.mergeQueuedRefresh(target.refreshState.queued, request);
            }
            return target.refreshState.promise;
        }
        if (!request.force && this.shouldThrottleNonForcedRefresh(target)) {
            return Promise.resolve(target.latestSnapshot);
        }
        const promise = this.runWorkspaceRefreshLoop(target, request).finally(() => {
            const state = target.refreshState;
            if (state.status === "in-flight" && state.promise === promise) {
                target.refreshState = { status: "idle" };
            }
        });
        target.refreshState = {
            status: "in-flight",
            promise,
            force: request.force,
            includeGitHub: request.includeGitHub,
            queued: null,
        };
        return promise;
    }
    normalizeRefreshRequest(options, defaultReason, notify) {
        if (options?.force && !options.reason) {
            throw new Error("WorkspaceGitService.getSnapshot force refresh requires a reason");
        }
        const force = options?.force === true;
        return {
            force,
            includeGitHub: options?.includeGitHub ?? true,
            reason: options?.reason ?? defaultReason,
            notify,
        };
    }
    async resolveGitHubRemoteForTarget(target, remoteUrl) {
        if (!remoteUrl) {
            target.cachedGitHubRemote = null;
            return null;
        }
        if (target.cachedGitHubRemote?.remoteUrl === remoteUrl) {
            return target.cachedGitHubRemote.identity;
        }
        const identity = await resolveGitHubRemote({ remoteUrl });
        target.cachedGitHubRemote = { remoteUrl, identity };
        return identity;
    }
    shouldThrottleNonForcedRefresh(target) {
        if (!target.latestSnapshot || target.lastShellOutAtMs === null) {
            return false;
        }
        return this.deps.now().getTime() - target.lastShellOutAtMs < WORKSPACE_GIT_INTERNAL_MIN_GAP_MS;
    }
    mergeQueuedRefresh(queued, request) {
        if (!queued) {
            return {
                force: request.force,
                includeGitHub: request.includeGitHub,
                reason: request.reason,
                notify: request.notify,
            };
        }
        const force = queued.force || request.force;
        const upgradesForce = request.force && !queued.force;
        const upgradesGitHub = request.includeGitHub && !queued.includeGitHub;
        return {
            force,
            includeGitHub: queued.includeGitHub || request.includeGitHub,
            reason: upgradesForce || upgradesGitHub ? request.reason : queued.reason,
            notify: queued.notify || request.notify,
        };
    }
    async runWorkspaceRefreshLoop(target, initialRequest) {
        let request = initialRequest;
        let snapshot;
        while (true) {
            snapshot = await this.refreshSnapshot(target, request);
            this.rememberSnapshot(target, snapshot, {
                notify: request.notify,
                forceEmit: request.force,
            });
            const state = target.refreshState;
            if (state.status !== "in-flight" || !state.queued) {
                break;
            }
            request = state.queued;
            state.queued = null;
            state.force = request.force;
            state.includeGitHub = request.includeGitHub;
        }
        return snapshot;
    }
    async refreshSnapshot(target, request) {
        const facts = await this.refreshGitSnapshot(target, request);
        if (request.includeGitHub) {
            await this.refreshGitHubSnapshot(target, request, facts);
        }
        const snapshot = this.combineSnapshot(target);
        target.latestSnapshotLoadedAtMs = this.deps.now().getTime();
        return snapshot;
    }
    async refreshGitSnapshot(target, request) {
        const now = this.deps.now();
        target.lastShellOutAtMs = now.getTime();
        const cwd = target.cwd;
        const previousGitHubPollKey = this.getGitHubPollKey(target);
        const baseContext = {
            paseoHome: this.paseoHome,
            worktreesRoot: this.worktreesRoot,
            logger: this.logger,
        };
        const facts = await this.loadCheckoutFacts(target, {
            ...baseContext,
            allowRecent: !request.force,
        });
        const context = { ...baseContext, facts };
        const checkoutStatus = await this.deps.getCheckoutStatus(cwd, context);
        if (!checkoutStatus.isGit) {
            target.latestGit = buildNotGitSnapshot(cwd).git;
            target.latestGitLoadedAtMs = this.deps.now().getTime();
            target.cachedGitHubRemote = null;
            target.latestGithub = buildGitHubUnavailableSnapshot();
            target.latestGithubLoadedAtMs = target.latestGitLoadedAtMs;
            return facts;
        }
        await this.resolveGitHubRemoteForTarget(target, checkoutStatus.remoteUrl);
        const diffStat = await this.deps
            .getCheckoutShortstat(cwd, context, { force: request.force })
            .catch(() => null);
        target.latestGit = {
            isGit: true,
            repoRoot: checkoutStatus.repoRoot,
            mainRepoRoot: checkoutStatus.mainRepoRoot,
            currentBranch: checkoutStatus.currentBranch,
            remoteUrl: checkoutStatus.remoteUrl,
            isPaseoOwnedWorktree: checkoutStatus.isPaseoOwnedWorktree,
            isDirty: checkoutStatus.isDirty,
            baseRef: checkoutStatus.baseRef,
            aheadBehind: checkoutStatus.aheadBehind,
            aheadOfOrigin: checkoutStatus.aheadOfOrigin,
            behindOfOrigin: checkoutStatus.behindOfOrigin,
            hasRemote: checkoutStatus.hasRemote,
            diffStat,
        };
        target.latestGitLoadedAtMs = this.deps.now().getTime();
        if (previousGitHubPollKey !== this.getGitHubPollKey(target)) {
            target.latestGithub = buildGitHubUnavailableSnapshot();
            target.latestGithubLoadedAtMs = target.latestGitLoadedAtMs;
        }
        return facts;
    }
    async refreshGitHubSnapshot(target, request, facts) {
        const githubRemote = target.cachedGitHubRemote?.identity ?? null;
        const forceGitHub = request.force && request.includeGitHub;
        if (forceGitHub) {
            this.deps.github.invalidate({ cwd: target.cwd });
        }
        target.latestGithub = await loadGitHubSnapshot({
            cwd: target.cwd,
            githubRemote,
            now: this.deps.now(),
            deps: this.deps,
            force: forceGitHub,
            reason: request.reason,
            facts,
        });
        target.latestGithubLoadedAtMs = this.deps.now().getTime();
    }
    combineSnapshot(target) {
        if (!target.latestGit) {
            return target.latestSnapshot ?? buildNotGitSnapshot(target.cwd);
        }
        return {
            cwd: target.cwd,
            git: target.latestGit,
            github: target.latestGithub ?? buildGitHubUnavailableSnapshot(),
        };
    }
    getGitHubPollKey(target) {
        const git = target.latestGit;
        if (!git?.currentBranch || !git.remoteUrl) {
            return null;
        }
        const githubRemote = target.cachedGitHubRemote;
        if (!githubRemote || githubRemote.remoteUrl !== git.remoteUrl || !githubRemote.identity) {
            return null;
        }
        const pollTarget = this.resolveGitHubPollTarget(target);
        if (!pollTarget) {
            return null;
        }
        return buildWorkspaceGitHubPollKey(git.remoteUrl, pollTarget);
    }
    rememberGitHubSnapshot(target, github, options) {
        if (target.closed || this.workspaceTargets.get(target.cwd) !== target) {
            return;
        }
        target.latestGithub = github;
        target.latestGithubLoadedAtMs = this.deps.now().getTime();
        this.rememberSnapshot(target, this.combineSnapshot(target), {
            notify: options?.notify,
            forceEmit: false,
        });
    }
    rememberSnapshot(target, snapshot, options) {
        target.latestSnapshot = snapshot;
        if (target.listeners.size > 0) {
            this.updateGitHubPollForTarget(target);
        }
        const fingerprint = JSON.stringify(snapshot);
        const fingerprintMatches = target.latestFingerprint === fingerprint;
        if (fingerprintMatches && !options?.forceEmit) {
            return;
        }
        target.latestFingerprint = fingerprint;
        if (!options?.notify || target.listeners.size === 0) {
            return;
        }
        for (const listener of target.listeners) {
            listener(snapshot);
        }
        for (const listener of this.snapshotUpdatedListeners) {
            try {
                listener(snapshot);
            }
            catch (error) {
                this.logger.warn({ err: error, cwd: snapshot.cwd }, "Workspace git snapshot listener threw");
            }
        }
    }
    async runRepoFetch(target) {
        if (target.fetchInFlight) {
            return;
        }
        target.fetchInFlight = true;
        this.logger.debug({ repoGitRoot: target.repoGitRoot, cwd: target.cwd }, "Running background git fetch");
        try {
            await this.deps.runGitFetch(target.cwd);
        }
        catch (error) {
            this.logger.warn({ err: error, repoGitRoot: target.repoGitRoot, cwd: target.cwd }, "Background git fetch failed");
        }
        finally {
            target.fetchInFlight = false;
            await Promise.all(Array.from(target.workspaceKeys, async (workspaceKey) => {
                const workspaceTarget = this.workspaceTargets.get(workspaceKey);
                if (!workspaceTarget) {
                    return;
                }
                await this.refreshWorkspaceTarget(workspaceTarget, {
                    force: false,
                    includeGitHub: false,
                    reason: "repo-fetch",
                    notify: true,
                });
            }));
        }
    }
    removeWorkspaceListener(cwd, listener) {
        const target = this.workspaceTargets.get(cwd);
        if (!target) {
            return;
        }
        target.listeners.delete(listener);
        if (target.listeners.size > 0) {
            return;
        }
        this.removeWorkspaceTarget(target);
    }
    removeWorkspaceTarget(target) {
        if (target.repoGitRoot) {
            const repoTarget = this.repoTargets.get(target.repoGitRoot);
            repoTarget?.workspaceKeys.delete(target.cwd);
            if (repoTarget && repoTarget.workspaceKeys.size === 0) {
                this.closeRepoTarget(repoTarget);
                this.repoTargets.delete(target.repoGitRoot);
            }
        }
        this.closeWorkspaceTarget(target);
        this.workspaceTargets.delete(target.cwd);
    }
    removeWorkingTreeWatchListener(cwd, listener) {
        const target = this.workingTreeWatchTargets.get(cwd);
        if (!target) {
            return;
        }
        target.listeners.delete(listener);
        if (target.listeners.size > 0) {
            return;
        }
        this.closeWorkingTreeWatchTarget(target);
        this.workingTreeWatchTargets.delete(cwd);
    }
    closeWorkspaceTarget(target) {
        target.closed = true;
        if (target.debounceTimer) {
            clearTimeout(target.debounceTimer);
            target.debounceTimer = null;
        }
        if (target.selfHealTimer) {
            clearInterval(target.selfHealTimer);
            target.selfHealTimer = null;
        }
        this.stopGitHubPollForTarget(target);
        for (const watcher of target.watchers) {
            watcher.close();
        }
        target.watchers = [];
        target.listeners.clear();
    }
    closeWorkingTreeWatchTarget(target) {
        if (target.fallbackRefreshInterval) {
            clearInterval(target.fallbackRefreshInterval);
            target.fallbackRefreshInterval = null;
        }
        for (const watcher of target.watchers) {
            watcher.close();
        }
        target.watchers = [];
        target.watchedPaths.clear();
        target.listeners.clear();
        if (target.repoWatchPath) {
            this.linuxIgnoredDirsCache.delete(target.repoWatchPath);
        }
    }
    closeRepoTarget(target) {
        if (target.intervalId) {
            clearInterval(target.intervalId);
            target.intervalId = null;
        }
        target.workspaceKeys.clear();
    }
}
async function loadGitHubSnapshot(options) {
    if (!options.githubRemote) {
        return {
            featuresEnabled: false,
            pullRequest: null,
            error: null,
        };
    }
    try {
        await options.deps.github.isAuthenticated({ cwd: options.cwd });
    }
    catch {
        return {
            featuresEnabled: false,
            pullRequest: null,
            error: null,
        };
    }
    try {
        const result = await options.deps.getPullRequestStatus(options.cwd, options.deps.github, {
            force: options.force,
            reason: options.reason,
        }, { facts: options.facts });
        return {
            featuresEnabled: true,
            pullRequest: result.status,
            error: null,
        };
    }
    catch (error) {
        return {
            featuresEnabled: true,
            pullRequest: null,
            error: {
                message: error instanceof Error ? error.message : String(error),
            },
        };
    }
}
function parseWorkspaceGitStashList(stdout, options) {
    const entries = [];
    const lines = stdout.trim().split("\n").filter(Boolean);
    for (const line of lines) {
        const sepIdx = line.indexOf("\0");
        if (sepIdx < 0) {
            continue;
        }
        const refPart = line.slice(0, sepIdx);
        const subject = line.slice(sepIdx + 1);
        const indexMatch = refPart.match(/\{(\d+)\}/);
        if (!indexMatch) {
            continue;
        }
        const index = Number(indexMatch[1]);
        const prefix = "paseo-auto-stash:";
        const prefixIdx = subject.indexOf(prefix);
        const isPaseo = prefixIdx >= 0;
        const branch = isPaseo ? subject.slice(prefixIdx + prefix.length).trim() || null : null;
        if (options.paseoOnly && !isPaseo) {
            continue;
        }
        entries.push({ index, message: subject, branch, isPaseo });
    }
    return entries;
}
function buildNotGitSnapshot(cwd) {
    return {
        cwd,
        git: {
            isGit: false,
            repoRoot: null,
            mainRepoRoot: null,
            currentBranch: null,
            remoteUrl: null,
            isPaseoOwnedWorktree: false,
            isDirty: null,
            baseRef: null,
            aheadBehind: null,
            aheadOfOrigin: null,
            behindOfOrigin: null,
            hasRemote: false,
            diffStat: null,
        },
        github: buildGitHubUnavailableSnapshot(),
    };
}
function buildGitHubUnavailableSnapshot() {
    return {
        featuresEnabled: false,
        pullRequest: null,
        error: null,
    };
}
function buildGitHubSnapshotFromStatus(status) {
    return {
        featuresEnabled: true,
        pullRequest: status,
        error: null,
    };
}
function buildWorkspaceGitHubPollKey(remoteUrl, target) {
    return JSON.stringify([remoteUrl, target.headRef, target.headRepositoryOwner ?? null]);
}
async function runGitFetch(cwd) {
    await runGitCommand(["fetch", "origin", "--prune"], {
        cwd,
        envOverlay: { GIT_TERMINAL_PROMPT: "0" },
        timeout: 120000,
    });
}
//# sourceMappingURL=workspace-git-service.js.map