import fs from "node:fs/promises";
import path from "node:path";
import crypto from "node:crypto";
import { TaskEngineError } from "../transitions.js";
import { normalizeTaskStoreDocumentFromUnknown } from "./task-store-migration.js";
export const DEFAULT_TASK_STORE_PATH = ".workspace-kit/tasks/state.json";
function emptyStore() {
    return {
        schemaVersion: 1,
        tasks: [],
        transitionLog: [],
        mutationLog: [],
        lastUpdated: new Date().toISOString()
    };
}
export class TaskStore {
    document;
    persistence;
    dirtyTaskIds = new Set();
    newTransitionIds = new Set();
    newMutationIds = new Set();
    forceFullPersist = false;
    constructor(persistence) {
        this.persistence = persistence;
        this.document = emptyStore();
    }
    static forJsonFile(workspacePath, storeRelativePath) {
        const filePath = path.resolve(workspacePath, storeRelativePath ?? DEFAULT_TASK_STORE_PATH);
        return new TaskStore({
            pathLabel: filePath,
            loadDocument: async () => {
                try {
                    const raw = await fs.readFile(filePath, "utf8");
                    const parsed = JSON.parse(raw);
                    return normalizeTaskStoreDocumentFromUnknown(parsed);
                }
                catch (err) {
                    if (err.code === "ENOENT") {
                        return emptyStore();
                    }
                    if (err instanceof TaskEngineError) {
                        throw err;
                    }
                    throw new TaskEngineError("storage-read-error", `Failed to read task store: ${err.message}`);
                }
            },
            saveDocument: async (doc, _opts) => {
                const dir = path.dirname(filePath);
                const tmpPath = `${filePath}.${crypto.randomUUID().slice(0, 8)}.tmp`;
                try {
                    await fs.mkdir(dir, { recursive: true });
                    await fs.writeFile(tmpPath, JSON.stringify(doc, null, 2) + "\n", "utf8");
                    await fs.rename(tmpPath, filePath);
                }
                catch (err) {
                    try {
                        await fs.unlink(tmpPath);
                    }
                    catch {
                        /* cleanup best-effort */
                    }
                    throw new TaskEngineError("storage-write-error", `Failed to write task store: ${err.message}`);
                }
            }
        });
    }
    static forSqliteDual(dual) {
        return new TaskStore({
            pathLabel: `${dual.getDisplayPath()}#task_engine`,
            loadDocument: async () => dual.taskDocument,
            saveDocument: async (doc, opts) => {
                dual.seedFromDocuments(doc, dual.wishlistDocument);
                const hook = opts?.beforePersistInSqliteTransaction;
                if (hook) {
                    dual.withTransaction(hook, opts);
                }
                else {
                    dual.persistSync(opts);
                }
            }
        });
    }
    async load() {
        this.document = await this.persistence.loadDocument();
        this.resetDirtyTracking();
    }
    async save(opts) {
        this.document.lastUpdated = new Date().toISOString();
        const saveOpts = this.buildSaveOptions(opts);
        await this.persistence.saveDocument(this.document, saveOpts);
        this.resetDirtyTracking();
    }
    getAllTasks() {
        return [...this.document.tasks];
    }
    getActiveTasks() {
        return this.document.tasks.filter((task) => !task.archived).map((task) => ({ ...task }));
    }
    getTask(id) {
        return this.document.tasks.find((t) => t.id === id);
    }
    addTask(task) {
        if (this.document.tasks.some((t) => t.id === task.id)) {
            throw new TaskEngineError("duplicate-task-id", `Task '${task.id}' already exists`);
        }
        this.document.tasks.push({ ...task });
        this.dirtyTaskIds.add(task.id);
    }
    updateTask(task) {
        const idx = this.document.tasks.findIndex((t) => t.id === task.id);
        if (idx === -1) {
            throw new TaskEngineError("task-not-found", `Task '${task.id}' not found`);
        }
        this.document.tasks[idx] = { ...task };
        this.dirtyTaskIds.add(task.id);
    }
    addEvidence(evidence) {
        this.document.transitionLog.push(evidence);
        this.newTransitionIds.add(evidence.transitionId);
    }
    addMutationEvidence(evidence) {
        if (!Array.isArray(this.document.mutationLog)) {
            this.document.mutationLog = [];
        }
        this.document.mutationLog.push(evidence);
        this.newMutationIds.add(evidence.mutationId);
    }
    getTransitionLog() {
        return [...this.document.transitionLog];
    }
    getMutationLog() {
        return [...(this.document.mutationLog ?? [])];
    }
    replaceAllTasks(tasks) {
        this.document.tasks = tasks.map((t) => ({ ...t }));
        this.forceFullPersist = true;
    }
    getFilePath() {
        return this.persistence.pathLabel;
    }
    getLastUpdated() {
        return this.document.lastUpdated;
    }
    buildSaveOptions(opts) {
        const base = opts ?? {};
        if (base.persistScope === "full" || this.forceFullPersist || base.beforePersistInSqliteTransaction) {
            return { ...base, persistScope: "full" };
        }
        const dirtyTaskIds = base.dirtyTaskIds ?? [...this.dirtyTaskIds];
        const newTransitionIds = base.newTransitionIds ?? [...this.newTransitionIds];
        const newMutationIds = base.newMutationIds ?? [...this.newMutationIds];
        if (base.persistScope === "incremental" || dirtyTaskIds.length > 0 || newTransitionIds.length > 0 || newMutationIds.length > 0) {
            return {
                ...base,
                persistScope: "incremental",
                dirtyTaskIds,
                newTransitionIds,
                newMutationIds
            };
        }
        return base;
    }
    resetDirtyTracking() {
        this.dirtyTaskIds.clear();
        this.newTransitionIds.clear();
        this.newMutationIds.clear();
        this.forceFullPersist = false;
    }
}
