const MODULE_ID = "augur-nexus";
const FLAG_KEY = "regionPopulation";
const SCHEMA_VERSION = 4;

export class RegionPopulationRunStore {
    static get(scene) {
        const value = scene?.getFlag?.(MODULE_ID, FLAG_KEY);
        const version = Number(value?.schemaVersion || 0);
        if (!value || ![1, 2, 3, SCHEMA_VERSION].includes(version)) return null;
        return this.#normalize(value);
    }

    static async set(scene, run = {}) {
        if (!scene?.id) throw new Error("A Scene is required to store a population run.");
        const value = this.#normalize(run);
        await scene.setFlag(MODULE_ID, FLAG_KEY, value);
        return foundry.utils.deepClone(value);
    }

    static start(scene, run = {}) {
        const current = this.get(scene);
        const started = {
            ...run,
            status: "generating",
            createdAt: Date.now(),
            updatedAt: Date.now(),
            locationIds: [],
            npcIds: [],
            factionIds: [],
            connectionIds: []
        };
        return this.set(scene, {
            ...started,
            currentYear: Number(current?.currentYear || 0),
            summary: foundry.utils.deepClone(current?.summary || {}),
            chronicleJournalEntryId: String(current?.chronicleJournalEntryId || ""),
            runs: [
                ...(current?.runs?.length ? current.runs : current ? [this.#normalizeManifest(current)] : []),
                this.#normalizeManifest(started)
            ]
        });
    }

    static async appendLocation(scene, locationId) {
        return this.appendEntity(scene, "location", locationId);
    }

    static async appendEntity(scene, entityType, entityId) {
        const run = this.get(scene);
        const id = String(entityId || "").trim();
        if (!run || !id) return run;
        const field = { location: "locationIds", npc: "npcIds", faction: "factionIds" }[entityType];
        if (!field) return run;
        run[field] = [...new Set([...(run[field] || []), id])];
        this.#patchActiveManifest(run, { [field]: run[field] });
        run.updatedAt = Date.now();
        return this.set(scene, run);
    }

    static async appendConnection(scene, connectionId) {
        const run = this.get(scene);
        const id = String(connectionId || "").trim();
        if (!run || !id) return run;
        run.connectionIds = [...new Set([...(run.connectionIds || []), id])];
        this.#patchActiveManifest(run, { connectionIds: run.connectionIds });
        run.updatedAt = Date.now();
        return this.set(scene, run);
    }

    static async checkpoint(scene, {
        locationIds = [],
        npcIds = [],
        factionIds = [],
        connectionIds = [],
        currentYear = null
    } = {}) {
        const run = this.get(scene);
        if (!run) return null;
        run.locationIds = [...new Set([...(run.locationIds || []), ...locationIds.map(String).filter(Boolean)])];
        run.npcIds = [...new Set([...(run.npcIds || []), ...npcIds.map(String).filter(Boolean)])];
        run.factionIds = [...new Set([...(run.factionIds || []), ...factionIds.map(String).filter(Boolean)])];
        run.connectionIds = [...new Set([...(run.connectionIds || []), ...connectionIds.map(String).filter(Boolean)])];
        if (currentYear !== null) {
            run.currentYear = Math.max(Number(run.currentYear || 0), Number(currentYear || 0));
            run.endYear = Math.max(Number(run.endYear || 0), run.currentYear);
        }
        run.updatedAt = Date.now();
        this.#patchActiveManifest(run, {
            locationIds: run.locationIds,
            npcIds: run.npcIds,
            factionIds: run.factionIds,
            connectionIds: run.connectionIds,
            currentYear: run.currentYear,
            endYear: run.endYear,
            updatedAt: run.updatedAt
        });
        return this.set(scene, run);
    }

