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

const RELATION_TYPE = "trade-center";

export class TradingCenterService {
    static RELATION_TYPE = RELATION_TYPE;

    static getTradingCenter(locationTarget, { user = game.user, includeHidden = false } = {}) {
        const locationNodeId = ConnectionTargetResolver.getNodeId(locationTarget);
        const entry = this.#getEntry(locationNodeId);
        if (!entry) return null;
        if (!includeHidden && !ConnectionStore.canUserSeeConnection(entry.edge, user)) return null;
        if (!includeHidden && !ConnectionTargetResolver.canUserSeeNode(entry.node, user)) return null;

        const center = ConnectionTargetResolver.resolveDisplayNodeSync(entry.node) || entry.node;
        return {
            edgeId: entry.edge.id,
            relationType: RELATION_TYPE,
            locationNodeId,
            centerNodeId: entry.node.id,
            edge: foundry.utils.deepClone(entry.edge),
            center: {
                id: entry.node.id,
                entityId: entry.node.entityId || "",
                name: center.name || "Unknown Trading Center",
                imageSrc: center.img || "",
                subtitle: center.subtitle || "",
                icon: "fas fa-coins"
            }
        };
    }

    static canSetTradingCenter(locationTarget, centerTarget) {
        const location = this.#nodeLike(locationTarget);
        const center = this.#nodeLike(centerTarget);
        return this.#isLocation(location)
            && this.#isLocation(center)
            && location.id !== center.id
            && !this.#wouldCreateCycle(location.id, center.id);
    }

    static isTradingCenterEdge(edge, locationTarget) {
        const locationNodeId = ConnectionTargetResolver.getNodeId(locationTarget);
        return edge?.relationType === RELATION_TYPE && edge.targetNodeId === locationNodeId;
    }

    static async setTradingCenter(locationTarget, centerTarget, { edgeId = "", clearPreviousFlows = false } = {}) {
        if (!game.user?.isGM) return null;
        const location = this.#nodeLike(locationTarget);
        const center = this.#nodeLike(centerTarget);
        if (!this.#isLocation(location) || !this.#isLocation(center) || location.id === center.id) {
            throw new Error("That Connection cannot be used as a Location's trading center.");
        }
        if (this.#wouldCreateCycle(location.id, center.id)) {
            throw new Error("That trading relationship would create a cycle between Locations.");
        }

        return ConnectionStore.runBatch(async () => {
            const current = this.#getEntry(location.id);
            const selected = edgeId ? ConnectionStore.getConnectionEdge(edgeId) : this.#findEdge(location.id, center.id);
            if (
                current
                && selected
                && current.edge.id === selected.id
                && current.node.id === center.id
            ) return current.edge;
            if (current && current.edge.id !== selected?.id) {
                await ConnectionStore.updateConnection(current.edge.id, {
                    relationType: "",
                    ...(clearPreviousFlows ? { resourceFlows: [] } : {})
                });
            }

            if (selected) {
                if (!this.#connects(selected, location.id, center.id)) {
                    throw new Error("The selected Connection does not join this Location and trading center.");
                }
                return ConnectionStore.updateConnection(selected.id, {
                    sourceNodeId: center.id,
                    targetNodeId: location.id,
                    relationType: RELATION_TYPE
                });
            }

            return ConnectionStore.addConnection(center, location, {
                relationType: RELATION_TYPE,
                sourceView: { category: "place", role: "Trading Reach" },
                targetView: { category: "place", role: "Trades Through" }
            });
        }, { reason: "setTradingCenter" });
    }

    static async clearTradingCenter(locationTarget, { edgeId = "", clearResourceFlows = false } = {}) {
        if (!game.user?.isGM) return false;
        const locationNodeId = ConnectionTargetResolver.getNodeId(locationTarget);
        const current = this.#getEntry(locationNodeId);
        if (!current || (edgeId && current.edge.id !== edgeId)) return false;
        await ConnectionStore.updateConnection(current.edge.id, {
            relationType: "",
            ...(clearResourceFlows ? { resourceFlows: [] } : {})
        });
        return true;
    }

    static async openTradingCenter(locationTarget, options = {}) {
        const tradingCenter = this.getTradingCenter(locationTarget, options);
        if (!tradingCenter) return false;
        const node = ConnectionStore.getNode(tradingCenter.centerNodeId);
        if (!node) return false;
        await ConnectionTargetResolver.openNode(node);
        return true;
    }

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

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

    static #wouldCreateCycle(locationNodeId, centerNodeId) {
        let cursor = centerNodeId;
        const visited = new Set();
        while (cursor && !visited.has(cursor)) {
            if (cursor === locationNodeId) return true;
            visited.add(cursor);
            cursor = this.#getEntry(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 #isLocation(node) {
        return node?.kind === "nexus-entity" && node.entityType === "location";
    }
}
