import { getNexusMapMarker } from "../../../api/markers.js";
import { ConnectionStore } from "../../connections/services/ConnectionStore.js";
import { ConnectionTargetResolver } from "../../connections/services/ConnectionTargetResolver.js";
import { PlaceHierarchyModel } from "../../places/models/PlaceHierarchyModel.js";
import { PlaceTypeRegistry } from "../../places/services/PlaceTypeRegistry.js";

const ENTITY_TYPES = new Set(["npc", "faction", "ship"]);

export class SceneEntityPresenceModel {
    static build(scene, { user = game.user } = {}) {
        const result = this.#empty(scene);
        if (!scene?.id) return result;

        const placeNodes = PlaceTypeRegistry.getNodes({
            isGM: user?.isGM === true,
            includeHidden: user?.isGM === true
        });
        const placeRows = PlaceHierarchyModel.buildRows(placeNodes);
        const placeKeys = new Set();

        for (const node of placeNodes) {
            if (node.primarySceneId === scene.id
                || (node.type === "scene" && (node.sceneId === scene.id || node.objectId === scene.id))
                || (node.type === "site" && node.parentSceneId === scene.id)) {
                placeKeys.add(node.key);
            }
        }

        const sceneNodeId = ConnectionTargetResolver.getSceneNodeId(scene.id);
        result.anchorNodeIds.add(sceneNodeId);
        this.#readMarkers(scene, placeNodes, placeKeys, result);

        const localPlaceRows = this.#expandPlaceBranches(placeNodes, placeRows, placeKeys);
        for (const row of localPlaceRows) {
            for (const nodeId of this.#getPlaceConnectionNodeIds(row)) {
                result.anchorNodeIds.add(nodeId);
            }
        }

        for (const nodeId of result.anchorNodeIds) {
            for (const { edge, node } of ConnectionStore.getConnectionsForNode(nodeId)) {
                this.#addEntityNode(result, node, "local-place-connection", edge?.id);
            }
        }

        this.#expandFactionDependents(result);
        this.#expandShipCrew(result);
        return result;
    }

    static #readMarkers(scene, placeNodes, placeKeys, result) {
        for (const tile of scene.tiles?.contents || []) {
            const marker = getNexusMapMarker(tile);
            if (!marker) continue;
            const target = marker.target || {};

            if (target.kind === "campaign-entity") {
                if (target.entityType === "location" && target.entityId) {
                    placeKeys.add(`location:${target.entityId}`);
                } else {
                    this.#addEntity(result, target.entityType, target.entityId, "direct-marker", marker.markerId);
                }
                continue;
            }

            if (target.kind === "nexus-site" || marker.kind === "site") {
                const parentSceneId = target.parentSceneId || scene.id;
                const siteId = target.siteId || marker.id;
                if (parentSceneId && siteId) placeKeys.add(`site:${parentSceneId}:${siteId}`);
                continue;
            }

            if (target.kind === "nexus-scene" && target.sceneId) {
                const referenced = placeNodes.find(node => node.primarySceneId === target.sceneId
                    || (node.type === "scene" && (node.sceneId === target.sceneId || node.objectId === target.sceneId)));
                if (referenced?.key) placeKeys.add(referenced.key);
                result.anchorNodeIds.add(ConnectionTargetResolver.getSceneNodeId(target.sceneId));
            }
        }
    }

    static #expandPlaceBranches(placeNodes, placeRows, rootKeys) {
        const localKeys = new Set();
        for (const rootKey of rootKeys) {
            for (const row of PlaceHierarchyModel.getBranch(placeNodes, rootKey)) localKeys.add(row.key);
        }
        return placeRows.filter(row => localKeys.has(row.key));
    }

    static #getPlaceConnectionNodeIds(row) {
        const ids = new Set();
        const targetId = ConnectionTargetResolver.getNodeId(row.connectionTarget);
        if (targetId) ids.add(targetId);

        if (row.type === "location" && row.entityId) {
            ids.add(ConnectionTargetResolver.getEntityNodeId(row.entityId));
            if (row.primarySceneId) ids.add(ConnectionTargetResolver.getSceneNodeId(row.primarySceneId));
        } else if (row.type === "site" && row.parentSceneId && row.siteId) {
            ids.add(ConnectionTargetResolver.getSiteNodeId(row.parentSceneId, row.siteId));
        } else if (row.type === "scene") {
            const sceneId = row.sceneId || row.objectId;
            if (sceneId) ids.add(ConnectionTargetResolver.getSceneNodeId(sceneId));
        }
        return ids;
    }

    static #expandFactionDependents(result) {
        for (const factionId of [...result.factionIds]) {
            const factionNodeId = ConnectionTargetResolver.getEntityNodeId(factionId);
            for (const { edge, node } of ConnectionStore.getConnectionsForNode(factionNodeId)) {
                if (edge?.factionMembership?.factionNodeId === factionNodeId) {
                    this.#addEntityNode(result, node, "faction-member", edge.id);
                    continue;
                }
                const category = ConnectionStore.getConnectionCategoryForNode(edge, factionNodeId);
                if (category === "ship" && node?.entityType === "ship") {
                    this.#addEntityNode(result, node, "faction-ship", edge.id);
                }
            }
        }
    }

    static #expandShipCrew(result) {
        for (const shipId of [...result.shipIds]) {
            const shipNodeId = ConnectionTargetResolver.getEntityNodeId(shipId);
            for (const { edge, node } of ConnectionStore.getConnectionsForNode(shipNodeId)) {
                const category = ConnectionStore.getConnectionCategoryForNode(edge, shipNodeId);
                if (category === "crew" && node?.entityType === "npc") {
                    this.#addEntityNode(result, node, "ship-crew", edge.id);
                }
            }
        }
    }

    static #addEntityNode(result, node, reason, sourceId = "") {
        if (node?.kind !== "nexus-entity") return;
        this.#addEntity(result, node.entityType, node.entityId, reason, sourceId);
    }

    static #addEntity(result, entityType, entityId, reason, sourceId = "") {
        const type = String(entityType || "").trim();
        const id = String(entityId || "").trim();
        if (!ENTITY_TYPES.has(type) || !id) return;
        result[`${type}Ids`].add(id);

        const nodeId = ConnectionTargetResolver.getEntityNodeId(id);
        const reasons = result.reasonsByNodeId.get(nodeId) || [];
        if (!reasons.some(entry => entry.reason === reason && entry.sourceId === sourceId)) {
            reasons.push({ reason, sourceId: String(sourceId || "") });
            result.reasonsByNodeId.set(nodeId, reasons);
        }
    }

    static #empty(scene = null) {
        return {
            sceneId: scene?.id || "",
            sceneName: scene?.name || "",
            npcIds: new Set(),
            factionIds: new Set(),
            shipIds: new Set(),
            anchorNodeIds: new Set(),
            reasonsByNodeId: new Map()
        };
    }
}