    static async removeLocation(scene, locationId) {
        const run = this.get(scene);
        const id = String(locationId || "").trim();
        if (!run || !id || ![(run.locationIds || []), ...(run.runs || []).map(manifest => manifest.locationIds || [])].some(ids => ids.includes(id))) return run;
        run.locationIds = (run.locationIds || []).filter(candidate => candidate !== id);
        run.runs = (run.runs || []).map(manifest => this.#normalizeManifest({
            ...manifest,
            locationIds: (manifest.locationIds || []).filter(candidate => candidate !== id)
        }));
        run.updatedAt = Date.now();
        return this.set(scene, run);
    }

    static async removeEntity(scene, entityType, entityId) {
        const run = this.get(scene);
        const id = String(entityId || "").trim();
        const field = { npc: "npcIds", faction: "factionIds" }[entityType];
        if (!run || !id || !field) return run;
        const present = (run[field] || []).includes(id)
            || (run.runs || []).some(manifest => (manifest[field] || []).includes(id));
        if (!present) return run;
        run[field] = (run[field] || []).filter(candidate => candidate !== id);
        run.runs = (run.runs || []).map(manifest => this.#normalizeManifest({
            ...manifest,
            [field]: (manifest[field] || []).filter(candidate => candidate !== id)
        }));
        run.updatedAt = Date.now();
        return this.set(scene, run);
    }

    static async removeChronicleReference(scene, journalEntryId = "") {
        const run = this.get(scene);
        const id = String(journalEntryId || "").trim();
        if (!run || !id || run.chronicleJournalEntryId !== id) return run;
        return this.set(scene, {
            ...run,
            chronicleJournalEntryId: "",
            updatedAt: Date.now()
        });
    }

    static async markStatus(scene, status, patch = {}) {
        const run = this.get(scene);
        if (!run) return null;
        const next = {
            ...run,
            ...foundry.utils.deepClone(patch),
            status: ["generating", "complete", "incomplete", "partial"].includes(status) ? status : run.status,
            updatedAt: Date.now()
        };
        this.#patchActiveManifest(next, {
            ...foundry.utils.deepClone(patch),
            status: next.status,
            updatedAt: next.updatedAt,
            completedAt: next.completedAt,
            summary: next.summary,
            failureMessage: next.failureMessage
        });
        return this.set(scene, next);
    }

    static async clear(scene) {
        if (!scene?.id) return false;
        await scene.unsetFlag(MODULE_ID, FLAG_KEY);
        return true;
    }

    static #normalize(run = {}) {
        const createdAt = Number(run.createdAt || Date.now());
        return {
            schemaVersion: SCHEMA_VERSION,
            runId: String(run.runId || foundry.utils.randomID()),
            status: ["generating", "complete", "incomplete", "partial"].includes(run.status) ? run.status : "complete",
            createdAt,
            updatedAt: Number(run.updatedAt || createdAt),
            completedAt: Number(run.completedAt || 0),
            elapsedMs: Math.max(0, Number(run.elapsedMs || 0)),
            regionProviderId: String(run.regionProviderId || ""),
            populationProviderId: String(run.populationProviderId || ""),
            seed: String(run.seed || ""),
            settings: foundry.utils.deepClone(run.settings || {}),
            startYear: Math.max(0, Number(run.startYear || 0)),
            endYear: Math.max(0, Number(run.endYear || 0)),
            locationIds: [...new Set((run.locationIds || []).map(String).filter(Boolean))],
            npcIds: [...new Set((run.npcIds || []).map(String).filter(Boolean))],
            factionIds: [...new Set((run.factionIds || []).map(String).filter(Boolean))],
            connectionIds: [...new Set((run.connectionIds || []).map(String).filter(Boolean))],
            roadContributionId: String(run.roadContributionId || ""),
            overlayContributionId: String(run.overlayContributionId || ""),
            chronicleJournalEntryId: String(run.chronicleJournalEntryId || ""),
            failureMessage: String(run.failureMessage || ""),
            summary: foundry.utils.deepClone(run.summary || {}),
            currentYear: Math.max(0, Number(run.currentYear ?? run.summary?.years ?? 0)),
            runs: (run.runs || []).map(value => this.#normalizeManifest(value))
        };
    }

    static #normalizeManifest(run = {}) {
        const createdAt = Number(run.createdAt || Date.now());
        return {
            runId: String(run.runId || foundry.utils.randomID()),
            status: ["generating", "complete", "incomplete", "partial"].includes(run.status) ? run.status : "complete",
            createdAt,
            updatedAt: Number(run.updatedAt || createdAt),
            completedAt: Number(run.completedAt || 0),
            elapsedMs: Math.max(0, Number(run.elapsedMs || 0)),
            seed: String(run.seed || ""),
            settings: foundry.utils.deepClone(run.settings || {}),
            startYear: Math.max(0, Number(run.startYear || 0)),
            endYear: Math.max(0, Number(run.endYear || run.summary?.years || 0)),
            locationIds: [...new Set((run.locationIds || []).map(String).filter(Boolean))],
            npcIds: [...new Set((run.npcIds || []).map(String).filter(Boolean))],
            factionIds: [...new Set((run.factionIds || []).map(String).filter(Boolean))],
            connectionIds: [...new Set((run.connectionIds || []).map(String).filter(Boolean))],
            roadContributionId: String(run.roadContributionId || ""),
            overlayContributionId: String(run.overlayContributionId || ""),
            summary: foundry.utils.deepClone(run.summary || {}),
            failureMessage: String(run.failureMessage || "")
        };
    }

    static #patchActiveManifest(run, patch = {}) {
        const index = (run.runs || []).findIndex(manifest => manifest.runId === run.runId);
        if (index < 0) return;
        run.runs[index] = this.#normalizeManifest({ ...run.runs[index], ...foundry.utils.deepClone(patch) });
    }
}
