import { openActionMenu } from "../../../api/ui.js";
import { openNpcCreator } from "../../../api/npcs.js";
import { canRerollFactionCurrentThread, deleteFaction, getFaction, rerollFactionCurrentThread, updateFaction } from "../../../api/factions.js";
import { ConnectionsBoard } from "../../connections/components/ConnectionsBoard.js";
import { ConnectionDropResolver } from "../../connections/services/ConnectionDropResolver.js";
import { ConnectionStore } from "../../connections/services/ConnectionStore.js";
import { ConnectionTargetResolver } from "../../connections/services/ConnectionTargetResolver.js";
import { FactionMembershipService } from "../../connections/services/FactionMembershipService.js";
import { FactionSeatService } from "../../connections/services/FactionSeatService.js";
import { OwnershipService } from "../../connections/services/OwnershipService.js";
import { ShipOwnershipService } from "../../connections/services/ShipOwnershipService.js";
import { SiteCoverPicker } from "../../site/applications/SiteCoverPicker.js";
import { SiteCoverCatalog } from "../../site/services/SiteCoverCatalog.js";
import { FactionEntityModel } from "../models/FactionEntityModel.js";
import { FactionProfileViewModel } from "../models/FactionProfileViewModel.js";
import { FactionDomainViewModel } from "../models/FactionDomainViewModel.js";
import { FactionDiplomacyViewModel } from "../models/FactionDiplomacyViewModel.js";
import { CampaignEntityDossierRefresh } from "../services/CampaignEntityDossierRefresh.js";
import { CampaignEntityJournalStore } from "../services/CampaignEntityJournalStore.js";
import { CampaignEntityVisibilityManager } from "../services/CampaignEntityVisibilityManager.js";
import { PlayerNpcVisibilityDialog } from "../../nexus/applications/PlayerNpcVisibilityDialog.js";
import { PlayerVisibilityInfoPanel } from "../../nexus/applications/PlayerVisibilityInfoPanel.js";
import { DossierTabPreference } from "../../nexus/services/DossierTabPreference.js";
import { FactionActionRegistry } from "../services/FactionActionRegistry.js";
import { applySessionWindowPlacement, rememberSessionWindowPlacement } from "/modules/augur-nexus/scripts/api/window-placement.js";
import { FactionDataEditor } from "./FactionDataEditor.js";
import { EntityChroniclePanel } from "../../region-population/components/EntityChroniclePanel.js";

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

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

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

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

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

    static show({ factionId } = {}) {
        if (!factionId) return null;
        const app = new this({ factionId });
        app.render(true, { focus: true });
        return app;
    }

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

    async _prepareContext() {
        const entity = getFaction(this.#factionId);
        if (!entity) {
            return {
                missing: true,
                faction: { name: "Missing Organization" },
                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 canRerollCurrentThread = game.user.isGM && canRerollFactionCurrentThread(entity);
        const domain = FactionDomainViewModel.fromEntity(entity, { isGM: game.user.isGM });
        const diplomacy = this.#isCompact
            ? { relations: [], hasRelations: false, count: 0 }
            : FactionDiplomacyViewModel.fromEntity(entity);
        const model = {
            faction: {
                id: entity.id,
                name: FactionEntityModel.getName(entity),
                profile: FactionProfileViewModel.fromEntity(entity, {
                    showTabs: true,
                    showCompactToggle: true,
                    compactMode: this.#isCompact,
                    showActions: game.user.isGM,
                    activeTab: this.#activeTab,
                    hasChronicle,
                    showBody: this.#activeTab === "profile",
                    showDiplomacy: !this.#isCompact,
                    canRerollCurrentThread,
                    showIdeology: this.#isCompact,
                    showIdentity: false
                })
            },
            domain,
            diplomacy,
            dossier: {
                isCompact: this.#isCompact,
                isProfileTab: this.#activeTab === "profile",
                isJournalTab: this.#activeTab === "journal",
                isChronicleTab: this.#activeTab === "chronicle",
                isConnectionsTab: this.#activeTab === "connections"
            },
            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);
        this.#activateActionListeners(htmlElement instanceof HTMLElement ? htmlElement : htmlElement?.[0]);
    }

    _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;
        factionDossierCompactPreference = 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));
        this.setPosition({
            left,
            top: Number(current.top ?? this.position?.top ?? 20),
            width,
            height: Number(current.height || this.position?.height || this.element?.offsetHeight || 780)
        });
    }

    #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"] });
    }

    #activateActionListeners(element) {
        if (!element) return;
        const emblem = element.querySelector("[data-faction-dossier-drag='true']");
        if (emblem && !emblem.dataset.factionDossierDragBound) {
            emblem.dataset.factionDossierDragBound = "true";
            emblem.addEventListener("dragstart", event => {
                const entity = getFaction(this.#factionId);
                const target = ConnectionTargetResolver.fromCampaignEntity(entity);
                if (!ConnectionTargetResolver.setDragData(event.dataTransfer, target, { effectAllowed: "copy" })) {
                    event.preventDefault();
                    return;
                }
                event.stopPropagation();
                emblem.classList.add("is-dragging");
            });
            emblem.addEventListener("dragend", () => emblem.classList.remove("is-dragging"));
        }
        element.querySelectorAll("[data-action='setFactionDossierTab']").forEach(button => {
            if (button.dataset.factionDossierBound) return;
            button.dataset.factionDossierBound = "true";
            button.addEventListener("click", event => {
                event.preventDefault();
                const tab = event.currentTarget.dataset.tab || "profile";
                this.#activeTab = ["journal", "connections", "chronicle"].includes(tab) ? tab : "profile";
                this.render({ parts: ["content"] });
            });
        });
        this.#bindAction(element, "openFactionActions", event => {
            event.preventDefault();
            event.stopPropagation();
            this.#openActions(event.currentTarget);
        });
        this.#bindAction(element, "toggleFactionDossierCompact", event => {
            event.preventDefault();
            event.stopPropagation();
            void this.#toggleCompactMode();
        });
        this.#bindAction(element, "rerollFactionCurrentThread", event => {
            event.preventDefault();
            event.stopPropagation();
            void this.#rerollCurrentThread(event.currentTarget);
        });
        this.#bindAction(element, "createFactionJournal", event => {
            event.preventDefault();
            this.#editJournal({ create: true });
        });
        this.#bindAction(element, "openFactionJournal", event => {
            event.preventDefault();
            this.#editJournal({ create: false });
        });
        this.#bindAction(element, "createFactionMember", event => {
            event.preventDefault();
            this.#createMember();
        });
        this.#bindAction(element, "createFactionLeader", event => {
            event.preventDefault();
            event.stopPropagation();
            this.#createLeader();
        });
        element.querySelectorAll("[data-faction-domain-node]").forEach(button => {
            if (button.dataset.factionDomainBound) return;
            button.dataset.factionDomainBound = "true";
            button.addEventListener("click", async event => {
                const node = ConnectionStore.getNode(event.currentTarget.dataset.factionDomainNode || "");
                if (node) await ConnectionTargetResolver.openNode(node);
            });
        });
        element.querySelectorAll("[data-faction-diplomacy-opinion]").forEach(button => {
            if (button.dataset.factionDiplomacyOpinionBound) return;
            button.dataset.factionDiplomacyOpinionBound = "true";
            button.addEventListener("click", event => {
                event.preventDefault();
                event.stopPropagation();
                this.#connectionsBoard?.openOpinionSummary(event.currentTarget, this);
            });
        });
        this.#activateDomainDragAndDrop(element);
    }

    #bindAction(element, action, listener) {
        const button = element.querySelector(`[data-action='${action}']`);
        if (!button || button.dataset.factionActionBound) return;
        button.dataset.factionActionBound = "true";
        button.addEventListener("click", listener);
    }

    #activateDomainDragAndDrop(element) {
        if (!game.user.isGM) return;
        element.querySelectorAll("[data-faction-domain-node][draggable='true']").forEach(card => {
            if (card.dataset.factionDomainDragBound) return;
            card.dataset.factionDomainDragBound = "true";
            card.addEventListener("dragstart", event => {
                const node = ConnectionStore.getNode(card.dataset.factionDomainNode || "");
                if (node) ConnectionTargetResolver.setDragData(event.dataTransfer, node, { effectAllowed: "copyMove" });
            });
        });

        element.querySelectorAll("[data-faction-domain-drop]").forEach(zone => {
            if (zone.dataset.factionDomainDropBound) return;
            zone.dataset.factionDomainDropBound = "true";
            zone.addEventListener("dragover", event => {
                event.preventDefault();
                event.stopPropagation();
                if (event.dataTransfer) event.dataTransfer.dropEffect = "copy";
                zone.classList.add("is-domain-drop-target");
            });
            zone.addEventListener("dragleave", event => {
                if (zone.contains(event.relatedTarget)) return;
                zone.classList.remove("is-domain-drop-target");
            });
            zone.addEventListener("drop", event => {
                event.preventDefault();
                event.stopPropagation();
                zone.classList.remove("is-domain-drop-target");
                void this.#handleDomainDrop(event, zone.dataset.factionDomainDrop || "");
            });
        });
    }

    async #handleDomainDrop(event, action) {
        const faction = getFaction(this.#factionId);
        const relatedTarget = await ConnectionDropResolver.fromEvent(event);
        const factionTarget = faction ? ConnectionTargetResolver.fromCampaignEntity(faction) : null;
        if (!faction || !factionTarget || !relatedTarget) {
            ui.notifications.warn("Drop a Nexus Person or Place here.");
            return;
        }

        try {
            if (action === "member" || action === "leader") {
                if (relatedTarget.kind !== "nexus-entity" || relatedTarget.entityType !== "npc") {
                    ui.notifications.warn(action === "leader" ? "Only a Nexus Person can rule an Organization." : "Only a Nexus Person can become a faction member.");
                    return;
                }
                if (action === "leader") await FactionMembershipService.setLeader(factionTarget, relatedTarget);
                else await FactionMembershipService.setMember(factionTarget, relatedTarget, { role: this.#pickMembershipRole(faction) });
                this.refresh();
                return;
            }

            if (action === "holding" || action === "seat") {
                const isShip = this.#isShipTarget(relatedTarget);
                if (!this.#isPlaceTarget(relatedTarget) && !isShip) {
                    ui.notifications.warn(action === "seat" ? "Only a Nexus Place can become a seat of power." : "Only a Nexus Place or Ship can become a faction holding.");
                    return;
                }
                if (action === "seat" && isShip) {
                    ui.notifications.warn("A Ship can be a faction holding, but not its seat of power.");
                    return;
                }
                if (isShip) {
                    const changed = await this.#assignShipHolding(relatedTarget, factionTarget);
                    if (changed) this.refresh();
                    return;
                }
                const changed = await this.#assignHolding(relatedTarget, factionTarget, { makeSeat: action === "seat" });
                if (changed) this.refresh();
            }
        } catch (error) {
            console.error("Augur Nexus | Failed to update faction domain", error);
            ui.notifications.error(error?.message || "The faction domain could not be updated.");
        }
    }

    async #assignHolding(locationTarget, factionTarget, { makeSeat = false } = {}) {
        const current = OwnershipService.getOwnership(locationTarget, { includeHidden: true });
        if (current && current.ownerNodeId !== factionTarget.id) {
            const confirmed = await this.#confirmHoldingTransfer(current, locationTarget, factionTarget, makeSeat);
            if (!confirmed) return false;
        }
        if (!current || current.ownerNodeId !== factionTarget.id) {
            await OwnershipService.setOwner(locationTarget, factionTarget);
        }
        if (makeSeat) await FactionSeatService.setSeat(factionTarget, locationTarget);
        return true;
    }

    #isPlaceTarget(target) {
        return target?.kind === "nexus-site"
            || target?.kind === "nexus-scene"
            || (target?.kind === "nexus-entity" && target.entityType === "location");
    }

    #isShipTarget(target) {
        return target?.kind === "nexus-entity" && target.entityType === "ship";
    }

    async #assignShipHolding(shipTarget, factionTarget) {
        const current = ShipOwnershipService.getOwner(shipTarget, { includeHidden: true });
        if (current && current.ownerNodeId !== factionTarget.id) {
            const confirmed = await this.#confirmHoldingTransfer(current, shipTarget, factionTarget, false, { assetLabel: "Ship" });
            if (!confirmed) return false;
        }
        if (!current || current.ownerNodeId !== factionTarget.id) {
            await ShipOwnershipService.setOwner(shipTarget, factionTarget);
        }
        return true;
    }

    async #confirmHoldingTransfer(current, locationTarget, factionTarget, makeSeat, { assetLabel = "Location" } = {}) {
        const location = ConnectionTargetResolver.resolveDisplayNodeSync(locationTarget) || locationTarget;
        const faction = ConnectionTargetResolver.resolveDisplayNodeSync(factionTarget) || factionTarget;
        const safeLocation = foundry.utils.escapeHTML(location.name || "this Location");
        const safeOwner = foundry.utils.escapeHTML(current.owner?.name || "its current owner");
        const safeFaction = foundry.utils.escapeHTML(faction.name || "this Organization");
        return (await foundry.applications.api.DialogV2.wait({
            window: { title: makeSeat ? "Transfer and Designate Seat" : `Transfer ${assetLabel} Ownership`, icon: "fa-solid fa-shield-halved" },
            position: { width: 440 },
            content: `<div class="nexus-delete-confirm"><p><strong>${safeLocation}</strong> is currently owned by <strong>${safeOwner}</strong>.</p><p>Transfer it to <strong>${safeFaction}</strong>${makeSeat ? " and designate it as the seat of power" : ""}?</p></div>`,
            rejectClose: false,
            modal: true,
            buttons: [
                { action: "confirm", label: makeSeat ? "Transfer and Designate" : "Transfer Holding", icon: "fa-solid fa-right-left", callback: () => true },
                { action: "cancel", label: "Cancel", icon: "fa-solid fa-xmark", default: true, callback: () => false }
            ]
        })) === true;

    }
    async #rerollCurrentThread(button) {
        if (!game.user.isGM) return;
        button.disabled = true;
        try {
            const updated = await rerollFactionCurrentThread(this.#factionId);
            if (!updated) {
                ui.notifications.warn("This organization's generator cannot reroll its Current Thread.");
                return;
            }
            this.refresh();
        } catch (error) {
            console.error("Augur Nexus | Failed to reroll faction Current Thread", error);
            ui.notifications.error("The Current Thread could not be rerolled.");
        } finally {
            button.disabled = false;
        }
    }
    #openActions(anchor) {
        if (!game.user.isGM) return;
        const entity = getFaction(this.#factionId);
        if (!entity) return;
        const registeredActions = FactionActionRegistry.listFor(entity, { onChanged: () => this.refresh() });
        openActionMenu({
            anchor,
            className: "faction-dossier-actions-menu",
            items: [
                { id: "edit-faction", label: "Edit Organization", icon: "fas fa-sliders", onSelect: () => this.#editFaction() },
                ...registeredActions,
                {
                    id: "default-dossier-tab",
                    label: `Default Tab: ${DossierTabPreference.getTabLabel(DossierTabPreference.getDefaultTab(entity))}`,
                    icon: "fas fa-table-columns",
                    onSelect: () => this.#openDefaultDossierTabMenu(anchor)
                },
                { id: "change-cover", label: "Change Cover Image", icon: "fas fa-image", onSelect: () => this.#changeCoverImage() },
                ...(FactionEntityModel.getCoverImage(entity) ? [{
                    id: "clear-cover",
                    label: "Clear Cover Image",
                    icon: "fas fa-rotate-left",
                    onSelect: () => this.#setCoverImage("")
                }] : []),
                {
                    id: "player-faction-visibility",
                    label: `Visible To Players: ${CampaignEntityVisibilityManager.getPlayerVisibilityLabel(entity)}`,
                    icon: CampaignEntityVisibilityManager.getPlayerVisibilityIcon(entity),
                    onSelect: () => this.#openFactionVisibilityMenu(anchor)
                },
                { id: "delete", label: "Delete Organization", status: "Permanent", icon: "fas fa-skull-crossbones", danger: true, className: "permanent-danger", onSelect: () => this.#deleteFaction() }
            ]
        });
    }

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

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

    #openFactionVisibilityMenu(anchor) {
        const entity = getFaction(this.#factionId);
        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.#setFactionPlayerVisibility("inherit") },
                { id: "show", label: current === "show" ? "Yes (Current)" : "Yes", icon: "fas fa-eye", onSelect: () => this.#setFactionPlayerVisibility("show") },
                { id: "hide", label: current === "hide" ? "No (Current)" : "No", icon: "fas fa-eye-slash", onSelect: () => this.#setFactionPlayerVisibility("hide") },
                { id: "change-global", label: "Change Global Setting...", icon: "fas fa-sliders", onSelect: () => PlayerNpcVisibilityDialog.show("faction") },
                { id: "player-visibility-info", label: "What's this?", icon: "fas fa-circle-info", onSelect: () => PlayerVisibilityInfoPanel.show("campaignEntityVisibility") }
            ]
        });
    }

    async #setFactionPlayerVisibility(value) {
        const entity = getFaction(this.#factionId);
        if (!game.user.isGM || !entity) return;
        await CampaignEntityVisibilityManager.setPlayerVisibilityOverride(entity.id, "faction", value);
        this.refresh();
    }

    #editFaction() {
        const entity = getFaction(this.#factionId);
        if (!game.user.isGM || !entity) return;
        FactionDataEditor.show({ factionId: entity.id, onSave: () => this.refresh() });
    }

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

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

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

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

    #createMember() {
        const faction = getFaction(this.#factionId);
        if (!game.user.isGM || !faction) return;
        void openNpcCreator({
            openAfterCreate: false,
            generationContext: {
                affiliation: {
                    entityType: "faction",
                    entityId: faction.id,
                    ideology: foundry.utils.deepClone(faction.ideology || {})
                }
            },
            onCreated: async npc => {
                await this.#connectMember(npc);
                this.refresh();
            }
        });
    }

    #createLeader() {
        const faction = getFaction(this.#factionId);
        if (!game.user.isGM || !faction) return;
        void openNpcCreator({
            openAfterCreate: false,
            generationContext: {
                affiliation: {
                    entityType: "faction",
                    entityId: faction.id,
                    ideology: foundry.utils.deepClone(faction.ideology || {})
                }
            },
            onCreated: async npc => {
                await this.#connectLeader(npc);
                this.refresh();
            }
        });
    }


    async #connectMember(npc) {
        const faction = getFaction(this.#factionId);
        if (!faction || !npc) return null;
        const factionTarget = ConnectionTargetResolver.fromCampaignEntity(faction);
        const npcTarget = ConnectionTargetResolver.fromCampaignEntity(npc);
        if (!factionTarget || !npcTarget) return null;
        const role = this.#pickMembershipRole(faction);
        return FactionMembershipService.setMember(factionTarget, npcTarget, { role });
    }

    async #connectLeader(npc) {
        const faction = getFaction(this.#factionId);
        if (!faction || !npc) return null;
        const factionTarget = ConnectionTargetResolver.fromCampaignEntity(faction);
        const npcTarget = ConnectionTargetResolver.fromCampaignEntity(npc);
        if (!factionTarget || !npcTarget) return null;
        return FactionMembershipService.setLeader(factionTarget, npcTarget);
    }

    #pickMembershipRole(faction) {
        const moduleData = faction?.moduleData?.[faction.sourceModule] || faction?.moduleData?.["augur-scifi"] || {};
        const roles = Array.isArray(moduleData.membershipRoles) ? moduleData.membershipRoles : [];
        const values = roles.map(role => String(role || "").trim()).filter(Boolean);
        if (values.length) return values[Math.floor(Math.random() * values.length)];
        return String(moduleData.defaultMembershipRole || "").trim() || "Member";
    }

    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 = FactionEntityModel.getCoverImage(entity)
            || SiteCoverCatalog.getAbstractCover(entity?.id || FactionEntityModel.getName(entity) || "faction");
        return cover ? `background-image: url('${cover}');` : "";
    }

}
