import { LocationLeadershipModel } from "../models/LocationLeadershipModel.js";
import { ConnectionStore } from "./ConnectionStore.js";
import { ConnectionTargetResolver } from "./ConnectionTargetResolver.js";

export class LocationLeadershipService {
    static canSetLeader(leftTarget, rightTarget) {
        return !!this.#resolveTargetEndpoints(leftTarget, rightTarget);
    }

    static isLeadershipEdge(edgeOrId) {
        const edge = typeof edgeOrId === "string" ? ConnectionStore.getConnectionEdge(edgeOrId) : edgeOrId;
        return LocationLeadershipModel.isLeadership(edge);
    }

    static getPresentation(edgeOrLeadership = null) {
        const value = edgeOrLeadership?.locationLeadership || edgeOrLeadership || "ruler";
        return LocationLeadershipModel.getPresentation(value);
    }

    static getPresentationForTargets(leftTarget, rightTarget) {
        const endpoints = this.#resolveTargetEndpoints(leftTarget, rightTarget);
        return LocationLeadershipModel.getPresentation(endpoints?.locationNode?.locationAuthorityType || "ruler");
    }

    static getLeader(locationTarget, { user = game.user, includeHidden = false } = {}) {
        const locationNodeId = ConnectionTargetResolver.getNodeId(locationTarget);
        const entry = this.#getLeadershipEntries(locationNodeId)
            .find(candidate => candidate.leadership.locationNodeId === locationNodeId);
        if (!entry) return null;
        if (!includeHidden && !ConnectionStore.canUserSeeConnection(entry.edge, user)) return null;
        if (!includeHidden && !ConnectionTargetResolver.canUserSeeNode(entry.otherNode, user)) return null;
        return this.#toLocationLeadershipView(entry, locationTarget);
    }

    static getLocationsForLeader(leaderTarget, { user = game.user, includeHidden = false } = {}) {
        const leaderNodeId = ConnectionTargetResolver.getNodeId(leaderTarget);
        return this.#getLeadershipEntries(leaderNodeId)
            .filter(entry => entry.leadership.leaderNodeId === leaderNodeId)
            .filter(entry => includeHidden || ConnectionStore.canUserSeeConnection(entry.edge, user))
            .filter(entry => includeHidden || ConnectionTargetResolver.canUserSeeNode(entry.otherNode, user))
            .map(entry => this.#toNpcLeadershipView(entry))
            .filter(Boolean)
            .sort((left, right) => left.location.name.localeCompare(right.location.name));
    }

    static async setLeader(locationTarget, leaderTarget, { edgeId = "", authorityType = "", role = "" } = {}) {
        if (!game.user?.isGM) return null;
        const endpoints = this.#resolveTargetEndpoints(locationTarget, leaderTarget);
        if (!endpoints) throw new Error("Location leadership requires one Location and one Person.");
        const selected = edgeId
            ? ConnectionStore.getConnectionEdge(edgeId)
            : this.#findEdge(endpoints.locationNode.id, endpoints.leaderNode.id);
        const resolvedAuthorityType = authorityType
            || selected?.locationLeadership?.authorityType
            || endpoints.locationNode.locationAuthorityType
            || "ruler";
        const leadership = LocationLeadershipModel.create(endpoints.locationNode, endpoints.leaderNode, {
            authorityType: resolvedAuthorityType
        });
        const presentation = LocationLeadershipModel.getPresentation(leadership);
        const displayRole = String(role || presentation.title).trim() || presentation.title;
        const views = {
            ...(selected?.views || {}),
            [endpoints.leaderNode.id]: {
                ...(selected?.views?.[endpoints.leaderNode.id] || {}),
                category: "place",
                role: `${presentation.relationshipRole} ${endpoints.locationNode.name || "Location"}`
            },
            [endpoints.locationNode.id]: {
                ...(selected?.views?.[endpoints.locationNode.id] || {}),
                category: "npc",
                role: displayRole
            }
        };

        return ConnectionStore.runBatch(async () => {
            for (const current of this.#getLeadershipEntries(endpoints.locationNode.id)) {
                if (current.leadership.locationNodeId !== endpoints.locationNode.id) continue;
                if (current.edge.id === edgeId || current.leadership.leaderNodeId === endpoints.leaderNode.id) continue;
                await ConnectionStore.updateConnection(current.edge.id, { locationLeadership: null });
            }

            if (selected) {
                if (!this.#connects(selected, endpoints.locationNode.id, endpoints.leaderNode.id)) {
                    throw new Error("The selected Connection does not join this Location and Person.");
                }
                return ConnectionStore.updateConnection(selected.id, { locationLeadership: leadership, views });
            }

            return ConnectionStore.addConnection(endpoints.leaderNode, endpoints.locationNode, {
                locationLeadership: leadership,
                sourceView: views[endpoints.leaderNode.id],
                targetView: { category: "npc", role: displayRole }
            });
        }, { reason: "setLocationLeader" });
    }

    static async clearLeader(locationTarget, { edgeId = "" } = {}) {
        if (!game.user?.isGM) return false;
        if (edgeId) {
            const edge = ConnectionStore.getConnectionEdge(edgeId);
            if (!LocationLeadershipModel.isLeadership(edge)) return false;
            await ConnectionStore.updateConnection(edgeId, { locationLeadership: null });
            return true;
        }
        const current = this.getLeader(locationTarget, { includeHidden: true });
        if (!current) return false;
        await ConnectionStore.updateConnection(current.edgeId, { locationLeadership: null });
        return true;
    }

    static async openLeader(locationTarget, options = {}) {
        const leadership = this.getLeader(locationTarget, options);
        const node = leadership ? ConnectionStore.getNode(leadership.leaderNodeId) : null;
        if (!node) return false;
        await ConnectionTargetResolver.openNode(node);
        return true;
    }

    static #getLeadershipEntries(nodeId = "") {
        if (!nodeId) return [];
        return (ConnectionStore.getGraphContext().adjacency.get(nodeId) || [])
            .map(({ edge, node }) => ({ edge, otherNode: node, leadership: edge.locationLeadership }))
            .filter(entry => !!entry.leadership);
    }

