import { getNpc, getNpcGeneratorProfileOptions, updateNpc } from "../../../api/npcs.js";
import { getIdeologyTopics } from "../../../api/ideologies.js";
import { promptTextInput } from "../../../api/ui.js";
import { NpcEntityModel } from "../models/NpcEntityModel.js";
import { NpcPortraitPicker } from "./NpcPortraitPicker.js";
import { NpcTraitPicker } from "./NpcTraitPicker.js";

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

const FIELD_DEFINITIONS = [
    { key: "descriptor", label: "Descriptor", path: "flavor.descriptor", section: "profile" },
    { key: "drive", label: "Wants", path: "flavor.drive", section: "thread" },
    { key: "action", label: "Approach", path: "flavor.action", section: "thread" },
    { key: "theme", label: "Tone", path: "flavor.theme", section: "thread" },
    { key: "focus", label: "Complication", path: "flavor.focus", section: "thread" }
];

export class NpcDataEditor extends HandlebarsApplicationMixin(ApplicationV2) {
    #npcId = "";
    #candidate = null;
    #onSave = null;
    #ideologyTopics = [];
    #positionInitialized = false;

    static DEFAULT_OPTIONS = {
        id: "augur-nexus-npc-data-editor",
        classes: ["augur-nexus", "augur-theme-fantasy", "npc-data-editor", "npc-person-data-editor"],
        tag: "div",
        window: { title: "Edit Person", resizable: true, minimizable: true },
        position: { width: 1320, height: 720 },
        actions: {
            save: NpcDataEditor._onSave,
            closeEditor: NpcDataEditor._onCloseEditor,
            toggleCombo: NpcDataEditor._onToggleCombo,
            selectComboOption: NpcDataEditor._onSelectComboOption,
            renameNpc: NpcDataEditor._onRenameNpc,
            changePortrait: NpcDataEditor._onChangePortrait,
            editTraits: NpcDataEditor._onEditTraits,
            addIdeologyTopic: NpcDataEditor._onAddIdeologyTopic,
            deleteIdeologyTopic: NpcDataEditor._onDeleteIdeologyTopic,
            addCustomField: NpcDataEditor._onAddCustomField,
            deleteCustomField: NpcDataEditor._onDeleteCustomField,
            toggleDefaultField: NpcDataEditor._onToggleDefaultField,
            moveCustomFieldUp: NpcDataEditor._onMoveCustomFieldUp,
            moveCustomFieldDown: NpcDataEditor._onMoveCustomFieldDown
        }
    };

    static PARTS = { content: { template: "modules/augur-nexus/templates/campaign-entities/npc-data-editor.hbs" } };

    static show({ npcId = "", candidate = null, onSave = null } = {}) {
        if (!game.user.isGM || (!npcId && !candidate)) return null;
        const app = new this({ npcId, candidate, onSave });
        app.render(true, { focus: true });
        return app;
    }

    constructor({ npcId = "", candidate = null, onSave = null } = {}, options = {}) {
        super(options);
        this.#npcId = npcId || "";
        this.#candidate = candidate ? foundry.utils.deepClone(candidate) : null;
        this.#onSave = typeof onSave === "function" ? onSave : null;
    }

