import { NexusDeletionConfirmation } from "../../deletion/applications/NexusDeletionConfirmation.js";
import { NexusDeletionProgressApplication } from "../../deletion/applications/NexusDeletionProgressApplication.js";
import { NexusDeletionExecutor } from "../../deletion/services/NexusDeletionExecutor.js";
import { NexusDeletionPlanBuilder } from "../../deletion/services/NexusDeletionPlanBuilder.js";
import { NexusSceneOperations } from "./NexusSceneOperations.js";

const MODULE_ID = "augur-nexus";

const participants = new Map();

function normalizeParticipantImpacts(result) {
    if (!result) return [];
    if (Array.isArray(result)) return result;
    if (Array.isArray(result.impacts)) return result.impacts;

    return Object.entries(result)
        .filter(([, count]) => Number(count || 0) > 0)
        .map(([label, count]) => ({ label, count }));
}

function getMatchingParticipants(context) {
    return [...participants.values()].filter(participant => {
        if (typeof participant?.matches !== "function") return false;
        try {
            return participant.matches(context) === true;
        } catch (err) {
            console.warn("Augur Nexus | Scene delete participant match failed.", err);
            return false;
        }
    });
}

function getParticipantContexts(plan, { intent = "delete-branch", source = "nexus" } = {}) {
    const contexts = (plan?.sceneRootContexts || []).map(context => ({
        rootScene: context.rootScene,
        branch: context.branch,
        intent,
        source,
        plan
    }));
    if (contexts.length) return contexts;
    return plan?.rootScene ? [{
        rootScene: plan.rootScene,
        branch: (plan.scenes || []).map(row => row.scene),
        intent,
        source,
        plan
    }] : [];
}

function buildOperation(rootScene, operation = {}) {
    return foundry.utils.mergeObject({
        [MODULE_ID]: {
            nexusDeleteHandled: true,
            nexusDeleteRootSceneId: rootScene?.id || null
        }
    }, operation || {}, { inplace: false });
}

export class NexusSceneDeletionCoordinator {
    static registerParticipant(moduleId, participant) {
        if (!moduleId || !participant) return false;
        participants.set(moduleId, { ...participant, moduleId });
        return true;
    }

    static unregisterParticipant(moduleId) {
        return participants.delete(moduleId);
    }

    static getPlanParticipantImpacts(plan, options = {}) {
        if (!plan) return [];
        const contexts = getParticipantContexts(plan, {
            intent: options.intent || "delete-branch",
            source: options.source || "nexus"
        });
        const participantImpacts = [];

        for (const context of contexts) {
            for (const participant of getMatchingParticipants(context)) {
                if (typeof participant.getImpact !== "function") continue;
                try {
                    participantImpacts.push(...normalizeParticipantImpacts(participant.getImpact(context)));
                } catch (err) {
                    console.warn("Augur Nexus | Scene delete participant impact failed.", err);
                }
            }
        }
        plan.participantImpacts = participantImpacts;
        return participantImpacts;
    }

    static getSceneBranchDeleteImpact(rootScene, options = {}) {
        if (!rootScene) return null;

        const plan = options.plan || NexusDeletionPlanBuilder.buildForScene(rootScene, {
            branch: options.branch || null
        });
        const branch = (plan.scenes || []).map(row => row.scene);
        const baseImpact = NexusSceneOperations.getDeleteBranchImpact(rootScene, branch);
        const participantImpacts = this.getPlanParticipantImpacts(plan, {
            intent: options.intent || "delete-branch",
            source: options.source || "nexus"
        });

        return {
            ...baseImpact,
            branch,
            impacts: [
                { label: "scene", count: baseImpact.sceneCount },
                { label: "location", count: plan.locations?.length || 0 },
                { label: "shop", count: plan.shops?.length || 0 },
                { label: "person", count: plan.entityCandidates?.filter(row => row.entityType === "npc").length || 0 },
                { label: "ship", count: plan.entityCandidates?.filter(row => row.entityType === "ship").length || 0 },
                { label: "organization", count: plan.entityCandidates?.filter(row => row.entityType === "faction").length || 0 },
                { label: "site pin", count: baseImpact.noteCount },
                { label: "journal page", count: baseImpact.pageCount },
                { label: "site journal entry", count: baseImpact.journalEntryCount },
                { label: "regional chronicle", count: baseImpact.chronicleCount },
                ...participantImpacts
            ],
            participantImpacts,
            plan
        };
    }

