import { randomUUID } from "node:crypto";
import { promises as fs } from "node:fs";
import path from "node:path";
import { z } from "zod";
const PersistedProjectRecordSchema = z.object({
    projectId: z.string(),
    rootPath: z.string(),
    kind: z.enum(["git", "non_git"]),
    displayName: z.string(),
    createdAt: z.string(),
    updatedAt: z.string(),
    archivedAt: z.string().nullable(),
    worktreeParentDir: z.string().nullable().optional(),
});
const PersistedWorkspaceRecordSchema = z.object({
    workspaceId: z.string(),
    projectId: z.string(),
    cwd: z.string(),
    kind: z.enum(["local_checkout", "worktree", "directory"]),
    displayName: z.string(),
    createdAt: z.string(),
    updatedAt: z.string(),
    archivedAt: z.string().nullable(),
});
class FileBackedRegistry {
    constructor(options) {
        this.loaded = false;
        this.cache = new Map();
        this.persistQueue = Promise.resolve();
        this.filePath = options.filePath;
        this.schema = options.schema;
        this.getId = options.getId;
        this.logger = options.logger.child({
            module: "workspace-registry",
            component: options.component,
        });
    }
    async initialize() {
        await this.load();
    }
    async existsOnDisk() {
        try {
            await fs.access(this.filePath);
            return true;
        }
        catch {
            return false;
        }
    }
    async list() {
        await this.load();
        return Array.from(this.cache.values());
    }
    async get(id) {
        await this.load();
        return this.cache.get(id) ?? null;
    }
    async upsert(record) {
        await this.load();
        const parsed = this.schema.parse(record);
        this.cache.set(this.getId(parsed), parsed);
        await this.enqueuePersist();
    }
    async archive(id, archivedAt) {
        await this.load();
        const existing = this.cache.get(id);
        if (!existing) {
            return;
        }
        const next = this.schema.parse({
            ...existing,
            updatedAt: archivedAt,
            archivedAt,
        });
        this.cache.set(id, next);
        await this.enqueuePersist();
    }
    async remove(id) {
        await this.load();
        if (!this.cache.delete(id)) {
            return;
        }
        await this.enqueuePersist();
    }
    async load() {
        if (this.loaded) {
            return;
        }
        this.cache.clear();
        try {
            const raw = await fs.readFile(this.filePath, "utf8");
            const parsed = z.array(this.schema).parse(JSON.parse(raw));
            for (const record of parsed) {
                this.cache.set(this.getId(record), record);
            }
        }
        catch (error) {
            const code = error.code;
            if (code !== "ENOENT") {
                this.logger.error({ err: error, filePath: this.filePath }, "Failed to load registry file");
            }
        }
        this.loaded = true;
    }
    async persist() {
        const records = Array.from(this.cache.values());
        await fs.mkdir(path.dirname(this.filePath), { recursive: true });
        const tempPath = `${this.filePath}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
        await fs.writeFile(tempPath, JSON.stringify(records, null, 2), "utf8");
        await fs.rename(tempPath, this.filePath);
    }
    async enqueuePersist() {
        const nextPersist = this.persistQueue.then(() => this.persist());
        this.persistQueue = nextPersist.catch(() => { });
        await nextPersist;
    }
}
export class FileBackedProjectRegistry extends FileBackedRegistry {
    constructor(filePath, logger) {
        super({
            filePath,
            logger,
            schema: PersistedProjectRecordSchema,
            getId: (record) => record.projectId,
            component: "projects",
        });
    }
}
export class FileBackedWorkspaceRegistry extends FileBackedRegistry {
    constructor(filePath, logger) {
        super({
            filePath,
            logger,
            schema: PersistedWorkspaceRecordSchema,
            getId: (record) => record.workspaceId,
            component: "workspaces",
        });
    }
}
export function createPersistedProjectRecord(input) {
    return PersistedProjectRecordSchema.parse({
        ...input,
        archivedAt: input.archivedAt ?? null,
        worktreeParentDir: input.worktreeParentDir ?? null,
    });
}
export function createPersistedWorkspaceRecord(input) {
    return PersistedWorkspaceRecordSchema.parse({
        ...input,
        archivedAt: input.archivedAt ?? null,
    });
}
//# sourceMappingURL=workspace-registry.js.map