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

export class ShipCaptainService {
    static getCaptain(shipTarget, { user = game.user, includeHidden = false } = {}) {
        const shipNodeId = ConnectionTargetResolver.getNodeId(shipTarget);
        const entry = this.#captainEntries(shipNodeId)[0] || null;
        if (!entry) return null;
        if (!includeHidden && !ConnectionStore.canUserSeeConnection(entry.edge, user)) return null;
        if (!includeHidden && !ConnectionTargetResolver.canUserSeeNode(entry.otherNode, user)) return null;
        return this.#view(entry);
    }

    static getShipsForCaptain(captainTarget, { user = game.user, includeHidden = false } = {}) {
        const captainNodeId = ConnectionTargetResolver.getNodeId(captainTarget);
        return this.#captainEntries(captainNodeId)
            .filter(entry => entry.captain.captainNodeId === captainNodeId)
            .filter(entry => includeHidden || ConnectionStore.canUserSeeConnection(entry.edge, user))
            .filter(entry => includeHidden || ConnectionTargetResolver.canUserSeeNode(entry.otherNode, user))
            .map(entry => this.#shipView(entry))
            .filter(Boolean);
    }

    static async setCaptain(shipTarget, captainTarget, { edgeId = "" } = {}) {
        if (!game.user?.isGM) return null;
        const endpoints = this.#endpoints(shipTarget, captainTarget);
        if (!endpoints) throw new Error("Ship command requires one Ship and one Person.");
        const selected = edgeId
            ? ConnectionStore.getConnectionEdge(edgeId)
            : this.#findEdge(endpoints.shipNode.id, endpoints.captainNode.id);
        const shipCaptain = ShipCaptainModel.create(endpoints.shipNode, endpoints.captainNode);
        const views = {
            ...(selected?.views || {}),
            [endpoints.shipNode.id]: {
                ...(selected?.views?.[endpoints.shipNode.id] || {}),
                category: "npc",
                role: "Captain"
            },
            [endpoints.captainNode.id]: {
                ...(selected?.views?.[endpoints.captainNode.id] || {}),
                category: "ship",
                role: `Captains ${endpoints.shipNode.name || "Ship"}`
            }
        };

        return ConnectionStore.runBatch(async () => {
            for (const current of this.#captainEntries(endpoints.shipNode.id)) {
                if (current.edge.id === selected?.id) continue;
                await ConnectionStore.updateConnection(current.edge.id, {
                    shipCaptain: null,
                    relationType: "ship-crew",
                    views: this.#crewViews(current.edge, current.captain.shipNodeId, current.captain.captainNodeId)
                });
            }

            if (selected) {
                if (!this.#connects(selected, endpoints.shipNode.id, endpoints.captainNode.id)) {
                    throw new Error("The selected Connection does not join this Ship and Person.");
                }
                return ConnectionStore.updateConnection(selected.id, {
                    relationType: "ship-captain",
                    shipCaptain,
                    views
                });
            }

            return ConnectionStore.addConnection(endpoints.shipNode, endpoints.captainNode, {
                relationType: "ship-captain",
                shipCaptain,
                sourceView: views[endpoints.shipNode.id],
                targetView: views[endpoints.captainNode.id]
            });
        }, { reason: "setShipCaptain" });
    }

    static async clearCaptain(shipTarget) {
        if (!game.user?.isGM) return false;
        const current = this.getCaptain(shipTarget, { includeHidden: true });
        if (!current) return false;
        await ConnectionStore.updateConnection(current.edgeId, {
            shipCaptain: null,
            relationType: "ship-crew",
            views: this.#crewViews(current.edge, current.shipNodeId, current.captainNodeId)
        });
        return true;
    }

    static #captainEntries(nodeId = "") {
        if (!nodeId) return [];
        return (ConnectionStore.getGraphContext().adjacency.get(nodeId) || [])
            .map(({ edge, node }) => ({
                edge,
                otherNode: node,
                captain: edge.shipCaptain || this.#legacyCaptain(edge)
            }))
            .filter(entry => !!entry.captain)
            .sort((left, right) => Number(left.edge.createdTime || 0) - Number(right.edge.createdTime || 0)
                || String(left.edge.id).localeCompare(String(right.edge.id)));
    }

    static #legacyCaptain(edge = {}) {
        if (edge.relationType !== "ship-captain") return null;
        const source = ConnectionStore.getNode(edge.sourceNodeId);
        const target = ConnectionStore.getNode(edge.targetNodeId);
        return ShipCaptainModel.create(source, target);
    }

    static #view(entry) {
        const display = ConnectionTargetResolver.resolveDisplayNodeSync(entry.otherNode) || entry.otherNode;
        return {
            edgeId: entry.edge.id,
            edge: entry.edge,
            shipNodeId: entry.captain.shipNodeId,
            captainNodeId: entry.captain.captainNodeId,
            captain: {
                nodeId: entry.otherNode.id,
                entityId: entry.otherNode.entityId || "",
                name: display.name || "Unknown Captain",
                imageSrc: display.img || "",
                subtitle: display.subtitle || "Captain"
            }
        };
    }

    static #shipView(entry) {
        const display = ConnectionTargetResolver.resolveDisplayNodeSync(entry.otherNode) || entry.otherNode;
        return {
            edgeId: entry.edge.id,
            shipNodeId: entry.captain.shipNodeId,
            captainNodeId: entry.captain.captainNodeId,
            ship: {
                nodeId: entry.otherNode.id,
                entityId: entry.otherNode.entityId || "",
                name: display.name || "Unknown Ship",
                imageSrc: display.img || "",
                subtitle: display.subtitle || "Ship"
            }
        };
    }

    static #crewViews(edge = {}, shipNodeId = "", npcNodeId = "") {
        return {
            ...(edge.views || {}),
            [shipNodeId]: { ...(edge.views?.[shipNodeId] || {}), category: "crew", role: "Crew" },
            [npcNodeId]: { ...(edge.views?.[npcNodeId] || {}), category: "ship", role: "Crew Member" }
        };
    }

    static #endpoints(left, right) {
        return ShipCaptainModel.resolveEndpoints(this.#nodeLike(left), this.#nodeLike(right));
    }

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

    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);
    }
}