    static async deleteSceneBranch(rootScene, options = {}) {
        if (!rootScene) return false;

        const impact = this.getSceneBranchDeleteImpact(rootScene, {
            ...options,
            plan: options.plan || null
        });
        const plan = impact?.plan;
        if (!plan) return false;
        const hasStructuralCascade = !!(plan.locations?.length || plan.shops?.length);
        const hasSelectableEntities = !!plan.entityCandidates?.length;
        const hasReviewedPlan = options.plan === plan;
        const hasReviewedSelection = hasReviewedPlan && Array.isArray(options.selectedEntityIds);
        const canBypassConfirmation = (
            (!hasStructuralCascade && !hasSelectableEntities)
            || (hasReviewedPlan && (!hasSelectableEntities || hasReviewedSelection))
        ) && (options.confirmed === true || options.confirm === false);
        const sceneCount = Number(impact.sceneCount || plan.scenes?.length || 0);
        const decision = canBypassConfirmation
            ? {
                selectedEntityIds: hasReviewedSelection
                    ? options.selectedEntityIds
                    : NexusDeletionConfirmation.getDefaultSelection(plan)
            }
            : await NexusDeletionConfirmation.confirm(plan, {
                title: options.title || "Delete Nexus Branch",
                message: options.message || `Delete <strong>${foundry.utils.escapeHTML(rootScene.name)}</strong> and everything below it in the Nexus tree?`,
                baseImpact: impact,
                participantImpacts: impact.participantImpacts,
                warning: `${sceneCount} ${sceneCount === 1 ? "scene" : "scenes"} will be permanently deleted.`,
                confirmLabel: options.confirmLabel || "Delete Branch"
            });

        if (!decision) return false;
        const executed = await this.executePlan(plan, {
            intent: options.intent || "delete-branch",
            source: options.source || "nexus",
            operation: options.operation,
            selectedEntityIds: decision.selectedEntityIds || []
        });
        return executed !== false;
    }

    static async executePlan(plan, {
        intent = "delete-branch",
        source = "nexus",
        operation = {},
        selectedEntityIds = [],
        progress = null
    } = {}) {
        if (!plan) return false;

        const freshPlan = NexusDeletionPlanBuilder.rebuild(plan);
        this.getPlanParticipantImpacts(freshPlan, { intent, source });
        const reviewedSignature = NexusDeletionPlanBuilder.getSignature(plan);
        const freshSignature = NexusDeletionPlanBuilder.getSignature(freshPlan);
        if (!freshPlan || reviewedSignature !== freshSignature) {
            ui.notifications?.warn?.(
                "The Nexus hierarchy or its linked entities changed after confirmation. Review the deletion again."
            );
            return false;
        }
        plan = freshPlan;

        const progressApplication = progress || new NexusDeletionProgressApplication();
        const ownsProgress = !progress;
        await progressApplication.open();

        try {
            for (const context of getParticipantContexts(plan, { intent, source })) {
                for (const participant of getMatchingParticipants(context)) {
                    if (typeof participant.beforeDelete !== "function") continue;
                    let impactCount = 0;
                    if (typeof participant.getImpact === "function") {
                        try {
                            impactCount = normalizeParticipantImpacts(participant.getImpact(context))
                                .reduce((sum, impact) => sum + Math.max(0, Number(impact.count || 0)), 0);
                        } catch (err) {
                            console.warn("Augur Nexus | Scene delete participant progress impact failed.", err);
                        }
                    }
                    const participantWork = Math.max(1, impactCount);
                    progressApplication.addTotal(participantWork);
                    progressApplication.setMessage(
                        "Deleting " + (participant.moduleId || "module") + " linked content..."
                    );
                    await participant.beforeDelete(context);
                    progressApplication.advance(participantWork);
                }
            }

            const operationRoot = plan.rootScene
                || plan.sceneRootContexts?.find(context => context.rootScene)?.rootScene
                || null;
            const result = await NexusDeletionExecutor.execute(plan, {
                operation: buildOperation(operationRoot, operation),
                deleteScenes: true,
                selectedEntityIds,
                onProgress: update => {
                    if (Number(update?.total || 0) > 0) progressApplication.addTotal(update.total);
                    if (update?.message) progressApplication.setMessage(update.message);
                    if (Number(update?.advance || 0) > 0) progressApplication.advance(update.advance);
                }
            });
            if (ownsProgress) await progressApplication.complete();
            return result;
        } catch (error) {
            if (ownsProgress) await progressApplication.closeProgress();
            throw error;
        }
    }
}