    async _prepareContext() {
        const entity = this.#getEntity();
        const profileOptions = await getNpcGeneratorProfileOptions(entity?.generation?.generatorId || "");
        const roleOptions = this.#buildRoleOptions(profileOptions.roles || [], entity);
        const traits = NpcEntityModel.getTraits(entity);
        this.#ideologyTopics = getIdeologyTopics({ perspective: "personal" });
        const ideologyRows = NpcEntityModel.getIdeology(entity).map((selection, index) => this.#buildIdeologyRow(selection, index));
        const hiddenDefaultFields = this.#getHiddenDefaultFields(entity);
        const fields = FIELD_DEFINITIONS.map(definition => {
            const value = this.#getFieldValue(entity, definition.path);
            return {
                ...definition,
                value,
                hidden: hiddenDefaultFields.has(definition.key),
                options: this.#buildOptions(profileOptions.fields?.[definition.key] || [], value)
            };
        });


        return {
            missing: !entity,
            npc: entity ? {
                id: entity.id,
                name: NpcEntityModel.getName(entity),
                subtitle: NpcEntityModel.getSubtitle(entity),
                imageSrc: NpcEntityModel.getImage(entity)
            } : { name: "Missing Person" },
            role: {
                value: entity?.role?.roleLabel || "",
                options: roleOptions,
                hidden: hiddenDefaultFields.has("role")
            },
            traits,
            hasTraits: traits.length > 0,
            ideology: {
                rows: ideologyRows,
                hasRows: ideologyRows.length > 0,
                canAdd: ideologyRows.length < this.#ideologyTopics.length
            },
            profileFields: fields.filter(field => field.section === "profile"),
            currentThreadFields: fields.filter(field => field.section === "thread"),
            customFields: NpcEntityModel.getCustomFields(entity)
        };
    }

    _onRender(context, options) {
        super._onRender(context, options);
        if (!this.#positionInitialized) {
            this.setPosition(this.#getCenteredPosition());
            this.#positionInitialized = true;
        }
        this.element.querySelector("form")?.addEventListener("submit", event => {
            event.preventDefault();
            this.#saveForm();
        });
        this.element.addEventListener("focusin", event => {
            if (!event.target?.closest?.(".npc-data-combo")) this.#closeCombos();
        });
        this.element.addEventListener("click", event => {
            if (!event.target?.closest?.(".npc-data-combo")) this.#closeCombos();
        });
        for (const row of this.element.querySelectorAll("[data-ideology-row]")) this.#wireIdeologyRow(row);
        this.#refreshIdeologyControls();
    }

    static async _onSave() { return this.#saveForm(); }
    static async _onCloseEditor() { await this.close(); }

    static async _onRenameNpc() {
        const app = this;
        const entity = app.#getEntity();
        if (!entity) return;
        const nextName = await promptTextInput({ title: "Rename Person", label: "Name", value: NpcEntityModel.getName(entity), confirmLabel: "Rename" });
        if (!nextName) return;
        if (app.#candidate) {
            app.#candidate.display = { ...(app.#candidate.display || {}), name: nextName };
            if (app.#onSave) await app.#onSave(foundry.utils.deepClone(app.#candidate));
        } else {
            await updateNpc(entity.id, { display: { name: nextName } });
            if (app.#onSave) await app.#onSave(getNpc(app.#npcId));
        }
        app.render({ parts: ["content"] });
    }

    static async _onChangePortrait() {
        const app = this;
        const entity = app.#getEntity();
        if (!entity) return;
        new NpcPortraitPicker({ currentPortraitSrc: NpcEntityModel.getImage(entity), onSelect: selection => app.#setPortrait(selection?.src || "") }).render(true, { focus: true });
    }

    static async _onEditTraits() {
        const app = this;
        const entity = app.#getEntity();
        if (!entity) return;
        NpcTraitPicker.show({
            npcId: app.#candidate ? "" : entity.id,
            selectedTraitIds: entity.personality?.traitIds || [],
            onSave: traitIds => app.#candidate ? app.#setTraitIds(traitIds) : app.#refreshSavedEntity()
        });
    }

    static async _onAddIdeologyTopic() {
        const container = this.element.querySelector("[data-ideology-rows]");
        if (!container) return;
        const used = new Set([...container.querySelectorAll("[data-ideology-topic-select]")].map(select => select.value));
        const topic = this.#ideologyTopics.find(entry => !used.has(entry.id));
        if (!topic) return;
        const row = document.createElement("div");
        row.className = "faction-ideology-editor-row";
        row.dataset.ideologyRow = "";
        row.innerHTML = this.#renderIdeologyRow({ topicId: topic.id, tenetId: topic.tenets[0]?.id || "" });
        container.append(row);
        this.#wireIdeologyRow(row);
        this.#refreshIdeologyControls();
        row.querySelector("[data-ideology-topic-select]")?.focus();
    }

    static async _onDeleteIdeologyTopic(event, target) {
        target?.closest?.("[data-ideology-row]")?.remove();
        this.#refreshIdeologyControls();
    }

    static async _onToggleCombo(event, target) {
        const app = this;
        const key = target?.dataset?.comboKey || "";
        if (!key) return;
        const combo = app.element.querySelector(`[data-combo="${CSS.escape(key)}"]`);
        if (!combo) return;
        const wasOpen = combo.classList.contains("open");
        app.#closeCombos();
        if (!wasOpen) app.#positionComboMenu(combo);
        combo.classList.toggle("open", !wasOpen);
        combo.querySelector("input")?.focus();
    }

    static async _onSelectComboOption(event, target) {
        const app = this;
        const key = target?.dataset?.comboKey || "";
        const value = target?.dataset?.comboValue || "";
        const combo = key ? app.element.querySelector(`[data-combo="${CSS.escape(key)}"]`) : null;
        const input = combo?.querySelector("input");
        if (!input) return;
        input.value = value;
        const selectedIdInput = combo.querySelector("input[type='hidden']");
        if (selectedIdInput) selectedIdInput.value = target?.dataset?.comboId || "";
        app.#closeCombos();
        input.focus();
    }

    static async _onAddCustomField() {
        const app = this;
        const container = app.element.querySelector("[data-custom-fields]");
        if (!container) return;
        const id = `field.${foundry.utils.randomID()}`;
        const row = document.createElement("div");
        row.className = "npc-data-custom-field-row";
        row.dataset.customFieldRow = id;
        row.innerHTML = app.#renderCustomFieldRow({ id, label: "", value: "" });
        app.#wireCustomFieldRow(row);
        container.append(row);
        row.querySelector("[name='customField.label']")?.focus();
    }

    static async _onDeleteCustomField(event, target) { target?.closest?.("[data-custom-field-row]")?.remove(); }
    static async _onToggleDefaultField(event, target) { this.#toggleDefaultFieldButton(target); }
    static async _onMoveCustomFieldUp(event, target) { this.#moveCustomField(target, -1); }
    static async _onMoveCustomFieldDown(event, target) { this.#moveCustomField(target, 1); }

    async #saveForm() {
        const entity = this.#getEntity();
        const form = this.element.querySelector("form");
        if (!entity || !form) return;
        const ideology = this.#collectIdeology(form);
        if (!ideology) {
            ui.notifications.warn("Each ideology topic may only be selected once.");
            return;
        }
        const patch = { ...this.#buildPatch(form, entity), ideology };
        try {
            const updated = this.#candidate ? this.#applyCandidatePatch(entity, patch) : await updateNpc(entity.id, patch);
            ui.notifications.info(this.#candidate ? "Person candidate updated." : "Person updated.");
            if (this.#onSave) await this.#onSave(updated);
            await this.close();
        } catch (err) {
            console.error(err);
            ui.notifications.error(this.#candidate ? "Failed to update person candidate." : "Failed to update person.");
        }
    }

    #getEntity() { return this.#candidate || getNpc(this.#npcId); }

    #buildPatch(form, entity) {
        const formData = new FormData(form);
        const roleLabel = String(formData.get("role.roleLabel") || "").trim();
        const roleId = String(formData.get("role.roleId") || "").trim();
        const currentRoleLabel = String(entity.role?.roleLabel || "");
        return {
            role: {
                roleId: roleId || (roleLabel === currentRoleLabel ? entity.role?.roleId : this.#idFromLabel(roleLabel)),
                roleLabel
            },
            dossier: { hiddenDefaultFields: this.#collectHiddenDefaultFields(form) },
            flavor: {
                descriptor: String(formData.get("flavor.descriptor") || "").trim(),
                drive: String(formData.get("flavor.drive") || "").trim(),
                action: String(formData.get("flavor.action") || "").trim(),
                theme: String(formData.get("flavor.theme") || "").trim(),
                focus: String(formData.get("flavor.focus") || "").trim()
            },
            profile: { customFields: this.#collectCustomFields(form) }
        };
    }

    async #setPortrait(path) {
        const entity = this.#getEntity();
        if (!entity || !path) return;
        if (this.#candidate) {
            this.#candidate.display = { ...(this.#candidate.display || {}), imageSrc: path };
            if (this.#onSave) await this.#onSave(foundry.utils.deepClone(this.#candidate));
        } else {
            await updateNpc(entity.id, { display: { imageSrc: path } });
            if (this.#onSave) await this.#onSave(getNpc(this.#npcId));
        }
        this.render({ parts: ["content"] });
    }

    async #setTraitIds(traitIds = []) {
        const entity = this.#getEntity();
        if (!entity) return;
        if (this.#candidate) {
            this.#candidate.personality = { ...(this.#candidate.personality || {}), traitIds };
            if (this.#onSave) await this.#onSave(foundry.utils.deepClone(this.#candidate));
        } else {
            await updateNpc(entity.id, { personality: { traitIds } });
            if (this.#onSave) await this.#onSave(getNpc(this.#npcId));
        }
        this.render({ parts: ["content"] });
    }

    async #refreshSavedEntity() {
        if (this.#onSave) await this.#onSave(getNpc(this.#npcId));
        this.render({ parts: ["content"] });
    }

    #applyCandidatePatch(entity, patch) {
        this.#candidate = foundry.utils.mergeObject(foundry.utils.deepClone(entity), patch, { inplace: false, recursive: true });
        return foundry.utils.deepClone(this.#candidate);
    }

    #buildRoleOptions(options = [], entity = null) {
        const currentId = entity?.role?.roleId || "";
        const currentLabel = entity?.role?.roleLabel || "";
        const roles = [...options];
        if (currentLabel && !roles.some(role => role.id === currentId || role.label === currentLabel)) roles.push({ id: currentId || this.#idFromLabel(currentLabel), label: currentLabel });
        return roles.map(role => ({ ...role, value: role.label, selected: (currentId && role.id === currentId) || role.label === currentLabel }));
    }

    #buildOptions(options = [], currentValue = "") {
        const values = new Set((options || []).map(option => String(option?.value || option?.label || option || "").trim()).filter(Boolean));
        if (currentValue) values.add(currentValue);
        return [...values].map(value => ({ value, label: value, selected: value === currentValue }));
    }

    #getFieldValue(entity, path) {
        if (!entity || !path) return "";
        return String(path.split(".").reduce((value, key) => value?.[key], entity) || "");
    }

    #closeCombos() {
        for (const combo of this.element.querySelectorAll(".npc-data-combo.open")) combo.classList.remove("open", "drop-up");
    }

    #buildIdeologyRow(selection = {}, index = 0) {
        const topic = this.#ideologyTopics.find(entry => entry.id === selection.topicId) || this.#ideologyTopics[index] || this.#ideologyTopics[0];
        const tenet = topic?.tenets.find(entry => entry.id === selection.tenetId) || topic?.tenets[0];
        return {
            topicId: topic?.id || "",
            topicQuestion: topic?.question || "",
            topics: this.#ideologyTopics.map(entry => ({ id: entry.id, label: entry.label, selected: entry.id === topic?.id })),
            tenetId: tenet?.id || "",
            tenetDescription: tenet?.description || "",
            tenets: (topic?.tenets || []).map(entry => ({ id: entry.id, label: entry.label, selected: entry.id === tenet?.id }))
        };
    }

    #collectIdeology(form) {
        const selections = [...form.querySelectorAll("[data-ideology-row]")].map(row => ({
            topicId: String(row.querySelector("[data-ideology-topic-select]")?.value || "").trim(),
            tenetId: String(row.querySelector("[data-ideology-tenet-select]")?.value || "").trim()
        })).filter(selection => selection.topicId && selection.tenetId);
        if (new Set(selections.map(selection => selection.topicId)).size !== selections.length) return null;
        return { schemaVersion: 1, selections };
    }

    #renderIdeologyRow(selection = {}) {
        const row = this.#buildIdeologyRow(selection);
        const safe = value => foundry.utils.escapeHTML(String(value || ""));
        const topicOptions = row.topics.map(option => `<option value="${safe(option.id)}" ${option.selected ? "selected" : ""}>${safe(option.label)}</option>`).join("");
        const tenetOptions = row.tenets.map(option => `<option value="${safe(option.id)}" ${option.selected ? "selected" : ""}>${safe(option.label)}</option>`).join("");
        return `<label><span>Topic</span><select data-ideology-topic-select>${topicOptions}</select><small data-ideology-question>${safe(row.topicQuestion)}</small></label><label><span>Tenet</span><select data-ideology-tenet-select>${tenetOptions}</select><small data-ideology-description>${safe(row.tenetDescription)}</small></label><button type="button" class="faction-ideology-editor-delete" data-action="deleteIdeologyTopic" title="Remove ideology topic" aria-label="Remove ideology topic"><i class="fas fa-trash"></i></button>`;
    }

    #wireIdeologyRow(row) {
        const topicSelect = row.querySelector("[data-ideology-topic-select]");
        const tenetSelect = row.querySelector("[data-ideology-tenet-select]");
        if (!topicSelect || !tenetSelect) return;
        topicSelect.dataset.previousValue = topicSelect.value;
        topicSelect.addEventListener("change", () => {
            const duplicate = [...this.element.querySelectorAll("[data-ideology-topic-select]")].some(select => select !== topicSelect && select.value === topicSelect.value);
            if (duplicate) {
                ui.notifications.warn("Each ideology topic may only be selected once.");
                topicSelect.value = topicSelect.dataset.previousValue || "";
                return;
            }
            topicSelect.dataset.previousValue = topicSelect.value;
            this.#updateIdeologyRow(row);
            this.#refreshIdeologyControls();
        });
        tenetSelect.addEventListener("change", () => this.#updateIdeologyRow(row, { preserveTenet: true }));
        row.querySelector("[data-action='deleteIdeologyTopic']")?.addEventListener("click", event => {
            event.preventDefault();
            event.stopPropagation();
            row.remove();
            this.#refreshIdeologyControls();
        });
    }

    #updateIdeologyRow(row, { preserveTenet = false } = {}) {
        const topicSelect = row.querySelector("[data-ideology-topic-select]");
        const tenetSelect = row.querySelector("[data-ideology-tenet-select]");
        const topic = this.#ideologyTopics.find(entry => entry.id === topicSelect?.value);
        if (!topic || !tenetSelect) return;
        const requestedTenetId = preserveTenet ? tenetSelect.value : "";
        const tenet = topic.tenets.find(entry => entry.id === requestedTenetId) || topic.tenets[0];
        const safe = value => foundry.utils.escapeHTML(String(value || ""));
        tenetSelect.innerHTML = topic.tenets.map(entry => `<option value="${safe(entry.id)}" ${entry.id === tenet?.id ? "selected" : ""}>${safe(entry.label)}</option>`).join("");
        const question = row.querySelector("[data-ideology-question]");
        const description = row.querySelector("[data-ideology-description]");
        if (question) question.textContent = topic.question;
        if (description) description.textContent = tenet?.description || "";
    }

    #refreshIdeologyControls() {
        const rows = [...this.element.querySelectorAll("[data-ideology-row]")];
        const used = rows.map(row => row.querySelector("[data-ideology-topic-select]")?.value).filter(Boolean);
        for (const row of rows) {
            const select = row.querySelector("[data-ideology-topic-select]");
            if (!select) continue;
            for (const option of select.options) option.disabled = option.value !== select.value && used.includes(option.value);
        }
        const empty = this.element.querySelector("[data-ideology-empty]");
        if (empty) empty.hidden = rows.length > 0;
        const addButton = this.element.querySelector("[data-action='addIdeologyTopic']");
        if (addButton) addButton.disabled = rows.length >= this.#ideologyTopics.length;
    }

    #getHiddenDefaultFields(entity = {}) {
        return new Set(Array.isArray(entity?.dossier?.hiddenDefaultFields) ? entity.dossier.hiddenDefaultFields : []);
    }

    #collectHiddenDefaultFields(form) {
        return [...form.querySelectorAll("input[name='defaultFieldHidden']:checked")].map(input => input.value).filter(Boolean);
    }

    #collectCustomFields(form) {
        return [...form.querySelectorAll("[data-custom-field-row]")].map(row => ({
            id: row.querySelector("[name='customField.id']")?.value || row.dataset.customFieldRow || "",
            label: row.querySelector("[name='customField.label']")?.value || "",
            value: row.querySelector("[name='customField.value']")?.value || ""
        })).filter(field => String(field.label || "").trim() || String(field.value || "").trim());
    }

    #renderCustomFieldRow(field = {}) {
        const safe = value => foundry.utils.escapeHTML(String(value || ""));
        return `
            <input type="hidden" name="customField.id" value="${safe(field.id)}">
            <input type="text" name="customField.label" value="${safe(field.label)}" placeholder="Field" autocomplete="off">
            <input type="text" name="customField.value" value="${safe(field.value)}" placeholder="Value" autocomplete="off">
            <button type="button" class="npc-data-custom-field-move" data-action="moveCustomFieldUp" title="Move field up" aria-label="Move field up"><i class="fas fa-arrow-up"></i></button>
            <button type="button" class="npc-data-custom-field-move" data-action="moveCustomFieldDown" title="Move field down" aria-label="Move field down"><i class="fas fa-arrow-down"></i></button>
            <button type="button" class="npc-data-custom-field-delete" data-action="deleteCustomField" title="Delete field" aria-label="Delete field"><i class="fas fa-trash"></i></button>
        `;
    }

    #wireCustomFieldRow(row) {
        row.querySelector("[data-action='deleteCustomField']")?.addEventListener("click", event => {
            event.preventDefault();
            event.stopPropagation();
            row.remove();
        });
        row.querySelector("[data-action='moveCustomFieldUp']")?.addEventListener("click", event => {
            event.preventDefault();
            event.stopPropagation();
            this.#moveCustomField(event.currentTarget, -1);
        });
        row.querySelector("[data-action='moveCustomFieldDown']")?.addEventListener("click", event => {
            event.preventDefault();
            event.stopPropagation();
            this.#moveCustomField(event.currentTarget, 1);
        });
    }

    #toggleDefaultFieldButton(target) {
        const button = target?.closest?.(".npc-data-field-visibility") || target;
        const input = button?.querySelector?.("input[name='defaultFieldHidden']");
        if (!input) return;
        input.checked = !input.checked;
        button.classList.toggle("is-hidden-field", input.checked);
        button.title = input.checked ? "Show field" : "Hide field";
        button.setAttribute("aria-label", button.title);
        const icon = button.querySelector("i");
        icon?.classList.toggle("fa-eye", !input.checked);
        icon?.classList.toggle("fa-eye-slash", input.checked);
    }

    #moveCustomField(target, direction) {
        const row = target?.closest?.("[data-custom-field-row]");
        if (!row) return;
        if (direction < 0 && row.previousElementSibling) row.previousElementSibling.before(row);
        if (direction > 0 && row.nextElementSibling) row.nextElementSibling.after(row);
    }

    #positionComboMenu(combo) {
        combo.classList.remove("drop-up");
        const menu = combo.querySelector(".npc-data-combo-menu");
        const fields = combo.closest(".npc-data-editor-fields");
        if (!menu || !fields) return;
        const comboRect = combo.getBoundingClientRect();
        const fieldsRect = fields.getBoundingClientRect();
        const menuHeight = Math.min(menu.scrollHeight || 188, 188);
        const spaceBelow = fieldsRect.bottom - comboRect.bottom;
        const spaceAbove = comboRect.top - fieldsRect.top;
        if (spaceBelow < menuHeight + 8 && spaceAbove > spaceBelow) combo.classList.add("drop-up");
    }

    #idFromLabel(label = "") {
        return String(label || "").trim().toLocaleLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
    }

    #getCenteredPosition() {
        const width = Math.min(1320, Math.max(640, window.innerWidth - 40));
        const height = Math.min(760, Math.max(520, window.innerHeight - 40));
        return {
            width,
            height,
            left: Math.max(20, Math.round((window.innerWidth - width) / 2)),
            top: Math.max(20, Math.round((window.innerHeight - height) / 2))
        };
    }
}
