import { ShipLocationModel } from "../models/ShipLocationModel.js";
import { ConnectionStore } from "./ConnectionStore.js";
import { ConnectionTargetResolver } from "./ConnectionTargetResolver.js";
import { CampaignEntityJournalStore } from "../../campaign-entities/services/CampaignEntityJournalStore.js";

export class ShipLocationService {
    static getCurrentLocation(shipTarget, { user = game.user, includeHidden = false, allowMarkerFallback = null } = {}) {
        const shipNodeId = ConnectionTargetResolver.getNodeId(shipTarget);
        const entry = this.#entries(shipNodeId)[0] || null;
        if (entry) {
            if (!includeHidden && !ConnectionStore.canUserSeeConnection(entry.edge, user)) return null;
            if (!includeHidden && !ConnectionTargetResolver.canUserSeeNode(entry.otherNode, user)) return null;
            return this.#view(entry);
        }
        const useMarkerFallback = allowMarkerFallback === null ? this.isAutomaticTrackingEnabled() : allowMarkerFallback === true;
        return useMarkerFallback ? this.#markerFallback(shipTarget) : null;
    }

    static async setCurrentLocation(shipTarget, placeTarget, {
        source = "manual",
        markerId = "",
        sceneId = "",
        edgeId = "",
        preserveExistingViews = false
    } = {}) {
        if (!game.user?.isGM) return null;
        const endpoints = this.#endpoints(shipTarget, placeTarget);
        if (!endpoints) throw new Error("Current location requires a Ship and a Nexus Place.");
        const selected = edgeId ? ConnectionStore.getConnectionEdge(edgeId) : this.#findEdge(endpoints.shipNode.id, endpoints.placeNode.id);
        const shipLocation = ShipLocationModel.create(endpoints.shipNode, endpoints.placeNode, {
            source,
            markerId,
            sceneId,
            managedConnection: selected?.shipLocation?.managedConnection === true || !selected,
            previousViews: selected?.shipLocation?.previousViews || (selected ? selected.views : null)
        });
        const views = ShipLocationModel.createConnectionViews(endpoints.shipNode, endpoints.placeNode, {
            existingViews: selected?.views || null,
            preserveExistingViews: preserveExistingViews && !!selected
        });

        return ConnectionStore.runBatch(async () => {
            for (const current of this.#entries(endpoints.shipNode.id)) {
                if (current.edge.id !== selected?.id) {
                    await this.#clearEntry(current);
                }
            }
            if (selected) return ConnectionStore.updateConnection(selected.id, { relationType: "ship-location", shipLocation, views });
            return ConnectionStore.addConnection(endpoints.shipNode, endpoints.placeNode, {
                relationType: "ship-location",
                shipLocation,
                sourceView: views[endpoints.shipNode.id],
                targetView: views[endpoints.placeNode.id]
            });
        }, { reason: "setShipLocation" });
    }

    static async clearCurrentLocation(shipTarget, { markerId = "", markerOnly = false } = {}) {
        if (!game.user?.isGM) return false;
        const current = this.getCurrentLocation(shipTarget, { includeHidden: true, allowMarkerFallback: false });
        if (!current) return false;
        if (markerOnly && current.source !== "marker") return false;
        if (markerId && current.markerId && current.markerId !== markerId) return false;
        await this.#clearEntry({ edge: current.edge, location: current.edge.shipLocation });
        return true;
    }

    static async adoptExistingMarkerLocation(shipTarget) {
        if (!game.user?.isGM || !this.isAutomaticTrackingEnabled()) return null;
        const current = this.getCurrentLocation(shipTarget, { includeHidden: true, allowMarkerFallback: false });
        if (current) return current;
        const fallback = this.#markerFallback(shipTarget);
        if (!fallback?.placeNodeId) return null;
        const placeTarget = ConnectionStore.getNode(fallback.placeNodeId)
            || ConnectionTargetResolver.fromSceneReference({ sceneId: fallback.sceneId });
        if (!placeTarget) return null;
        return this.setCurrentLocation(shipTarget, placeTarget, {
            source: "marker",
            markerId: fallback.markerId,
            sceneId: fallback.sceneId,
            preserveExistingViews: true
        });
    }

