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

const RELATION_TYPE = "ownership";
const OWNER_ENTITY_TYPES = new Set(["faction", "npc"]);

export class OwnershipService {
    static RELATION_TYPE = RELATION_TYPE;

    static getOwnership(assetTarget, { user = game.user, includeHidden = false } = {}) {
        const assetNodeId = ConnectionTargetResolver.getNodeId(assetTarget);
        const entry = this.#getOwnershipEntry(assetNodeId);
        if (!entry) return null;
        if (!includeHidden && !ConnectionStore.canUserSeeConnection(entry.edge, user)) return null;
        if (!includeHidden && !ConnectionTargetResolver.canUserSeeNode(entry.node, user)) return null;

        const owner = ConnectionTargetResolver.resolveDisplayNodeSync(entry.node) || entry.node;
        return {
            edgeId: entry.edge.id,
            relationType: RELATION_TYPE,
            assetNodeId,
            ownerNodeId: entry.node.id,
            owner: {
                id: entry.node.id,
                entityId: entry.node.entityId || "",
                entityType: entry.node.entityType || "",
                name: owner.name || "Unknown Owner",
                imageSrc: owner.img || "",
                subtitle: owner.subtitle || "",
                icon: this.#iconFor(entry.node)
            }
        };
    }

    static getHoldingsForOwner(ownerTarget, { user = game.user, includeHidden = false } = {}) {
        const ownerNodeId = ConnectionTargetResolver.getNodeId(ownerTarget);
        if (!ownerNodeId) return [];
        return (ConnectionStore.getGraphContext().adjacency.get(ownerNodeId) || [])
            .filter(({ edge }) => edge.relationType === RELATION_TYPE && edge.sourceNodeId === ownerNodeId)
            .filter(({ edge, node }) => includeHidden
                || ConnectionStore.canUserSeeConnection(edge, user) && ConnectionTargetResolver.canUserSeeNode(node, user))
            .map(({ edge, node }) => {
                const display = ConnectionTargetResolver.resolveDisplayNodeSync(node) || node;
                return {
                    edgeId: edge.id,
                    edge,
                    assetNodeId: node.id,
                    ownerNodeId,
                    asset: {
                        id: node.id,
                        entityId: node.entityId || "",
                        entityType: node.entityType || "",
                        name: display.name || "Unknown Holding",
                        imageSrc: display.img || "",
                        subtitle: display.subtitle || "",
                        icon: this.#iconFor(node)
                    }
                };
            })
            .sort((left, right) => left.asset.name.localeCompare(right.asset.name));
    }

    static canSetOwner(assetTarget, ownerTarget) {
        const asset = this.#nodeLike(assetTarget);
        const owner = this.#nodeLike(ownerTarget);
        return this.#isPlace(asset)
            && this.#isEligibleOwner(owner)
            && asset.id !== owner.id;
    }

    static isOwnershipEdge(edge, assetTarget) {
        const assetNodeId = ConnectionTargetResolver.getNodeId(assetTarget);
        return edge?.relationType === RELATION_TYPE && edge.targetNodeId === assetNodeId;
    }

    static async setOwner(assetTarget, ownerTarget, { edgeId = "" } = {}) {
        if (!game.user?.isGM) return null;
        const asset = this.#nodeLike(assetTarget);
        const owner = this.#nodeLike(ownerTarget);
        if (!this.canSetOwner(asset, owner)) throw new Error("That connection cannot be used as Place ownership.");
        if (this.#wouldCreateCycle(asset.id, owner.id)) {
            throw new Error("That ownership would create a cycle between Locations.");
        }

        return ConnectionStore.runBatch(async () => {
            const current = this.#getOwnershipEntry(asset.id);
            if (current && current.edge.id !== edgeId) {
                await ConnectionStore.updateConnection(current.edge.id, { relationType: "" });
            }

            const selected = edgeId ? ConnectionStore.getConnectionEdge(edgeId) : this.#findEdge(asset.id, owner.id);
            if (selected) {
                if (!this.#connects(selected, asset.id, owner.id)) {
                    throw new Error("The selected Connection does not join this owner and Location.");
                }
                return ConnectionStore.updateConnection(selected.id, {
                    sourceNodeId: owner.id,
                    targetNodeId: asset.id,
                    relationType: RELATION_TYPE
                });
            }

            return ConnectionStore.addConnection(owner, asset, {
                relationType: RELATION_TYPE,
                sourceView: { category: "place", role: "Owns" },
                targetView: {
                    category: owner.entityType === "faction" ? "faction" : owner.entityType === "npc" ? "npc" : "place",
                    role: "Owned By"
                }
            });
        }, { reason: "setOwnership" });
    }

    static async clearOwner(assetTarget, { edgeId = "" } = {}) {
        if (!game.user?.isGM) return false;
        const assetNodeId = ConnectionTargetResolver.getNodeId(assetTarget);
        const current = this.#getOwnershipEntry(assetNodeId);
        if (!current || (edgeId && current.edge.id !== edgeId)) return false;
        await ConnectionStore.updateConnection(current.edge.id, { relationType: "" });
        return true;
    }

    static async openOwner(assetTarget, options = {}) {
        const ownership = this.getOwnership(assetTarget, options);
        if (!ownership) return false;
        const node = ConnectionStore.getNode(ownership.ownerNodeId);
        if (!node) return false;
        await ConnectionTargetResolver.openNode(node);
        return true;
    }

    static #getOwnershipEntry(assetNodeId) {
        if (!assetNodeId) return null;
        return (ConnectionStore.getGraphContext().adjacency.get(assetNodeId) || [])
            .find(({ edge }) => edge.relationType === RELATION_TYPE && edge.targetNodeId === assetNodeId) || null;
    }

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

    static #wouldCreateCycle(assetNodeId, ownerNodeId) {
        let cursor = ownerNodeId;
        const visited = new Set();
        while (cursor && !visited.has(cursor)) {
            if (cursor === assetNodeId) return true;
            visited.add(cursor);
            const node = ConnectionStore.getNode(cursor);
            if (!this.#isPlace(node)) return false;
            cursor = this.#getOwnershipEntry(cursor)?.node?.id || "";
        }
        return false;
    }

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

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

    static #isPlace(node) {
        return node?.kind === "nexus-site"
            || node?.kind === "nexus-scene"
            || (node?.kind === "nexus-entity" && node.entityType === "location");
    }

    static #isEligibleOwner(node) {
        return node?.kind === "nexus-entity" && OWNER_ENTITY_TYPES.has(node.entityType);
    }

    static #iconFor(node) {
        if (node?.entityType === "faction") return "fas fa-shield-halved";
        if (node?.entityType === "npc") return "fas fa-user";
        return "fas fa-landmark";
    }
}
