import { openActionMenu, promptTextInput } from "../../../api/ui.js";
import { canRerollNpcCurrentThread, deleteNpc, getNpc, rerollNpcCurrentThread, updateNpc } from "../../../api/npcs.js";
import { ConnectionsBoard } from "../../connections/components/ConnectionsBoard.js";
import { ConnectionOpinionService } from "../../connections/services/ConnectionOpinionService.js";
import { ConnectionStore } from "../../connections/services/ConnectionStore.js";
import { ConnectionTargetResolver } from "../../connections/services/ConnectionTargetResolver.js";
import { SiteCoverPicker } from "../../site/applications/SiteCoverPicker.js";
import { SiteCoverCatalog } from "../../site/services/SiteCoverCatalog.js";
import { PlayerNpcVisibilityDialog } from "../../nexus/applications/PlayerNpcVisibilityDialog.js";
import { PlayerVisibilityInfoPanel } from "../../nexus/applications/PlayerVisibilityInfoPanel.js";
import { DossierTabPreference } from "../../nexus/services/DossierTabPreference.js";
import { applySessionWindowPlacement, rememberSessionWindowPlacement } from "/modules/augur-nexus/scripts/api/window-placement.js";
import { NpcEntityModel } from "../models/NpcEntityModel.js";
import { NpcLifeViewModel } from "../models/NpcLifeViewModel.js";
import { NpcProfileViewModel } from "../models/NpcProfileViewModel.js";
import { CampaignEntityDossierRefresh } from "../services/CampaignEntityDossierRefresh.js";
import { CampaignEntityActorBridge } from "../services/CampaignEntityActorBridge.js";
import { CampaignEntityJournalStore } from "../services/CampaignEntityJournalStore.js";
import { CampaignEntityVisibilityManager } from "../services/CampaignEntityVisibilityManager.js";
import { NpcVisibilityManager } from "../services/NpcVisibilityManager.js";
import { NpcDataEditor } from "./NpcDataEditor.js";
import { NpcPortraitPicker } from "./NpcPortraitPicker.js";
import { EntityChroniclePanel } from "../../region-population/components/EntityChroniclePanel.js";

const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
const PLACEMENT_KEY = "augur-nexus.npc-dossier";
const COMPACT_WIDTH = 420;
let npcDossierCompactPreference = false;


export class NpcDossier extends HandlebarsApplicationMixin(ApplicationV2) {
    #npcId = "";
    #activeTab = "profile";
    #connectionsBoard = null;
    #chroniclePanel = null;
    #connectionsChangedHook = null;
    #entitiesChangedHook = null;
    #lastModel = null;
    #positionInitialized = false;
    #isCompact = false;
    #expandedPosition = null;

    static DEFAULT_OPTIONS = {
        id: "augur-nexus-npc-dossier",
        classes: ["augur-nexus", "npc-dossier", "augur-theme-fantasy"],
        tag: "div",
        window: {
            title: "Person",
            resizable: true,
            minimizable: true
        },
        position: {
            width: 1420,
            height: 780
        }
    };

    static PARTS = {
        content: {
            template: "modules/augur-nexus/templates/campaign-entities/npc-dossier.hbs",
            templates: [
                "modules/augur-nexus/templates/connections/connections-board.hbs",
                "modules/augur-nexus/templates/campaign-entities/npc-life-overview.hbs",
                "modules/augur-nexus/templates/campaign-entities/npc-profile-panel.hbs",
                "modules/augur-nexus/templates/site/site-cover-picker.hbs"
            ]
        }
    };

    get title() {
        return this.#lastModel?.npc?.name || "Person";
    }

    static show({ npcId } = {}) {
        if (!npcId) return null;
        const entity = getNpc(npcId);
        if (entity && !NpcVisibilityManager.canUserSeeNpc(entity)) {
            ui.notifications.warn("That person is not currently visible.");
            return null;
        }
        const app = new this({ npcId });
        app.render(true, { focus: true });
        return app;
    }

