import { existsSync } from "node:fs";
const DEFAULT_RECONCILE_INTERVAL_MS = 60000;
export class WorkspaceReconciliationService {
    constructor(options) {
        this.timer = null;
        this.running = false;
        this.projectRegistry = options.projectRegistry;
        this.workspaceRegistry = options.workspaceRegistry;
        this.logger = options.logger.child({ module: "workspace-reconciliation" });
        this.intervalMs = options.intervalMs ?? DEFAULT_RECONCILE_INTERVAL_MS;
        this.onChanges = options.onChanges ?? null;
        this.workspaceGitService = options.workspaceGitService ?? null;
    }
    start() {
        if (this.timer)
            return;
        this.logger.info({ intervalMs: this.intervalMs }, "Starting workspace reconciliation service");
        this.timer = setInterval(() => void this.runSafe(), this.intervalMs);
        // Run once immediately on start
        void this.runSafe();
    }
    stop() {
        if (this.timer) {
            clearInterval(this.timer);
            this.timer = null;
        }
    }
    async runOnce() {
        return this.reconcile();
    }
    async runSafe() {
        if (this.running)
            return;
        this.running = true;
        try {
            const result = await this.reconcile();
            if (result.changesApplied.length > 0) {
                this.logger.info({ changeCount: result.changesApplied.length, durationMs: result.durationMs }, "Reconciliation pass completed with changes");
            }
        }
        catch (error) {
            this.logger.error({ err: error }, "Reconciliation pass failed");
        }
        finally {
            this.running = false;
        }
    }
    async reconcile() {
        const start = Date.now();
        const changes = [];
        const allProjects = await this.projectRegistry.list();
        const allWorkspaces = await this.workspaceRegistry.list();
        const activeProjects = allProjects.filter((p) => !p.archivedAt);
        const activeWorkspaces = allWorkspaces.filter((w) => !w.archivedAt);
        const workspacesByProject = new Map();
        for (const workspace of activeWorkspaces) {
            const list = workspacesByProject.get(workspace.projectId) ?? [];
            list.push(workspace);
            workspacesByProject.set(workspace.projectId, list);
        }
        // 1. Archive workspaces whose directories no longer exist
        const missingWorkspaces = activeWorkspaces.filter((workspace) => !existsSync(workspace.cwd));
        await Promise.all(missingWorkspaces.map(async (workspace) => {
            const timestamp = new Date().toISOString();
            await this.workspaceRegistry.archive(workspace.workspaceId, timestamp);
            changes.push({
                kind: "workspace_archived",
                workspaceId: workspace.workspaceId,
                directory: workspace.cwd,
                reason: "directory_missing",
            });
            // Update the in-memory list for the project orphan check below
            const siblings = workspacesByProject.get(workspace.projectId);
            if (siblings) {
                const updated = siblings.filter((w) => w.workspaceId !== workspace.workspaceId);
                workspacesByProject.set(workspace.projectId, updated);
            }
        }));
        // 2. Archive orphaned projects (all workspaces archived/removed)
        const orphanedProjects = activeProjects.filter((project) => {
            const siblings = workspacesByProject.get(project.projectId) ?? [];
            return siblings.length === 0;
        });
        await Promise.all(orphanedProjects.map(async (project) => {
            const timestamp = new Date().toISOString();
            await this.projectRegistry.archive(project.projectId, timestamp);
            changes.push({
                kind: "project_archived",
                projectId: project.projectId,
                directory: project.rootPath,
                reason: "no_active_workspaces",
            });
        }));
        // 3. Reconcile git metadata for active projects whose directories still exist
        const projectsToReconcile = activeProjects.filter((project) => {
            if (project.archivedAt)
                return false;
            const siblings = workspacesByProject.get(project.projectId) ?? [];
            if (siblings.length === 0)
                return false;
            if (!existsSync(project.rootPath))
                return false;
            return true;
        });
        await Promise.all(projectsToReconcile.map((project) => this.reconcileProject(project, workspacesByProject.get(project.projectId) ?? [], changes)));
        if (changes.length > 0 && this.onChanges) {
            this.onChanges(changes);
        }
        return { changesApplied: changes, durationMs: Date.now() - start };
    }
    async reconcileProject(project, siblings, changes) {
        const directoryName = project.rootPath.split(/[\\/]/).findLast(Boolean) ?? project.rootPath;
        const currentGit = await this.readWorkspaceGitMetadata(project.rootPath, directoryName);
        const projectUpdates = {};
        const mappedKind = currentGit.projectKind === "git" ? "git" : "non_git";
        if (project.kind !== mappedKind) {
            projectUpdates.kind = mappedKind;
            projectUpdates.displayName = currentGit.projectDisplayName;
        }
        if (project.kind === "git" &&
            currentGit.projectKind === "git" &&
            project.displayName !== currentGit.projectDisplayName) {
            projectUpdates.displayName = currentGit.projectDisplayName;
        }
        if (Object.keys(projectUpdates).length > 0) {
            const timestamp = new Date().toISOString();
            await this.projectRegistry.upsert({
                ...project,
                ...projectUpdates,
                updatedAt: timestamp,
            });
            changes.push({
                kind: "project_updated",
                projectId: project.projectId,
                directory: project.rootPath,
                fields: projectUpdates,
            });
        }
        const existingSiblings = siblings.filter((workspace) => existsSync(workspace.cwd));
        await Promise.all(existingSiblings.map(async (workspace) => {
            const wsDirName = workspace.cwd.split(/[\\/]/).findLast(Boolean) ?? workspace.cwd;
            const wsGit = await this.readWorkspaceGitMetadata(workspace.cwd, wsDirName);
            if (wsGit.projectKind === "git" && workspace.displayName !== wsGit.workspaceDisplayName) {
                const timestamp = new Date().toISOString();
                await this.workspaceRegistry.upsert({
                    ...workspace,
                    displayName: wsGit.workspaceDisplayName,
                    updatedAt: timestamp,
                });
                changes.push({
                    kind: "workspace_updated",
                    workspaceId: workspace.workspaceId,
                    directory: workspace.cwd,
                    fields: { displayName: wsGit.workspaceDisplayName },
                });
            }
        }));
    }
    async readWorkspaceGitMetadata(cwd, directoryName) {
        if (!this.workspaceGitService) {
            return {
                projectKind: "directory",
                projectDisplayName: directoryName,
                workspaceDisplayName: directoryName,
                gitRemote: null,
                isWorktree: false,
                projectSlug: "untitled",
                repoRoot: null,
                currentBranch: null,
                remoteUrl: null,
            };
        }
        return this.workspaceGitService.getWorkspaceGitMetadata(cwd, { directoryName });
    }
}
//# sourceMappingURL=workspace-reconciliation-service.js.map