    static async removeLegacyMarkerSceneConnection(shipTarget, placeTarget) {
        if (!game.user?.isGM) return false;
        const shipNodeId = ConnectionTargetResolver.getNodeId(shipTarget);
        const placeNodeId = ConnectionTargetResolver.getNodeId(placeTarget);
        const edge = this.#findEdge(shipNodeId, placeNodeId);
        if (!edge || edge.shipLocation || edge.relationType) return false;
        const shipView = ConnectionStore.getConnectionView(edge, shipNodeId);
        const placeView = ConnectionStore.getConnectionView(edge, placeNodeId);
        const markerShaped = shipView.category === "place"
            && shipView.role === "Located At"
            && placeView.category === "ship"
            && placeView.role === "Present";
        if (!markerShaped || edge.note || edge.resourceFlows?.length) return false;
        return ConnectionStore.removeConnection(edge.id);
    }

    static isAutomaticTrackingEnabled() {
        try {
            return game.settings.get("augur-nexus", "trackShipLocationFromMarkers") !== false;
        } catch (_error) {
            return true;
        }
    }

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

    static #view(entry) {
        const display = ConnectionTargetResolver.resolveDisplayNodeSync(entry.otherNode) || entry.otherNode;
        return {
            edgeId: entry.edge.id,
            edge: entry.edge,
            shipNodeId: entry.location.shipNodeId,
            placeNodeId: entry.location.placeNodeId,
            source: entry.location.source || "manual",
            markerId: entry.location.markerId || "",
            sceneId: entry.location.sceneId || "",
            managedConnection: entry.location.managedConnection === true,
            place: {
                nodeId: entry.otherNode.id,
                kind: entry.otherNode.kind,
                entityId: entry.otherNode.entityId || "",
                name: display.name || "Unknown Place",
                imageSrc: display.img || "",
                subtitle: display.subtitle || (entry.location.source === "marker" ? "Tracked by marker" : "Recorded location")
            }
        };
    }

    static #markerFallback(shipTarget) {
        const entityId = String(shipTarget?.entityId || shipTarget?.id || "").trim();
        const entity = shipTarget?.type === "ship" ? shipTarget : CampaignEntityJournalStore.getShip(entityId);
        const refs = Array.isArray(entity?.markerRefs) ? entity.markerRefs : [];
        const ref = refs.find(candidate => candidate?.sceneId && candidate?.markerId) || null;
        const scene = ref?.sceneId ? game.scenes.get(ref.sceneId) : null;
        if (!ref || !scene) return null;
        const placeTarget = ConnectionTargetResolver.fromSceneReference({ sceneId: scene.id });
        const display = ConnectionTargetResolver.resolveDisplayNodeSync(placeTarget) || placeTarget;
        return {
            edgeId: "",
            edge: null,
            shipNodeId: ConnectionTargetResolver.getNodeId(shipTarget),
            placeNodeId: placeTarget.id,
            source: "marker",
            markerId: ref.markerId,
            sceneId: scene.id,
            adopted: true,
            place: {
                nodeId: placeTarget.id,
                kind: placeTarget.kind,
                entityId: "",
                name: display.name || scene.name || "Scene",
                imageSrc: display.img || "",
                subtitle: "Tracked by existing marker"
            }
        };
    }

    static #endpoints(left, right) {
        return ShipLocationModel.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 async #clearEntry(entry) {
        if (!entry?.edge?.id) return false;
        if (entry.location?.managedConnection === true) return ConnectionStore.removeConnection(entry.edge.id);
        return ConnectionStore.updateConnection(entry.edge.id, {
            shipLocation: null,
            relationType: entry.edge.relationType === "ship-location" ? "" : entry.edge.relationType,
            views: entry.location?.previousViews || entry.edge.views
        });
    }
}