    constructor({ npcId } = {}, options = {}) {
        super(options);
        this.#isCompact = npcDossierCompactPreference;
        this.#npcId = npcId || "";
        this.#chroniclePanel = new EntityChroniclePanel({ entityId: this.#npcId });
        this.#activeTab = DossierTabPreference.getDefaultTab(getNpc(this.#npcId));
        this.#connectionsChangedHook = () => this.refresh();
        this.#entitiesChangedHook = event => {
            const changedEntityId = event?.entity?.id || "";
            const currentNodeId = ConnectionTargetResolver.getEntityNodeId(this.#npcId);
            const changedNodeId = ConnectionTargetResolver.getEntityNodeId(changedEntityId);
            const affectsTrackedOpinion = changedEntityId && ConnectionStore.getConnectionsForNode(currentNodeId)
                .some(({ edge, node }) => node.id === changedNodeId && ConnectionOpinionService.isTracked(edge));
            const affectsLifeProjection = changedEntityId && ConnectionStore.getConnectionsForNode(currentNodeId)
                .some(({ node }) => node.id === changedNodeId);
            if (!changedEntityId || changedEntityId === this.#npcId || affectsTrackedOpinion || affectsLifeProjection) this.refresh();
        };
        Hooks.on("augurNexusConnectionsChanged", this.#connectionsChangedHook);
        Hooks.on("augurNexusCampaignEntitiesChanged", this.#entitiesChangedHook);
        CampaignEntityDossierRefresh.watch(this);
    }

    async _prepareContext() {
        const entity = getNpc(this.#npcId);
        if (entity && !NpcVisibilityManager.canUserSeeNpc(entity)) {
            return {
                missing: true,
                npc: { name: "Hidden Person" },
                connections: { board: null }
            };
        }
        if (!entity) {
            return {
                missing: true,
                npc: { name: "Missing Person" },
                connections: { board: null }
            };
        }

        const target = ConnectionTargetResolver.fromCampaignEntity(entity);
        if (!this.#connectionsBoard) this.#connectionsBoard = new ConnectionsBoard({ target, isGM: game.user.isGM });
        else this.#connectionsBoard.setTarget(target);

        const page = CampaignEntityJournalStore.getJournalPage(entity.id);
        const hasChronicle = this.#chroniclePanel.hasEvents();
        if (this.#activeTab === "chronicle" && !hasChronicle) this.#activeTab = "profile";
        const content = await this.#enrichJournalContent(page);
        const linkedActor = await this.#getLinkedActorContext(entity);
        const canRerollCurrentThread = game.user.isGM && canRerollNpcCurrentThread(entity);
        const life = NpcLifeViewModel.fromEntity(entity, { canRerollCurrentThread });

        const model = {
            npc: {
                id: entity.id,
                name: NpcEntityModel.getName(entity),
                profile: NpcProfileViewModel.fromEntity(entity, {
                    showTabs: true,
                    showCompactToggle: true,
                    showDefaultFields: false,
                    includeDescriptorInSubtitle: true,
                    compactMode: this.#isCompact,
                    showActions: game.user.isGM,
                    activeTab: this.#activeTab,
                    hasChronicle,
                    linkedActor,
                    showBody: this.#activeTab === "profile",
                    showCore: this.#isCompact,
                    importance: life.importance,
                    currentThread: life.currentThread,
                    canRerollCurrentThread,
                    canDrag: game.user.isGM
                })
            },
            life,
            dossier: {
                isCompact: this.#isCompact,
                isProfileTab: this.#activeTab === "profile",
                isJournalTab: this.#activeTab === "journal",
                isConnectionsTab: this.#activeTab === "connections",
                isChronicleTab: this.#activeTab === "chronicle"
            },
            chronicleHtml: hasChronicle ? await this.#chroniclePanel.render() : "",
            journal: {
                hasPage: !!page,
                entryId: page?.parent?.id || entity.journalEntryId || "",
                pageId: page?.id || "",
                content,
                hasContent: !!String(page?.text?.content || "").trim()
            },
            connections: {
                board: this.#connectionsBoard.getContext()
            },
            backgroundStyle: this.#buildBackgroundStyle(entity),
            isGM: game.user.isGM
        };
        this.#lastModel = model;
        return model;
    }

    _attachPartListeners(partId, htmlElement, options) {
        if (super._attachPartListeners) super._attachPartListeners(partId, htmlElement, options);
        const element = htmlElement instanceof HTMLElement ? htmlElement : htmlElement?.[0];
        if (!element) return;

        this.#activateActionListeners(element);
    }

    #activateActionListeners(element) {
        const root = element instanceof HTMLElement ? element : element?.[0] || element;
        const portrait = root?.querySelector("[data-npc-dossier-drag='true']");
        if (portrait && !portrait.dataset.npcDossierDragBound) {
            portrait.dataset.npcDossierDragBound = "true";
            portrait.addEventListener("dragstart", event => {
                const entity = getNpc(this.#npcId);
                const target = ConnectionTargetResolver.fromCampaignEntity(entity);
                if (!ConnectionTargetResolver.setDragData(event.dataTransfer, target, { effectAllowed: "copy" })) {
                    event.preventDefault();
                    return;
                }
                event.stopPropagation();
                portrait.classList.add("is-dragging");
            });
            portrait.addEventListener("dragend", () => portrait.classList.remove("is-dragging"));
        }

        root?.querySelectorAll("[data-action='setNpcDossierTab']").forEach(button => {
            if (button.dataset.npcDossierBound) return;
            button.dataset.npcDossierBound = "true";
            button.addEventListener("click", event => {
                event.preventDefault();
                event.stopPropagation();
                NpcDossier._onSetDossierTab.call(this, event, event.currentTarget);
            });
        });

        const actionsButton = root?.querySelector("[data-action='openNpcActions']");
        if (actionsButton && !actionsButton.dataset.npcDossierBound) actionsButton.addEventListener("click", event => {
            event.preventDefault();
            event.stopPropagation();
            NpcDossier._onOpenNpcActions.call(this, event, event.currentTarget);
        });
        if (actionsButton) actionsButton.dataset.npcDossierBound = "true";

        const compactButton = root?.querySelector("[data-action='toggleNpcDossierCompact']");
        if (compactButton && !compactButton.dataset.npcDossierBound) compactButton.addEventListener("click", event => {
            event.preventDefault();
            event.stopPropagation();
            void this.#toggleCompactMode();
        });
        if (compactButton) compactButton.dataset.npcDossierBound = "true";

        const createJournalButton = root?.querySelector("[data-action='createNpcJournal']");
        if (createJournalButton && !createJournalButton.dataset.npcDossierBound) createJournalButton.addEventListener("click", event => {
            event.preventDefault();
            event.stopPropagation();
            NpcDossier._onCreateNpcJournal.call(this, event, event.currentTarget);
        });
        if (createJournalButton) createJournalButton.dataset.npcDossierBound = "true";

        const openJournalButton = root?.querySelector("[data-action='openNpcJournal']");
        if (openJournalButton && !openJournalButton.dataset.npcDossierBound) openJournalButton.addEventListener("click", event => {
            event.preventDefault();
            event.stopPropagation();
            NpcDossier._onOpenNpcJournal.call(this, event, event.currentTarget);
        });
        if (openJournalButton) openJournalButton.dataset.npcDossierBound = "true";

        const openLinkedActorButton = root?.querySelector("[data-action='openNpcLinkedActor']");
        if (openLinkedActorButton && !openLinkedActorButton.dataset.npcDossierBound) openLinkedActorButton.addEventListener("click", event => {
            event.preventDefault();
            event.stopPropagation();
            void this.#openLinkedActor();
        });
        if (openLinkedActorButton) openLinkedActorButton.dataset.npcDossierBound = "true";

        const linkActorButton = root?.querySelector("[data-action='linkNpcActor']");
        if (linkActorButton && !linkActorButton.dataset.npcDossierBound) linkActorButton.addEventListener("click", event => {
            event.preventDefault();
            event.stopPropagation();
            void this.#linkActor();
        });
        if (linkActorButton) linkActorButton.dataset.npcDossierBound = "true";

        const rerollThreadButton = root?.querySelector("[data-action='rerollNpcCurrentThread']");
        if (rerollThreadButton && !rerollThreadButton.dataset.npcDossierBound) rerollThreadButton.addEventListener("click", event => {
            event.preventDefault();
            event.stopPropagation();
            void this.#rerollCurrentThread(event.currentTarget);
        });
        if (rerollThreadButton) rerollThreadButton.dataset.npcDossierBound = "true";

        root?.querySelectorAll("[data-npc-life-node]").forEach(button => {
            if (button.dataset.npcLifeBound) return;
            button.dataset.npcLifeBound = "true";
            button.addEventListener("click", async event => {
                const node = ConnectionStore.getNode(event.currentTarget.dataset.npcLifeNode || "");
                if (node) await ConnectionTargetResolver.openNode(node);
            });
            button.addEventListener("dragstart", event => {
                const node = ConnectionStore.getNode(event.currentTarget.dataset.npcLifeNode || "");
                if (node) ConnectionTargetResolver.setDragData(event.dataTransfer, node, { effectAllowed: "copyMove" });
            });
        });

        root?.querySelectorAll("[data-npc-life-opinion]").forEach(button => {
            if (button.dataset.npcLifeOpinionBound) return;
            button.dataset.npcLifeOpinionBound = "true";
            button.addEventListener("click", event => {
                event.preventDefault();
                event.stopPropagation();
                this.#connectionsBoard?.openOpinionSummary(button, this);
            });
        });

        const resetFeaturedButton = root?.querySelector("[data-action='resetNpcFeaturedConnection']");
        if (resetFeaturedButton && !resetFeaturedButton.dataset.npcFeaturedBound) {
            resetFeaturedButton.dataset.npcFeaturedBound = "true";
            resetFeaturedButton.addEventListener("click", event => {
                event.preventDefault();
                event.stopPropagation();
                void this.#setFeaturedConnection("");
            });
        }

        const featuredDrop = root?.querySelector("[data-npc-featured-drop]");
        if (game.user.isGM && featuredDrop && !featuredDrop.dataset.npcFeaturedBound) {
            featuredDrop.dataset.npcFeaturedBound = "true";
            featuredDrop.addEventListener("dragover", event => {
                event.preventDefault();
                event.stopPropagation();
                if (event.dataTransfer) event.dataTransfer.dropEffect = "link";
                featuredDrop.classList.add("is-drop-target");
            });
            featuredDrop.addEventListener("dragleave", event => {
                if (featuredDrop.contains(event.relatedTarget)) return;
                featuredDrop.classList.remove("is-drop-target");
            });
            featuredDrop.addEventListener("drop", event => {
                event.preventDefault();
                event.stopPropagation();
                featuredDrop.classList.remove("is-drop-target");
                void this.#featureDroppedConnection(event);
            });
        }
    }


    async #featureDroppedConnection(event) {
        if (!game.user.isGM || !this.#connectionsBoard) return;
        try {
            const edge = await this.#connectionsBoard.connectFromDrop(event);
            if (edge?.id) await this.#setFeaturedConnection(edge.id);
        } catch (error) {
            console.error("Augur Nexus | Failed to feature NPC connection", error);
            ui.notifications.error(error?.message || "That connection could not be featured.");
        }
    }

    async #setFeaturedConnection(edgeId = "") {
        const entity = getNpc(this.#npcId);
        if (!game.user.isGM || !entity) return;
        await updateNpc(entity.id, {
            dossier: { featuredConnectionId: String(edgeId || "").trim() }
        });
        this.refresh();
    }
    _onRender(context, options) {
        super._onRender(context, options);
        if (this.window?.title) this.window.title.textContent = this.title;
        this.#activateActionListeners(this.element);
        this.#connectionsBoard?.activateListeners(this.element, this);
        this.#chroniclePanel?.activateListeners(this.element, this);
        if (!this.#positionInitialized) {
            const expanded = applySessionWindowPlacement(this, PLACEMENT_KEY, { fallback: "center" });
            if (this.#isCompact) {
                this.#expandedPosition = expanded ? { ...expanded } : this.#getCurrentPosition();
                this.#applyCompactPosition(this.#expandedPosition);
            }
            this.#positionInitialized = true;
        }
    }

    async #toggleCompactMode() {
        if (!this.#isCompact) {
            rememberSessionWindowPlacement(this, PLACEMENT_KEY);
            this.#expandedPosition = this.#getCurrentPosition();
        }
        this.#isCompact = !this.#isCompact;
        npcDossierCompactPreference = this.#isCompact;
        await this.render({ parts: ["content"] });
        if (this.#isCompact) this.#applyCompactPosition(this.#expandedPosition);
        else if (this.#expandedPosition) this.setPosition(this.#expandedPosition);
        else applySessionWindowPlacement(this, PLACEMENT_KEY, { fallback: "center" });
    }

    #applyCompactPosition(source = null) {
        const current = source || this.#getCurrentPosition() || {};
        const oldWidth = Number(current.width || this.position?.width || this.element?.offsetWidth || COMPACT_WIDTH);
        const oldLeft = Number(current.left ?? this.position?.left ?? 20);
        const width = Math.min(COMPACT_WIDTH, Math.max(320, window.innerWidth - 40));
        const right = oldLeft + oldWidth;
        const left = Math.min(Math.max(20, right - width), Math.max(20, window.innerWidth - width - 20));
        const position = {
            left,
            top: Number(current.top ?? this.position?.top ?? 20),
            width,
            height: Number(current.height || this.position?.height || this.element?.offsetHeight || 780)
        };
        this.setPosition(position);
    }

    #getCurrentPosition() {
        const position = this.position || {};
        const left = Number(position.left ?? this.element?.offsetLeft ?? NaN);
        const top = Number(position.top ?? this.element?.offsetTop ?? NaN);
        if (!Number.isFinite(left) || !Number.isFinite(top)) return null;
        return {
            left,
            top,
            width: Number(position.width || this.element?.offsetWidth || 1420),
            height: Number(position.height || this.element?.offsetHeight || 780)
        };
    }

    async close(options) {
        if (!this.#isCompact) rememberSessionWindowPlacement(this, PLACEMENT_KEY);
        CampaignEntityDossierRefresh.unwatch(this);
        this.#chroniclePanel?.destroy();
        if (this.#connectionsChangedHook) Hooks.off("augurNexusConnectionsChanged", this.#connectionsChangedHook);
        if (this.#entitiesChangedHook) Hooks.off("augurNexusCampaignEntitiesChanged", this.#entitiesChangedHook);
        return super.close(options);
    }

    matchesCampaignEntityJournal({ entryId, pageId } = {}) {
        const journal = this.#lastModel?.journal || null;
        if (!journal?.entryId) return false;
        if (pageId) return journal.pageId === pageId;
        return journal.entryId === entryId;
    }

    refreshCampaignEntityDossier() {
        this.refresh();
    }

    refresh() {
        if (this.rendered === false) return;
        this.render({ parts: ["content"] });
    }

    static async _onSetDossierTab(_event, target) {
        const tab = target?.dataset?.tab || "profile";
        this.#activeTab = ["journal", "connections", "chronicle"].includes(tab) ? tab : "profile";
        this.render({ parts: ["content"] });
    }

    static async _onOpenNpcActions(_event, target) {
        const app = this;
        if (!game.user.isGM) return;
        const entity = getNpc(app.#npcId);
        if (!entity) return;
        openActionMenu({
            anchor: target,
            className: "npc-dossier-actions-menu",
            items: [
                {
                    id: "edit-npc",
                    label: "Edit Person",
                    icon: "fas fa-sliders",
                    onSelect: () => app.#editNpc()
                },
                {
                    id: "linked-actor",
                    label: entity.projections?.actorUuid ? "Linked Actor" : "Link Actor",
                    icon: "fas fa-user-circle",
                    onSelect: () => app.#manageActorLink()
                },
                {
                    id: "default-dossier-tab",
                    label: `Default Tab: ${DossierTabPreference.getTabLabel(DossierTabPreference.getDefaultTab(entity))}`,
                    icon: "fas fa-table-columns",
                    onSelect: () => app.#openDefaultDossierTabMenu(target)
                },
                {
                    id: "change-cover",
                    label: "Change Cover Image",
                    icon: "fas fa-image",
                    onSelect: () => app.#changeCoverImage()
                },
                {
                    id: "player-npc-visibility",
                    label: `Visible To Players: ${CampaignEntityVisibilityManager.getPlayerVisibilityLabel(entity)}`,
                    icon: CampaignEntityVisibilityManager.getPlayerVisibilityIcon(entity),
                    onSelect: () => app.#openNpcVisibilityMenu(target)
                },
                {
                    id: "delete",
                    label: "Delete Person",
                    status: "Permanent",
                    icon: "fas fa-skull-crossbones",
                    danger: true,
                    className: "permanent-danger",
                    onSelect: () => app.#deleteNpc()
                }
            ]
        });
    }

    #openDefaultDossierTabMenu(anchor) {
        const entity = getNpc(this.#npcId);
        if (!game.user.isGM || !entity) return;
        openActionMenu({
            anchor,
            className: "npc-dossier-actions-menu",
            items: DossierTabPreference.buildMenuItems(
                DossierTabPreference.getDefaultTab(entity),
                tab => this.#setDefaultDossierTab(tab)
            )
        });
    }

    async #setDefaultDossierTab(tab) {
        const entity = getNpc(this.#npcId);
        if (!game.user.isGM || !entity) return;
        const defaultTab = DossierTabPreference.normalizeTab(tab);
        await updateNpc(entity.id, { dossier: { defaultTab } });
        this.#activeTab = defaultTab;
        this.refresh();
    }

    #openNpcVisibilityMenu(anchor) {
        const entity = getNpc(this.#npcId);
        if (!game.user.isGM || !entity) return;
        const current = CampaignEntityVisibilityManager.getPlayerVisibilityOverride(entity);
        openActionMenu({
            anchor,
            className: "nexus-scene-player-access-menu",
            items: [
                {
                    id: "inherit",
                    label: current === "inherit" ? "Global (Current)" : "Global",
                    icon: "fas fa-layer-group",
                    onSelect: () => this.#setNpcPlayerVisibility("inherit")
                },
                {
                    id: "show",
                    label: current === "show" ? "Yes (Current)" : "Yes",
                    icon: "fas fa-eye",
                    onSelect: () => this.#setNpcPlayerVisibility("show")
                },
                {
                    id: "hide",
                    label: current === "hide" ? "No (Current)" : "No",
                    icon: "fas fa-eye-slash",
                    onSelect: () => this.#setNpcPlayerVisibility("hide")
                },
                {
                    id: "change-global",
                    label: "Change Global Setting...",
                    icon: "fas fa-sliders",
                    onSelect: () => PlayerNpcVisibilityDialog.show("npc")
                },
                {
                    id: "player-visibility-info",
                    label: "What's this?",
                    icon: "fas fa-circle-info",
                    onSelect: () => PlayerVisibilityInfoPanel.show("campaignEntityVisibility")
                }
            ]
        });
    }

    async #getLinkedActorContext(entity) {
        const actor = await CampaignEntityActorBridge.getLinkedActor(entity);
        if (!actor) return null;
        return {
            name: actor.name || "Actor",
            uuid: actor.uuid || "",
            type: actor.type || "Actor"
        };
    }

    async #openLinkedActor() {
        const entity = getNpc(this.#npcId);
        if (!entity) return;
        const actor = await CampaignEntityActorBridge.getLinkedActor(entity);
        if (!actor) {
            ui.notifications.warn("No linked Actor was found for this person.");
            return;
        }
        actor.sheet?.render(true);
    }

    async #linkActor() {
        await this.#manageActorLink();
    }

    async #rerollCurrentThread(button) {
        if (!game.user.isGM) return;
        button.disabled = true;
        try {
            const updated = await rerollNpcCurrentThread(this.#npcId);
            if (!updated) {
                ui.notifications.warn("This person's generator cannot reroll their Current Thread.");
                return;
            }
            this.refresh();
        } catch (error) {
            console.error("Augur Nexus | Failed to reroll NPC Current Thread", error);
            ui.notifications.error("The Current Thread could not be rerolled.");
        } finally {
            button.disabled = false;
        }
    }

    async #manageActorLink() {
        const entity = getNpc(this.#npcId);
        if (!game.user.isGM || !entity) return;
        const result = await CampaignEntityActorBridge.manageActorLink(entity);
        if (result?.action && result.action !== "cancel" && result.action !== "open") this.refresh();
    }

    async #setNpcPlayerVisibility(value) {
        const entity = getNpc(this.#npcId);
        if (!game.user.isGM || !entity) return;
        await CampaignEntityVisibilityManager.setPlayerVisibilityOverride(entity.id, "npc", value);
        this.refresh();
    }

    static async _onCreateNpcJournal() {
        await this.#editJournal({ create: true });
    }

    static async _onOpenNpcJournal() {
        await this.#editJournal({ create: false });
    }

    async #renameNpc() {
        const entity = getNpc(this.#npcId);
        if (!entity) return;
        const name = await promptTextInput({
            title: "Rename Person",
            label: "Name",
            value: NpcEntityModel.getName(entity),
            confirmLabel: "Rename"
        });
        if (!name) return;
        await updateNpc(entity.id, { display: { name } });
        this.refresh();
    }

    #editNpc() {
        const entity = getNpc(this.#npcId);
        if (!game.user.isGM || !entity) return;
        NpcDataEditor.show({
            npcId: entity.id,
            onSave: () => this.refresh()
        });
    }

    #changePortrait() {
        const entity = getNpc(this.#npcId);
        if (!game.user.isGM || !entity) return;
        new NpcPortraitPicker({
            currentPortraitSrc: NpcEntityModel.getImage(entity),
            onSelect: selection => this.#setPortrait(selection?.src || "")
        }).render(true, { focus: true });
    }

    async #setPortrait(path) {
        const entity = getNpc(this.#npcId);
        if (!game.user.isGM || !entity || !path) return;
        await updateNpc(entity.id, { display: { imageSrc: path } });
        this.refresh();
    }

    #changeCoverImage() {
        const entity = getNpc(this.#npcId);
        if (!game.user.isGM || !entity) return;
        SiteCoverPicker.show({
            current: NpcEntityModel.getCoverImage(entity),
            onSelect: path => this.#setCoverImage(path || "")
        });
    }

    async #setCoverImage(path) {
        const entity = getNpc(this.#npcId);
        if (!game.user.isGM || !entity) return;
        await updateNpc(entity.id, { display: { coverImageSrc: path || "" } });
        this.refresh();
    }

    async #editJournal({ create = true } = {}) {
        if (!game.user.isGM) return;
        const page = create
            ? await CampaignEntityJournalStore.getOrCreateJournalPage(this.#npcId)
            : CampaignEntityJournalStore.getJournalPage(this.#npcId);
        if (!page?.parent) {
            ui.notifications.warn("No journal page was found for this person.");
            return;
        }
        page.sheet?.render(true);
    }

    async #deleteNpc() {
        const entity = getNpc(this.#npcId);
        if (!entity) return;
        const confirmed = await foundry.applications.api.DialogV2.confirm({
            window: { title: "Delete Person" },
            content: `<p>Delete <strong>${foundry.utils.escapeHTML(NpcEntityModel.getName(entity))}</strong>?</p>`,
            modal: true,
            rejectClose: false,
            yes: { label: "Delete" },
            no: { label: "Cancel" }
        });
        if (!confirmed) return;
        await deleteNpc(entity.id);
        await this.close();
    }

    async #enrichJournalContent(page) {
        const content = page?.text?.content || "";
        if (!String(content).trim()) return "";
        try {
            return foundry.applications.ux.TextEditor.implementation.enrichHTML(content, {
                relativeTo: page,
                secrets: page.isOwner
            });
        } catch (_err) {
            return content;
        }
    }

    #buildBackgroundStyle(entity) {
        const cover = NpcEntityModel.getCoverImage(entity)
            || SiteCoverCatalog.getAbstractCover(entity?.id || NpcEntityModel.getName(entity) || "npc");
        return cover ? `background-image: url('${cover}');` : "";
    }

}
