import { LocationEntityStore } from "../../campaign-entities/services/LocationEntityStore.js";
import { ConnectionTargetResolver } from "../../connections/services/ConnectionTargetResolver.js";
import { NexusSceneOperations } from "../../nexus/services/NexusSceneOperations.js";
import { ShopStore } from "../../shops/services/ShopStore.js";
import { SiteRecordManager } from "../../site/services/SiteRecordManager.js";
import { BranchLocalEntityDeletionPolicy } from "./BranchLocalEntityDeletionPolicy.js";

const MODULE_ID = "augur-nexus";

function siteKey(parentSceneId, siteId) {
    return parentSceneId && siteId ? `${parentSceneId}:${siteId}` : "";
}

function getLinkedSceneId(record = {}) {
    return String(record.siteSceneId || record.linkedSceneId || "").trim();
}

function parseSiteKey(key = "") {
    const separator = key.indexOf(":");
    if (separator < 1 || separator >= key.length - 1) return null;
    return {
        parentSceneId: key.slice(0, separator),
        siteId: key.slice(separator + 1)
    };
}

export class NexusDeletionPlanBuilder {
    static buildForScene(rootScene, { branch = null } = {}) {
        return this.#build({
            rootScene,
            sceneSeeds: rootScene ? [{ scene: rootScene, branch }] : [],
            origin: rootScene ? { kind: "scene", sceneId: rootScene.id } : null
        });
    }

    static buildForLocation(locationId) {
        const id = String(locationId || "").trim();
        return this.#build({
            locationSeeds: id ? [id] : [],
            origin: id ? { kind: "location", locationId: id } : null
        });
    }

    static buildForLocations(locationIds = []) {
        const ids = [...new Set((locationIds || [])
            .map(id => String(id || "").trim())
            .filter(Boolean))];
        return this.#build({
            locationSeeds: ids,
            origin: ids.length ? { kind: "locations", locationIds: ids } : null
        });
    }

    static buildForSite({ parentSceneId = null, siteId = null, linkedScene = null, deleteLinkedScene = false } = {}) {
        const key = siteKey(parentSceneId, siteId);
        return this.#build({
            rootScene: deleteLinkedScene ? linkedScene : null,
            siteSeeds: key ? [key] : [],
            sceneSeeds: deleteLinkedScene && linkedScene ? [{ scene: linkedScene }] : [],
            origin: key ? {
                kind: "site",
                parentSceneId,
                siteId,
                linkedSceneId: linkedScene?.id || null,
                deleteLinkedScene: Boolean(deleteLinkedScene)
            } : null
        });
    }

    static rebuild(plan) {
        const origin = plan?.origin || null;
        if (!origin) return null;

        if (origin.kind === "scene") {
            return this.buildForScene(game.scenes.get(origin.sceneId));
        }
        if (origin.kind === "location") {
            return this.buildForLocation(origin.locationId);
        }
        if (origin.kind === "locations") {
            return this.buildForLocations(origin.locationIds);
        }
        if (origin.kind === "site") {
            return this.buildForSite({
                parentSceneId: origin.parentSceneId,
                siteId: origin.siteId,
                linkedScene: origin.linkedSceneId ? game.scenes.get(origin.linkedSceneId) : null,
                deleteLinkedScene: origin.deleteLinkedScene
            });
        }
        return null;
    }

    static getSignature(plan) {
        if (!plan) return "";
        const ids = (values = [], getId = value => value?.id) => values
            .map(getId)
            .filter(Boolean)
            .sort();

        const entityCandidates = (plan.entityCandidates || [])
            .map(row => ({
                id: row.id,
                type: row.entityType,
                componentId: row.componentId
            }))
            .sort((left, right) => left.id.localeCompare(right.id));

        const participantImpacts = (plan.participantImpacts || [])
            .map(row => ({
                label: String(row?.label || ""),
                count: Number(row?.count || 0)
            }))
            .sort((left, right) => left.label.localeCompare(right.label) || left.count - right.count);

        return JSON.stringify({
            scenes: ids(plan.scenes),
            sites: [...(plan.siteKeys || [])].filter(Boolean).sort(),
            locations: ids(plan.locations),
            shops: ids(plan.shops),
            entityCandidates,
            participantImpacts
        });
    }

    static getDirectLocationsForSite({ parentSceneId = null, siteId = null } = {}) {
        const targetKey = siteKey(parentSceneId, siteId);
        if (!targetKey) return [];
        return LocationEntityStore.getLocations().filter(location => {
            const parent = location.hierarchy?.parent || null;
            return parent?.kind === "site" && siteKey(parent.parentSceneId, parent.siteId) === targetKey;
        });
    }

    static #build({
        rootScene = null,
        sceneSeeds = [],
        locationSeeds = [],
        siteSeeds = [],
        origin = null
    } = {}) {
        const scenesById = new Map();
        const sceneRootContexts = [];
        const sceneRootIds = new Set();
        const affectedSiteKeys = new Set(siteSeeds);
        const locationsById = new Map();
        const rootLocationIds = new Set(locationSeeds);
        const allLocations = LocationEntityStore.getLocations();

        const addSceneBranch = (scene, providedBranch = null) => {
            if (!scene?.id) return false;
            const branch = providedBranch || NexusSceneOperations.collectSceneBranch(scene);
            let changed = false;

            if (!sceneRootIds.has(scene.id)) {
                sceneRootIds.add(scene.id);
                sceneRootContexts.push({ rootScene: scene, branch });
            }
            for (const branchScene of branch) {
                if (!branchScene?.id || scenesById.has(branchScene.id)) continue;
                scenesById.set(branchScene.id, branchScene);
                changed = true;
            }
            return changed;
        };

        for (const seed of sceneSeeds) addSceneBranch(seed.scene, seed.branch);

        let changed = true;
        while (changed) {
            const before = `${scenesById.size}:${affectedSiteKeys.size}:${locationsById.size}`;

            for (const scene of scenesById.values()) {
                const siteFlags = scene.getFlag?.(MODULE_ID, "site") || {};
                const lineage = scene.getFlag?.(MODULE_ID, "lineage") || {};
                const parentSceneId = siteFlags.parentSceneId || lineage.parentSceneId || null;
                const parentSiteId = siteFlags.siteId || lineage.parentSiteId || null;
                const linkedKey = siteKey(parentSceneId, parentSiteId);
                if (linkedKey) affectedSiteKeys.add(linkedKey);

                for (const record of SiteRecordManager.getSceneRecordList(scene)) {
                    affectedSiteKeys.add(siteKey(scene.id, record.siteId));
                }
            }

            for (const parentScene of game.scenes.contents) {
                for (const record of SiteRecordManager.getSceneRecordList(parentScene)) {
                    if (scenesById.has(getLinkedSceneId(record))) {
                        affectedSiteKeys.add(siteKey(parentScene.id, record.siteId));
                    }
                }
            }

            for (const location of allLocations) {
                if (locationsById.has(location.id)) continue;
                const parent = location.hierarchy?.parent || null;
                const isSeed = rootLocationIds.has(location.id);
                const isContained = parent?.kind === "location"
                    ? locationsById.has(parent.locationId)
                    : parent?.kind === "scene"
                        ? scenesById.has(parent.sceneId)
                        : parent?.kind === "site"
                            ? affectedSiteKeys.has(siteKey(parent.parentSceneId, parent.siteId))
                            : false;
                if (isSeed || isContained) locationsById.set(location.id, location);
            }

            for (const location of locationsById.values()) {
                const primaryScene = game.scenes.get(location.scenes?.primarySceneId);
                if (primaryScene) addSceneBranch(primaryScene);
            }

            const after = `${scenesById.size}:${affectedSiteKeys.size}:${locationsById.size}`;
            changed = before !== after;
        }

        const locationIds = new Set(locationsById.keys());
        const ownerBySceneId = new Map([...locationsById.values()]
            .filter(location => location.scenes?.primarySceneId)
            .map(location => [location.scenes.primarySceneId, location.id]));
        const locationDepth = location => {
            let depth = 0;
            let current = location;
            const visited = new Set();
            while (current && !visited.has(current.id)) {
                visited.add(current.id);
                const parent = current.hierarchy?.parent || null;
                const parentId = parent?.kind === "location"
                    ? parent.locationId
                    : parent?.kind === "scene"
                        ? ownerBySceneId.get(parent.sceneId)
                        : null;
                if (!parentId || !locationIds.has(parentId)) break;
                depth += 1;
                current = locationsById.get(parentId) || null;
            }
            return depth;
        };

        const locations = [...locationsById.values()]
            .map(entity => ({
                id: entity.id,
                name: entity.display?.name || "Location",
                entity,
                depth: locationDepth(entity)
            }))
            .sort((left, right) => right.depth - left.depth || left.name.localeCompare(right.name, game.i18n.lang));

        const shops = ShopStore.getShops()
            .filter(shop => locationIds.has(shop.hierarchy?.parentLocationId))
            .map(entity => ({ id: entity.id, name: entity.name || "Shop", entity }))
            .sort((left, right) => left.name.localeCompare(right.name, game.i18n.lang));

        const sceneIds = new Set(scenesById.keys());
        const structuralNodeIds = new Set([
            ...[...sceneIds].map(sceneId => ConnectionTargetResolver.getSceneNodeId(sceneId)),
            ...[...locationIds].map(locationId => ConnectionTargetResolver.getEntityNodeId(locationId))
        ]);
        for (const key of affectedSiteKeys) {
            const site = parseSiteKey(key);
            if (site) structuralNodeIds.add(ConnectionTargetResolver.getSiteNodeId(site.parentSceneId, site.siteId));
        }
        for (const shop of shops) {
            const document = ShopStore.resolveDocument(shop.id);
            if (document?.uuid) structuralNodeIds.add(ConnectionTargetResolver.getFoundryNodeId(document.uuid));
        }

        const entityImpact = BranchLocalEntityDeletionPolicy.analyze({
            anchorNodeIds: [...structuralNodeIds],
            sceneIds: [...sceneIds]
        });

        return {
            origin,
            rootScene,
            rootLocationIds: [...rootLocationIds],
            siteKeys: [...affectedSiteKeys],
            sceneRootContexts,
            scenes: [...scenesById.values()].map(scene => ({ id: scene.id, name: scene.name || "Scene", scene })),
            locations,
            shops,
            structuralNodeIds: [...structuralNodeIds],
            entityCandidates: entityImpact.candidates,
            preservedEntities: entityImpact.preserved,
            entityComponents: entityImpact.components,
            participantImpacts: []
        };
    }
}
