import { ConnectionStore } from "../../connections/services/ConnectionStore.js";
import { ConnectionTargetResolver } from "../../connections/services/ConnectionTargetResolver.js";
import { CampaignEntityJournalStore } from "./CampaignEntityJournalStore.js";
import { LocationEntityStore } from "./LocationEntityStore.js";
import { LocationLeadershipService } from "../../connections/services/LocationLeadershipService.js";
import { ShopStore } from "../../shops/services/ShopStore.js";

const ROOT_TYPES = new Set(["location", "npc", "ship", "faction"]);
const MEMBER_TYPES = new Set([...ROOT_TYPES, "shop"]);

export class EntityPlacementCommitService {
    static async commit({ draft = {}, createMarker = null } = {}) {
        const type = String(draft.type || "").trim();
        if (!ROOT_TYPES.has(type) || !draft.candidate) throw new Error("The placement draft is invalid.");
        if (typeof createMarker !== "function") throw new Error("Placement requires a marker creation callback.");

        let root = null;
        let marker = null;
        const members = [];

        try {
            root = await this.#createEntity(type, draft.candidate);
            if (!root) throw new Error("The root entity could not be created.");

            marker = await createMarker(root);
            if (!marker) throw new Error("The root marker could not be created.");

            for (const definition of draft.composition?.members || []) {
                const entityType = String(definition?.entityType || "").trim();
                if (!MEMBER_TYPES.has(entityType) || !definition?.candidate) throw new Error("A placement companion definition is invalid.");
                const candidate = this.#prepareCandidate(root, entityType, definition.candidate, definition.attachment);
                const entity = await this.#createEntity(entityType, candidate);
                if (!entity) throw new Error(`A ${entityType} companion could not be created.`);
                members.push({ definition, entity });
                await this.#attach(root, entity, definition.attachment);
            }

            return { root, marker, members: members.map(entry => entry.entity) };
        } catch (error) {
            await this.#rollback({ type, root, members });
            throw error;
        }
    }

    static async #attach(root, member, attachment = {}) {
        if (attachment?.kind === "parent-location") {
            if (root?.type !== "location" || member?.hierarchy?.parentLocationId !== root.id) {
                throw new Error("A Shop companion could not be parented to the placed Location.");
            }
            return;
        }
        if (attachment?.kind === "location-leadership") {
            const locationTarget = ConnectionTargetResolver.fromCampaignEntity(root);
            const leaderTarget = ConnectionTargetResolver.fromCampaignEntity(member);
            if (!locationTarget || !leaderTarget) throw new Error("A Location authority target could not be resolved.");
            const edge = await LocationLeadershipService.setLeader(locationTarget, leaderTarget, {
                authorityType: attachment.authorityType,
                role: attachment.role
            });
            if (!edge) throw new Error("A Location authority Connection could not be created.");
            return;
        }
        if (attachment?.kind !== "connection") throw new Error(`Unsupported placement attachment: ${attachment?.kind || "missing"}`);
        const sourceTarget = ConnectionTargetResolver.fromCampaignEntity(root);
        const relatedTarget = ConnectionTargetResolver.fromCampaignEntity(member);
        if (!sourceTarget || !relatedTarget) throw new Error("A companion Connection target could not be resolved.");
        const edge = await ConnectionStore.addConnection(sourceTarget, relatedTarget, {
            sourceView: foundry.utils.deepClone(attachment.sourceView || {}),
            targetView: foundry.utils.deepClone(attachment.targetView || {})
        });
        if (!edge) throw new Error("A companion Connection could not be created.");
    }

    static #prepareCandidate(root, entityType, candidate, attachment = {}) {
        const next = foundry.utils.deepClone(candidate);
        if (entityType !== "shop") return next;
        if (attachment?.kind !== "parent-location" || root?.type !== "location") {
            throw new Error("Shop companions require a parent Location attachment.");
        }
        next.hierarchy = { ...(next.hierarchy || {}), parentLocationId: root.id };
        return next;
    }

    static async #rollback({ type = "", root = null, members = [] } = {}) {
        for (const entry of [...members].reverse()) {
            try {
                await this.#deleteEntity(entry.entity?.type || entry.definition?.entityType, entry.entity);
            } catch (error) {
                console.error("Augur: Nexus | Failed to roll back a placement companion.", error, entry.entity);
            }
        }
        if (!root) return;
        try {
            await this.#deleteEntity(type, root);
        } catch (error) {
            console.error("Augur: Nexus | Failed to roll back a placement root.", error, root);
        }
    }

    static #createEntity(type, candidate) {
        switch (type) {
            case "location": return LocationEntityStore.createLocation(candidate);
            case "npc": return CampaignEntityJournalStore.createNpc(candidate);
            case "ship": return CampaignEntityJournalStore.createShip(candidate);
            case "faction": return CampaignEntityJournalStore.createFaction(candidate);
            case "shop": return ShopStore.createShop(candidate);
            default: throw new Error(`Unsupported placement entity type: ${type}`);
        }
    }

    static #deleteEntity(type, entity) {
        const id = entity?.id || entity?.uuid || "";
        if (!id) return false;
        switch (type) {
            case "location": return LocationEntityStore.deleteLocation(id);
            case "npc": return CampaignEntityJournalStore.deleteNpc(id);
            case "ship": return CampaignEntityJournalStore.deleteShip(id);
            case "faction": return CampaignEntityJournalStore.deleteFaction(id);
            case "shop": return ShopStore.deleteShop(id);
            default: return false;
        }
    }
}
