import { LocationEntityStore } from "./LocationEntityStore.js";
import { NexusLineageManager } from "../../nexus/services/NexusLineageManager.js";
import { LegacySciFiCompatibility } from "../../../support/compatibility/LegacySciFiCompatibility.js";

const MODULE_ID = "augur-nexus";

export class LocationSceneService {
    static registerHooks() {
        Hooks.on("deleteScene", scene => {
            this.#clearDeletedScene(scene).catch(err => {
                console.error("Augur Nexus | Failed to clear a deleted Location scene.", err);
            });
        });
        Hooks.on("augurNexusCampaignEntitiesChanged", () => {
            if (!canvas.scene?.id || !this.getLocationForScene(canvas.scene.id)) return;
            this.#refreshActiveSceneControls([canvas.scene.id]);
        });
    }

    static getPrimaryScene(locationId) {
        const sceneId = LocationEntityStore.getLocation(locationId)?.scenes?.primarySceneId;
        return sceneId ? game.scenes.get(sceneId) || null : null;
    }

    static getLocationForScene(sceneId, { excludeLocationId = null } = {}) {
        const normalizedSceneId = String(sceneId || "").trim();
        if (!normalizedSceneId) return null;
        return LocationEntityStore.getLocations().find(location =>
            location.id !== excludeLocationId
            && location.scenes?.primarySceneId === normalizedSceneId
        ) || null;
    }

    static getReturnNavigation(sceneOrId) {
        const sceneId = typeof sceneOrId === "string" ? sceneOrId : sceneOrId?.id;
        const location = this.getLocationForScene(sceneId);
        if (!location) return null;

        const parentScene = this.#resolveNearestParentScene(location, { ownedSceneId: sceneId });
        if (!parentScene) return null;
        return {
            parentSceneId: parentScene.id,
            sceneId: parentScene.id,
            parentSiteId: location.hierarchy?.parent?.kind === "site"
                ? location.hierarchy.parent.siteId || null
                : null,
            locationId: location.id,
            transitionStyle: "focus-placeable",
            transitionContext: {}
        };
    }

    static async create(locationId) {
        const location = LocationEntityStore.getLocation(locationId);
        if (!location) throw new Error("Location could not be found.");
        if (location.scenes?.primarySceneId) throw new Error("This Location already has a Scene.");

        const scene = await Scene.create({
            name: location.display?.name || "Location Map",
            navigation: false
        });
        try {
            await this.link(location.id, scene.id);
        } catch (err) {
            await scene.delete();
            throw err;
        }
        await scene.view();
        return scene;
    }

    static async link(locationId, sceneId) {
        return this.#assign(locationId, sceneId, { replace: false });
    }

    static async replace(locationId, sceneId) {
        return this.#assign(locationId, sceneId, { replace: true });
    }

    static async #assign(locationId, sceneId, { replace = false } = {}) {
        const location = LocationEntityStore.getLocation(locationId);
        if (!location) throw new Error("Location could not be found.");
        const previousSceneId = location.scenes?.primarySceneId || null;
        if (location.scenes?.primarySceneId && !replace) {
            throw new Error("Detach the current Location Scene before linking another one.");
        }

        const scene = game.scenes.get(String(sceneId || "").trim());
        if (!scene) throw new Error("Scene could not be found.");
        if (scene.id === NexusLineageManager.getRootScene()?.id || scene.getFlag(MODULE_ID, NexusLineageManager.ROOT_FLAG) === true) {
            throw new Error("The designated Nexus root Scene cannot belong to a Location.");
        }
        if (NexusLineageManager.getParentScene(scene) || scene.getFlag(MODULE_ID, "navigation")?.parentSceneId) {
            throw new Error("Only a root Scene with no parent can belong to a Location.");
        }
        if (LegacySciFiCompatibility.getSceneKind(scene)) {
            throw new Error("Legacy Sci-Fi Scenes cannot belong to a Location.");
        }

        const parent = location.hierarchy?.parent || null;
        const containingSceneId = parent?.kind === "scene"
            ? parent.sceneId
            : parent?.kind === "site" ? parent.parentSceneId : null;
        if (containingSceneId === scene.id) {
            throw new Error("A Location cannot own the Scene that currently contains it.");
        }

        const owner = this.getLocationForScene(scene.id, { excludeLocationId: location.id });
        if (owner) throw new Error(`This Scene already belongs to ${owner.display?.name || "another Location"}.`);
        if (this.#isSiteScene(scene)) throw new Error("A Scene already owned by a Site cannot also belong to a Location.");

        const [{ PlaceHierarchyModel }, { PlaceTypeRegistry }] = await Promise.all([
            import("../../places/models/PlaceHierarchyModel.js"),
            import("../../places/services/PlaceTypeRegistry.js")
        ]);
        const sceneBranch = PlaceHierarchyModel.getBranch(
            PlaceTypeRegistry.getNodes({ isGM: true, includeHidden: true }),
            `scene:${scene.id}`
        );
        if (sceneBranch.some(node => node.key === `location:${location.id}`)) {
            throw new Error("A Location cannot own a Scene branch that already contains it.");
        }

        const linked = await LocationEntityStore.updateLocation(location.id, {
            scenes: { primarySceneId: scene.id }
        });
        this.#refreshActiveSceneControls([previousSceneId, scene.id]);
        return linked;
    }

    static async detach(locationId) {
        const location = LocationEntityStore.getLocation(locationId);
        if (!location) throw new Error("Location could not be found.");
        if (!location.scenes?.primarySceneId) return location;
        const previousSceneId = location.scenes.primarySceneId;
        const detached = await LocationEntityStore.updateLocation(location.id, {
            scenes: { primarySceneId: null }
        });
        this.#refreshActiveSceneControls([previousSceneId]);
        return detached;
    }

    static async open(locationId) {
        const scene = this.getPrimaryScene(locationId);
        if (!scene) {
            ui.notifications.warn("This Location does not have a Scene.");
            return false;
        }
        await scene.view();
        return true;
    }

    static #isSiteScene(scene) {
        const lineage = scene.getFlag(MODULE_ID, "lineage") || {};
        const site = scene.getFlag(MODULE_ID, "site") || {};
        return scene.getFlag(MODULE_ID, "siteScene") === true
            || !!lineage.parentSiteId
            || (!!lineage.parentSceneId && !!site.siteId)
            || (!!site.parentSceneId && !!site.siteId);
    }

    static #resolveNearestParentScene(location, { ownedSceneId = null } = {}) {
        const visited = new Set();
        let cursor = location;

        while (cursor && !visited.has(cursor.id)) {
            visited.add(cursor.id);
            const parent = cursor.hierarchy?.parent || null;
            if (parent?.kind === "scene") {
                const scene = game.scenes.get(parent.sceneId) || null;
                return scene?.id !== ownedSceneId ? scene : null;
            }
            if (parent?.kind === "site") {
                const scene = game.scenes.get(parent.parentSceneId) || null;
                return scene?.id !== ownedSceneId ? scene : null;
            }
            if (parent?.kind !== "location") return null;

            const parentLocation = LocationEntityStore.getLocation(parent.locationId);
            if (!parentLocation) return null;
            const parentLocationScene = game.scenes.get(parentLocation.scenes?.primarySceneId) || null;
            if (parentLocationScene && parentLocationScene.id !== ownedSceneId) return parentLocationScene;
            cursor = parentLocation;
        }

        return null;
    }

    static #refreshActiveSceneControls(sceneIds = []) {
        if (!canvas.scene?.id || !(sceneIds || []).filter(Boolean).includes(canvas.scene.id)) return;
        if (!ui.controls?.render) return;
        void Promise.resolve(ui.controls.render({ reset: true })).catch(error => {
            console.warn("Augur Nexus | Failed to refresh Scene controls after a Location Scene change.", error);
        });
    }

    static async #clearDeletedScene(scene) {
        if (!scene?.id) return;
        const owners = LocationEntityStore.getLocations()
            .filter(location => location.scenes?.primarySceneId === scene.id);
        for (const location of owners) {
            await LocationEntityStore.updateLocation(location.id, {
                scenes: { primarySceneId: null }
            });
        }
    }
}
