import { PopulationProviderRegistry } from "../services/PopulationProviderRegistry.js";
import { RegionProviderRegistry } from "../services/RegionProviderRegistry.js";
import { RegionPopulationClearService } from "../services/RegionPopulationClearService.js";
import { RegionPopulationProgressiveService } from "../services/RegionPopulationProgressiveService.js";
import { RegionPopulationRunStore } from "../services/RegionPopulationRunStore.js";
import { RegionChronicleService } from "../services/RegionChronicleService.js";
import { RegionChronicleViewModel } from "../services/RegionChronicleViewModel.js";
import { RegionPopulationSurveyService } from "../services/RegionPopulationSurveyService.js";
import { CampaignEntityJournalStore } from "../../campaign-entities/services/CampaignEntityJournalStore.js";
import { FactionEntityModel } from "../../campaign-entities/models/FactionEntityModel.js";
import { openFactionCreator } from "../../../api/factions.js";
import { ConnectionDropResolver } from "../../connections/services/ConnectionDropResolver.js";

const { ApplicationV2, HandlebarsApplicationMixin, DialogV2 } = foundry.applications.api;

export class ProgressiveRegionPopulationApplication extends HandlebarsApplicationMixin(ApplicationV2) {
    #sceneId;
    #settings = {
        years: 50,
        rootCount: 3,
        development: "settled",
        tradeNetwork: "connected",
        socialFabric: "natural",
        seed: "",
        regionalPowerEntityIds: []
    };
    #settingsHydrated = false;
    #phase = "configure";
    #busy = false;
    #progress = null;
    #events = [];
    #counts = {};
    #error = "";
    #abortController = null;
    #closed = false;
    #powerPlan = null;
    #powerPlanSignature = "";
    #survey = null;
    #runStartedAt = 0;
    #elapsedMs = 0;
    #elapsedTimer = null;
    #progressRenderTimer = null;

    static DEFAULT_OPTIONS = {
        id: "augur-region-population",
        tag: "div",
        classes: ["augur-nexus", "augur-theme-fantasy", "augur-region-population"],
        window: {
            title: "Develop Fantasy Region",
            icon: "fas fa-timeline",
            resizable: true
        },
        position: { width: 440, height: 820, left: 24, top: 70 },
        actions: {
            generate: ProgressiveRegionPopulationApplication._onGenerate,
            cancel: ProgressiveRegionPopulationApplication._onCancel,
            clear: ProgressiveRegionPopulationApplication._onClear,
            keep: ProgressiveRegionPopulationApplication._onKeep,
            openChronicle: ProgressiveRegionPopulationApplication._onOpenChronicle,
            newSeed: ProgressiveRegionPopulationApplication._onNewSeed,
            editRegionalPower: ProgressiveRegionPopulationApplication._onEditRegionalPower,
            createRegionalPower: ProgressiveRegionPopulationApplication._onCreateRegionalPower,
            chooseRegionalPower: ProgressiveRegionPopulationApplication._onChooseRegionalPower,
            removeRegionalPower: ProgressiveRegionPopulationApplication._onRemoveRegionalPower
        }
    };

    static PARTS = {
        content: {
            template: "modules/augur-nexus/templates/region-population/region-population.hbs"
        }
    };

    constructor({ sceneId = canvas.scene?.id } = {}, options = {}) {
        super(options);
        this.#sceneId = String(sceneId || "");
        this.#settings.seed = foundry.utils.randomID();
    }

    get scene() {
        return game.scenes.get(this.#sceneId) || null;
    }

    prepareToOpen() {
        this.#closed = false;
        return this;
    }

    async _prepareContext(options) {
        const scene = this.scene;
        const regionProvider = RegionProviderRegistry.findForScene(scene);
        const populationProviders = PopulationProviderRegistry.listForScene(scene, regionProvider);
        const populationProvider = populationProviders.find(provider => typeof provider.runDevelopment === "function") || null;
        const run = RegionPopulationRunStore.get(scene);
        if (!this.#settingsHydrated) {
            if (run?.settings) {
                this.#settings = this.#normalizeSettings({
                    ...run.settings,
                    seed: run.settings.seed || run.seed
                });
            }
            this.#settingsHydrated = true;
        }
        let survey = this.#busy ? this.#survey : null;
        if (!survey && regionProvider && scene) {
            try {
                survey = RegionPopulationSurveyService.build(scene, await regionProvider.getSnapshot(scene), {
                    includedFactionIds: this.#settings.regionalPowerEntityIds
                });
                this.#survey = survey;
            } catch (error) {
                survey = { warnings: [], errors: [error?.message || "The current region could not be surveyed."] };
            }
        }
        const includedPowerCount = Number(survey?.factions?.length || 0);
        if (!this.#busy && includedPowerCount > this.#settings.rootCount) {
            this.#settings.rootCount = Math.min(6, includedPowerCount);
        }
        if (!this.#busy && this.#phase === "configure" && populationProvider?.prepareDevelopment && survey && !survey.errors?.length) {
            await this.#preparePowerPlan(populationProvider, survey);
        }
        if (!this.#busy && this.#phase === "configure" && ["generating", "incomplete"].includes(run?.status)) {
            if (run.status === "generating") {
                await RegionPopulationRunStore.markStatus(scene, "incomplete", {
                    failureMessage: "Generation was interrupted before completion."
                });
            }
            this.#phase = "incomplete";
            this.#progress ||= {
                pct: 0,
                year: 0,
                years: Number(run.settings?.years || this.#settings.years),
                message: "Incomplete regional development"
            };
        }
        return {
            ...(super._prepareContext ? await super._prepareContext(options) : {}),
            available: !!regionProvider && !!populationProvider,
            sceneName: scene?.name || "Unnamed Region",
            settings: this.#settings,
            minimumPowerCount: Math.max(1, Math.min(6, includedPowerCount)),
            canAddRegionalPower: includedPowerCount < 6,
            yearValueLabel: this.#settings.years === 50 ? "50 years · Recommended" : `${this.#settings.years} years`,
            powerValueLabel: this.#settings.rootCount === 3 ? "3 powers · Recommended" : `${this.#settings.rootCount} ${this.#settings.rootCount === 1 ? "power" : "powers"}`,
            developmentIndex: { sparse: 0, settled: 1, dense: 2 }[this.#settings.development] ?? 1,
            developmentLabel: { sparse: "Sparse", settled: "Settled", dense: "Dense" }[this.#settings.development] || "Settled",
            tradeNetworkIndex: { isolated: 0, connected: 1, interwoven: 2 }[this.#settings.tradeNetwork] ?? 1,
            tradeNetworkLabel: { isolated: "Isolated", connected: "Connected", interwoven: "Interwoven" }[this.#settings.tradeNetwork] || "Connected",
            socialFabricIndex: { sparse: 0, natural: 1, interconnected: 2 }[this.#settings.socialFabric] ?? 1,
            socialFabricLabel: { sparse: "Loose", natural: "Familiar", interconnected: "Entangled" }[this.#settings.socialFabric] || "Familiar",
            phase: this.#phase,
            configuring: this.#phase === "configure",
            running: this.#phase === "running",
            finished: ["complete", "incomplete"].includes(this.#phase),
            incomplete: this.#phase === "incomplete",
            busy: this.#busy,
            progress: this.#progress,
            events: [...this.#events].reverse().map(event => RegionChronicleViewModel.prepareEvent(event)),
            counts: this.#counts,
            countItems: (populationProvider?.developmentCounters || []).map(counter => ({
                key: String(counter.key || ""),
                label: String(counter.label || counter.key || ""),
                value: Number(this.#counts?.[counter.key] || 0)
            })).filter(counter => counter.key),
            run,
            survey,
            surveyBlocked: !!survey?.errors?.length,
            hasExistingMaterial: !!survey && (survey.locations?.length || survey.factions?.length || survey.npcs?.length || survey.clock?.developedThroughYear),
            developedThroughYear: Number(survey?.clock?.developedThroughYear || 0),
            surveyCounts: survey ? {
                locations: Number(survey.locations?.length || 0),
                factions: Number(survey.factions?.length || 0),
                npcs: Number(survey.npcs?.length || 0)
            } : null,
            powerPlan: this.#powerPlan,
            elapsedLabel: this.#formatElapsed(this.#elapsedMs || Number(run?.elapsedMs || 0)),
            error: this.#error
        };
    }

    static async _onGenerate() {
        if (this.#busy) return;
        this.#readSettings();
        const scene = this.scene;
        const regionProvider = RegionProviderRegistry.findForScene(scene);
        const populationProvider = PopulationProviderRegistry.listForScene(scene, regionProvider)
            .find(provider => typeof provider.runDevelopment === "function") || null;
        if (!scene || !regionProvider || !populationProvider) return;

        const survey = RegionPopulationSurveyService.build(scene, await regionProvider.getSnapshot(scene), {
            includedFactionIds: this.#settings.regionalPowerEntityIds
        });
        this.#survey = survey;
        if (survey.errors?.length) {
            this.#error = survey.errors.join(" ");
            ui.notifications.error(this.#error);
            await this.render({ force: true });
            return;
        }
        await this.#preparePowerPlan(populationProvider, survey);
        const runSettings = {
            ...this.#settings,
            regionalPowerDrafts: (this.#powerPlan?.powers || [])
                .filter(power => power.mode === "planned")
                .map(power => foundry.utils.deepClone(power.candidate || {}))
        };

        this.#busy = true;
        this.#phase = "running";
        this.#error = "";
        this.#events = [];
        this.#counts = {};
        const currentYear = Number(survey.clock?.developedThroughYear || 0);
        this.#progress = {
            pct: 0,
            year: currentYear,
            years: currentYear + this.#settings.years,
            message: "Preparing the region..."
        };
        this.#startElapsedTimer();
        this.#abortController = new AbortController();
        await this.render({ force: true });

        try {
            await RegionPopulationProgressiveService.run({
                scene,
                regionProvider,
                populationProvider,
                settings: runSettings,
                signal: this.#abortController.signal,
                onProgress: update => {
                    this.#progress = {
                        pct: Number(update.pct || 0),
                        year: Number(update.year || 0),
                        years: Number(update.years || this.#settings.years),
                        message: update.message || "Developing the region..."
                    };
                    if (update.counts) this.#counts = foundry.utils.deepClone(update.counts);
                    this.#queueProgressRender();
                },
                onEvent: (event, counts) => {
                    this.#events.push(event);
                    if (counts) this.#counts = foundry.utils.deepClone(counts);
                    this.#queueProgressRender();
                }
            });
            this.#phase = "complete";
            ui.notifications.info("Fantasy regional development complete.");
        } catch (error) {
            const cancelled = error?.name === "AbortError";
            this.#phase = "incomplete";
            this.#error = cancelled ? "" : (error?.message || "Regional development stopped unexpectedly.");
            if (cancelled) ui.notifications.warn("Regional development stopped. The partial population can be kept or cleared.");
            else {
                console.error("Augur: Nexus | Progressive region population failed.", error);
                ui.notifications.error(this.#error);
            }
        } finally {
            this.#stopElapsedTimer();
            const recordedElapsed = Number(RegionPopulationRunStore.get(scene)?.elapsedMs || 0);
            if (recordedElapsed > 0) this.#elapsedMs = recordedElapsed;
            this.#busy = false;
            this.#abortController = null;
            clearTimeout(this.#progressRenderTimer);
            this.#progressRenderTimer = null;
            if (!this.#closed) await this.render({ force: true });
        }
    }

    static _onCancel() {
        this.#abortController?.abort();
    }

    static async _onClear() {
        if (this.#busy) return;
        this.#busy = true;
        await this.render({ force: true });
        try {
            const cleared = await RegionPopulationClearService.clear(this.scene, { purpose: "clear" });
            if (cleared) {
                this.#phase = "configure";
                this.#events = [];
                this.#progress = null;
                this.#elapsedMs = 0;
                this.#error = "";
                this.#counts = {};
                ui.notifications.info("Generated regional development cleared.");
            }
        } catch (error) {
            console.error("Augur: Nexus | Failed to clear regional development.", error);
            this.#error = error?.message || "The generated population could not be cleared.";
            ui.notifications.error(this.#error);
        } finally {
            this.#busy = false;
            await this.render({ force: true });
        }
    }

    static async _onOpenChronicle() {
        const opened = await RegionChronicleService.open(this.scene, RegionPopulationRunStore.get(this.scene));
        if (!opened) ui.notifications.warn("The regional chronicle could not be found.");
    }

    static async _onKeep() {
        if (this.#busy) return;
        await RegionPopulationRunStore.markStatus(this.scene, "partial");
        this.#phase = "configure";
        this.#error = "";
        await this.render({ force: true });
    }

    _onClose(options) {
        this.#closed = true;
        this.#stopElapsedTimer();
        clearTimeout(this.#progressRenderTimer);
        this.#progressRenderTimer = null;
        this.#abortController?.abort();
        return super._onClose(options);
    }

    #queueProgressRender() {
        if (this.#closed || this.#progressRenderTimer) return;
        this.#progressRenderTimer = setTimeout(() => {
            this.#progressRenderTimer = null;
            if (!this.#closed) void this.render({ force: true });
        }, 50);
    }

    static async _onNewSeed() {
        if (this.#busy) return;
        this.#readSettings();
        this.#settings.seed = foundry.utils.randomID();
        this.#powerPlan = null;
        this.#powerPlanSignature = "";
        await this.render({ force: true });
        ui.notifications.info("A new regional generation seed was rolled.");
    }

    static async _onEditRegionalPower(_event, target) {
        if (this.#busy) return;
        const id = String(target?.dataset?.powerId || "");
        const draft = this.#powerPlan?.powers?.find(power => power.id === id) || null;
        if (!draft) return;
        const scene = this.scene;
        const regionProvider = RegionProviderRegistry.findForScene(scene);
        const populationProvider = PopulationProviderRegistry.listForScene(scene, regionProvider)
            .find(provider => typeof provider.runDevelopment === "function") || null;
        if (typeof populationProvider?.editRegionalPower !== "function") return;
        await populationProvider.editRegionalPower({
            draft: foundry.utils.deepClone(draft),
            onSave: updated => {
                const index = this.#powerPlan?.powers?.findIndex(power => power.id === id) ?? -1;
                if (index < 0) return;
                this.#powerPlan.powers[index] = foundry.utils.deepClone(updated);
                void this.render({ force: true });
            }
        });
    }

    static _onCreateRegionalPower() {
        if (this.#busy || this.#includedPowerCount() >= 6) return;
        void openFactionCreator({
            openDossier: true,
            onCreated: faction => this.#addRegionalPower(faction?.id)
        });
    }

    static async _onChooseRegionalPower() {
        if (this.#busy || this.#includedPowerCount() >= 6) return;
        const included = new Set((this.#survey?.factions || []).map(row => String(row.entityId || row.entity?.id || "")));
        const factions = CampaignEntityJournalStore.getFactions()
            .filter(faction => !included.has(faction.id))
            .sort((left, right) => FactionEntityModel.getName(left).localeCompare(FactionEntityModel.getName(right)));
        if (!factions.length) return ui.notifications.info("Every available Faction is already part of this region.");
        const options = factions.map(faction => {
            const name = foundry.utils.escapeHTML(FactionEntityModel.getName(faction));
            const type = foundry.utils.escapeHTML(String(faction.profile?.type || "Organization"));
            return `<option value="${foundry.utils.escapeHTML(faction.id)}">${name} — ${type}</option>`;
        }).join("");
        const factionId = await DialogV2.prompt({
            window: { title: "Choose Existing Faction" },
            content: `<label>Faction <select name="factionId" autofocus>${options}</select></label><p>The faction will be preserved and developed as one of this region's powers.</p>`,
            rejectClose: false,
            ok: {
                label: "Add Faction",
                callback: (_event, button) => String(button.form.elements.factionId?.value || "")
            }
        });
        if (factionId) await this.#addRegionalPower(factionId);
    }

    static async _onRemoveRegionalPower(_event, target) {
        if (this.#busy) return;
        const factionId = String(target?.dataset?.factionId || "");
        if (!factionId) return;
        this.#settings.regionalPowerEntityIds = this.#settings.regionalPowerEntityIds
            .filter(id => id !== factionId);
        this.#survey = null;
        this.#powerPlan = null;
        this.#powerPlanSignature = "";
        await this.render({ force: true });
    }

    _onRender(context, options) {
        super._onRender(context, options);
        this.#activateRegionalPowerDrop();
        for (const slider of this.element?.querySelectorAll("[data-region-slider]") || []) {
            const update = () => this.#updateSliderPresentation(slider);
            slider.addEventListener("input", update);
            if (slider.name === "rootCount") {
                slider.addEventListener("change", () => {
                    this.#readSettings();
                    this.#powerPlan = null;
                    this.#powerPlanSignature = "";
                    void this.render({ force: true });
                });
            }
            update();
        }
        this.element?.querySelector('input[name="seed"]')?.addEventListener("change", () => {
            this.#readSettings();
            this.#powerPlan = null;
            this.#powerPlanSignature = "";
            void this.render({ force: true });
        });
    }

    #activateRegionalPowerDrop() {
        const zone = this.element?.querySelector?.("[data-regional-power-drop]");
        if (!zone || this.#busy) return;
        zone.addEventListener("dragover", event => {
            event.preventDefault();
            if (event.dataTransfer) event.dataTransfer.dropEffect = "copy";
            zone.classList.add("is-drop-target");
        });
        zone.addEventListener("dragleave", event => {
            if (zone.contains(event.relatedTarget)) return;
            zone.classList.remove("is-drop-target");
        });
        zone.addEventListener("drop", async event => {
            event.preventDefault();
            event.stopPropagation();
            zone.classList.remove("is-drop-target");
            const target = await ConnectionDropResolver.fromEvent(event);
            if (target?.kind !== "nexus-entity" || target.entityType !== "faction") {
                ui.notifications.warn("Drop a Nexus Faction here.");
                return;
            }
            await this.#addRegionalPower(target.entityId);
        });
    }

    async #addRegionalPower(factionId) {
        const id = String(factionId || "").trim();
        const faction = CampaignEntityJournalStore.getFaction(id);
        if (!faction) return ui.notifications.warn("That Faction could not be found.");
        const included = new Set((this.#survey?.factions || []).map(row => String(row.entityId || row.entity?.id || "")));
        if (included.has(id)) return ui.notifications.info(`${FactionEntityModel.getName(faction)} is already part of this region.`);
        if (included.size >= 6) return ui.notifications.warn("A region can currently contain at most six powers.");
        this.#settings.regionalPowerEntityIds = [...new Set([...this.#settings.regionalPowerEntityIds, id])];
        this.#settings.rootCount = Math.max(this.#settings.rootCount, included.size + 1);
        this.#survey = null;
        this.#powerPlan = null;
        this.#powerPlanSignature = "";
        await this.render({ force: true });
    }

    #includedPowerCount() {
        return Number(this.#survey?.factions?.length || 0);
    }

    #readSettings() {
        const form = this.element?.querySelector("form");
        if (!form) return;
        const data = new FormData(form);
        this.#settings = this.#normalizeSettings({
            years: data.get("years"),
            rootCount: data.get("rootCount"),
            development: ["sparse", "settled", "dense"][this.#number(data.get("developmentLevel"), 0, 2, 1)],
            tradeNetwork: ["isolated", "connected", "interwoven"][this.#number(data.get("tradeNetworkLevel"), 0, 2, 1)],
            socialFabric: ["sparse", "natural", "interconnected"][this.#number(data.get("socialFabricLevel"), 0, 2, 1)],
            seed: data.get("seed"),
            regionalPowerEntityIds: this.#settings.regionalPowerEntityIds
        });
    }

    #normalizeSettings(settings = {}) {
        const development = ["sparse", "settled", "dense"].includes(settings.development)
            ? settings.development
            : ({ slow: "sparse", normal: "settled", fast: "dense" }[settings.developmentPace] || "settled");
        const storedConnectivity = Number(settings.connectivity);
        const tradeNetwork = ["isolated", "connected", "interwoven"].includes(settings.tradeNetwork)
            ? settings.tradeNetwork
            : (Number.isFinite(storedConnectivity)
                ? (storedConnectivity <= 60 ? "isolated" : storedConnectivity >= 95 ? "interwoven" : "connected")
                : "connected");
        const socialFabric = ["sparse", "natural", "interconnected"].includes(settings.socialFabric)
            ? settings.socialFabric
            : "natural";
        const years = Math.round(this.#number(settings.years, 20, 100, 50) / 10) * 10;
        return {
            years,
            rootCount: this.#number(settings.rootCount, 1, 6, 3),
            development,
            tradeNetwork,
            socialFabric,
            seed: String(settings.seed || foundry.utils.randomID()),
            regionalPowerEntityIds: [...new Set(
                (Array.isArray(settings.regionalPowerEntityIds) ? settings.regionalPowerEntityIds : [])
                    .map(String)
                    .filter(Boolean)
            )]
        };
    }

    #updateSliderPresentation(slider) {
        const name = String(slider.name || "");
        const value = Number(slider.value || 0);
        const output = this.element?.querySelector(`[data-slider-output="${name}"]`);
        const labels = {
            years: value === 50 ? "50 years · Recommended" : `${value} years`,
            rootCount: value === 3 ? "3 powers · Recommended" : `${value} ${value === 1 ? "power" : "powers"}`,
            developmentLevel: ["Sparse", "Settled", "Dense"][value] || "Settled",
            tradeNetworkLevel: ["Isolated", "Connected", "Interwoven"][value] || "Connected",
            socialFabricLevel: ["Loose", "Familiar", "Entangled"][value] || "Familiar"
        };
        if (output) output.textContent = labels[name] || String(value);
        const ticks = this.element?.querySelector(`[data-slider-ticks="${name}"]`);
        for (const tick of ticks?.querySelectorAll("[data-value]") || []) {
            tick.classList.toggle("is-selected", Number(tick.dataset.value) === value);
        }
    }

    #number(value, min, max, fallback) {
        const parsed = Number.parseInt(value, 10);
        return Number.isFinite(parsed) ? Math.max(min, Math.min(max, parsed)) : fallback;
    }

    #startElapsedTimer() {
        this.#stopElapsedTimer();
        this.#elapsedMs = 0;
        this.#runStartedAt = Date.now();
        this.#elapsedTimer = window.setInterval(() => {
            this.#elapsedMs = Math.max(0, Date.now() - this.#runStartedAt);
            const output = this.element?.querySelector("[data-region-elapsed]");
            if (output) output.textContent = this.#formatElapsed(this.#elapsedMs);
        }, 1000);
    }

    #stopElapsedTimer() {
        if (this.#runStartedAt) this.#elapsedMs = Math.max(this.#elapsedMs, Date.now() - this.#runStartedAt);
        if (this.#elapsedTimer !== null) window.clearInterval(this.#elapsedTimer);
        this.#elapsedTimer = null;
        this.#runStartedAt = 0;
    }

    #formatElapsed(milliseconds = 0) {
        const totalSeconds = Math.max(0, Math.floor(Number(milliseconds || 0) / 1000));
        const seconds = String(totalSeconds % 60).padStart(2, "0");
        const totalMinutes = Math.floor(totalSeconds / 60);
        if (totalMinutes < 60) return `${String(totalMinutes).padStart(2, "0")}:${seconds}`;
        const hours = Math.floor(totalMinutes / 60);
        const minutes = String(totalMinutes % 60).padStart(2, "0");
        return `${hours}:${minutes}:${seconds}`;
    }

    async #preparePowerPlan(populationProvider, survey) {
        if (typeof populationProvider?.prepareDevelopment !== "function" || !survey) return null;
        const existingFactionIds = (survey.factions || [])
            .map(row => String(row.entityId || row.entity?.id || ""))
            .filter(Boolean)
            .sort()
            .join("|");
        const signature = `${survey.sceneId || ""}:${existingFactionIds}:${this.#settings.seed}:${this.#settings.rootCount}`;
        if (this.#powerPlan && this.#powerPlanSignature === signature) return this.#powerPlan;
        this.#powerPlan = await populationProvider.prepareDevelopment({
            scene: this.scene,
            survey: foundry.utils.deepClone(survey),
            settings: foundry.utils.deepClone(this.#settings)
        });
        this.#powerPlanSignature = signature;
        return this.#powerPlan;
    }
}