    static #toLocationLeadershipView(entry, locationTarget = null) {
        const display = ConnectionTargetResolver.resolveDisplayNodeSync(entry.otherNode) || entry.otherNode;
        if (!display) return null;
        const locationNode = this.#nodeLike(locationTarget);
        return {
            edgeId: entry.edge.id,
            edge: entry.edge,
            locationNodeId: entry.leadership.locationNodeId,
            leaderNodeId: entry.leadership.leaderNodeId,
            presentation: LocationLeadershipModel.getPresentation(locationNode?.locationAuthorityType || entry.leadership),
            leader: {
                id: entry.otherNode.id,
                entityId: entry.otherNode.entityId || "",
                name: display.name || "Unknown Person",
                imageSrc: display.img || "",
                subtitle: display.subtitle || ""
            }
        };
    }

    static #toNpcLeadershipView(entry) {
        const display = ConnectionTargetResolver.resolveDisplayNodeSync(entry.otherNode) || entry.otherNode;
        if (!display) return null;
        return {
            edgeId: entry.edge.id,
            locationNodeId: entry.leadership.locationNodeId,
            leaderNodeId: entry.leadership.leaderNodeId,
            presentation: LocationLeadershipModel.getPresentation(display.locationAuthorityType || entry.leadership),
            location: {
                id: entry.otherNode.id,
                entityId: entry.otherNode.entityId || "",
                name: display.name || "Unknown Location",
                imageSrc: display.img || "",
                subtitle: display.subtitle || ""
            }
        };
    }

    static #resolveTargetEndpoints(leftTarget, rightTarget) {
        const leftNode = this.#nodeLike(leftTarget);
        const rightNode = this.#nodeLike(rightTarget);
        return LocationLeadershipModel.resolveEndpoints(leftNode, rightNode);
    }

    static #nodeLike(target) {
        const id = ConnectionTargetResolver.getNodeId(target);
        if (!id) return null;
        return { ...(ConnectionStore.getNode(id) || {}), ...target, id };
    }

    static #findEdge(leftNodeId, rightNodeId) {
        return (ConnectionStore.getGraphContext().adjacency.get(leftNodeId) || [])
            .find(({ node }) => node.id === rightNodeId)?.edge || null;
    }

    static #connects(edge, leftNodeId, rightNodeId) {
        return (edge?.sourceNodeId === leftNodeId && edge?.targetNodeId === rightNodeId)
            || (edge?.sourceNodeId === rightNodeId && edge?.targetNodeId === leftNodeId);
    }
}
