import {
    createNpc,
    generateNpc,
    getNpcGeneratorOptions,
    getNpcGenerators,
    getNpcs,
    openNpcDossier
} from "../../../../api/npcs.js";
import {
    createShip,
    generateShip,
    getShipGeneratorOptions,
    getShipGenerators,
    getShips,
    openShipDossier
} from "../../../../api/ships.js";
import {
    createFaction,
    generateFaction,
    getFactionGeneratorOptions,
    getFactionGenerators,
    getFactions,
    openFactionDossier
} from "../../../../api/factions.js";
import {
    composeLocationGeneratorCandidate,
    createLocation,
    generateLocation,
    getLocationGeneratorOptions,
    getLocationGenerators,
    getLocations,
    openLocation,
    reconcileLocationGeneratorCandidate,
    renderLocationGeneratorCandidatePreview
} from "../../../../api/locations.js";
import { NpcDataEditor } from "../../../campaign-entities/applications/NpcDataEditor.js";
import { ShipDataEditor } from "../../../campaign-entities/applications/ShipDataEditor.js";
import { FactionDataEditor } from "../../../campaign-entities/applications/FactionDataEditor.js";
import { LocationDataEditor } from "../../../campaign-entities/applications/LocationDataEditor.js";
import { NpcEntityModel } from "../../../campaign-entities/models/NpcEntityModel.js";
import { ShipEntityModel } from "../../../campaign-entities/models/ShipEntityModel.js";
import { FactionEntityModel } from "../../../campaign-entities/models/FactionEntityModel.js";
import { LocationEntityModel } from "../../../campaign-entities/models/LocationEntityModel.js";
import { NpcProfileViewModel } from "../../../campaign-entities/models/NpcProfileViewModel.js";
import { ShipProfileViewModel } from "../../../campaign-entities/models/ShipProfileViewModel.js";
import { FactionProfileViewModel } from "../../../campaign-entities/models/FactionProfileViewModel.js";
import { LocationProfileViewModel } from "../../../campaign-entities/models/LocationProfileViewModel.js";
import { SiteCreateTab } from "./SiteCreateTab.js";
import { SiteLabelManager } from "../../services/SiteLabelManager.js";

const GENERATOR_GENRES = [
    { id: "fantasy", label: "Fantasy" },
    { id: "scifi", label: "Sci-Fi" }
];

const GENRE_MODULE_REQUIREMENTS = {
    fantasy: {
        moduleId: "augur-fantasy",
        label: "Augur: Fantasy",
        url: "https://www.patreon.com/TheAugur"
    },
    scifi: {
        moduleId: "augur-scifi",
        label: "Augur: Sci-Fi",
        url: "https://foundryvtt.com/packages/augur-scifi"
    }
};

const CONFIGS = {
    location: {
        type: "location",
        label: "Location",
        plural: "Locations",
        icon: "fas fa-map-location-dot",
        generatorLabel: "Generator",
        manualLabel: "Manual Location",
        getGenerators: getLocationGenerators,
        getGeneratorOptions: getLocationGeneratorOptions,
        generate: generateLocation,
        create: createLocation,
        list: getLocations,
        open: openLocation,
        editor: LocationDataEditor,
        model: LocationEntityModel,
        viewModel: LocationProfileViewModel,
        profilePartial: "modules/augur-nexus/templates/campaign-entities/location-profile-panel.hbs",
        composeCandidate: composeLocationGeneratorCandidate,
        reconcileCandidate: reconcileLocationGeneratorCandidate,
        renderCandidatePreview: renderLocationGeneratorCandidatePreview,
        optionGroups: [
            { key: "typeId", name: "generatorTypeId", label: "Kind", source: "types", defaultKey: "typeId" }
        ],
    },
    npc: {
        type: "npc",
        label: "People",
        plural: "People",
        icon: "fas fa-user-astronaut",
        generatorLabel: "Generator",
        manualLabel: "Manual Person",
        getGenerators: getNpcGenerators,
        getGeneratorOptions: getNpcGeneratorOptions,
        generate: generateNpc,
        create: createNpc,
        list: getNpcs,
        open: openNpcDossier,
        editor: NpcDataEditor,
        model: NpcEntityModel,
        viewModel: NpcProfileViewModel,
        profilePartial: "modules/augur-nexus/templates/campaign-entities/npc-profile-panel.hbs",
        optionGroups: [
            { key: "roleId", name: "generatorRoleId", label: "Role", source: "roles", defaultKey: "roleId" },
            { key: "gender", name: "generatorGender", label: "Appearance", source: "genders", defaultKey: "gender" }
        ],
    },
    ship: {
        type: "ship",
        label: "Ship",
        plural: "Ships",
        icon: "fas fa-rocket",
        generatorLabel: "Generator",
        manualLabel: "Manual Ship",
        getGenerators: getShipGenerators,
        getGeneratorOptions: getShipGeneratorOptions,
        generate: generateShip,
        create: createShip,
        list: getShips,
        open: openShipDossier,
        editor: ShipDataEditor,
        model: ShipEntityModel,
        viewModel: ShipProfileViewModel,
        profilePartial: "modules/augur-nexus/templates/campaign-entities/ship-profile-panel.hbs",
        optionGroups: [
            { key: "templateId", name: "generatorTemplateId", label: "Template", source: "templates", defaultKey: "templateId" }
        ],
    },
    faction: {
        type: "faction",
        label: "Organizations",
        plural: "Organizations",
        icon: "fas fa-shield-halved",
        generatorLabel: "Generator",
        manualLabel: "Manual Organization",
        getGenerators: getFactionGenerators,
        getGeneratorOptions: getFactionGeneratorOptions,
        generate: generateFaction,
        create: createFaction,
        list: getFactions,
        open: openFactionDossier,
        editor: FactionDataEditor,
        model: FactionEntityModel,
        viewModel: FactionProfileViewModel,
        profilePartial: "modules/augur-nexus/templates/campaign-entities/faction-profile-panel.hbs",
        optionGroups: [
            { key: "typeId", name: "generatorTypeId", label: "Type", source: "types", defaultKey: "typeId" }
        ],
    }
};

const STATES = new Map();

export class EntityPlacementTab {
    constructor(app, type) {
        this.app = app;
        this.type = type;
        this.config = CONFIGS[type];
        if (!this.config) throw new Error(`Unknown Nexus placement entity type: ${type}`);
        if (!STATES.has(type)) STATES.set(type, this.getDefaultState());
    }

    static isEntityTab(tabId) {
        return Object.hasOwn(CONFIGS, tabId);
    }

    static getConfig(type) {
        return CONFIGS[type] || null;
    }

    static getState(type) {
        return STATES.get(type) || null;
    }

    static getPlacementStyle(type) {
        const state = STATES.get(type) || null;
        if (!state) return null;
        const iconSize = this._getEffectiveIconSizeStatic(state);
        return {
            ...state,
            iconSize,
            labelFontSize: this._getEffectiveLabelFontSizeStatic(state, iconSize),
            labelFontFamily: SiteLabelManager.resolveFontFamily(state)
        };
    }

    static transformPlacementStyle(type, { rotationDelta = 0, iconSizeDelta = 0, resetIconSize = false } = {}) {
        const state = STATES.get(type);
        if (!state) return null;

        if (rotationDelta) {
            state.rotation = this._normalizeRotation(Number(state.rotation || 0) + Number(rotationDelta || 0));
        }

        if (resetIconSize) {
            state.iconSizeMode = "scene-default";
            state.customIconSize = null;
            state.labelFontSizeMode = "auto";
            state.customLabelFontSize = null;
        } else if (iconSizeDelta) {
            state.iconSizeMode = "custom";
            state.customIconSize = this._clampIconSizeStatic(this._getEffectiveIconSizeStatic(state) + Number(iconSizeDelta || 0));
            state.labelFontSizeMode = "auto";
            state.customLabelFontSize = null;
        }

        Hooks.callAll("augurNexusEntityPlacementStyleChanged", type);
        return this.getPlacementStyle(type);
    }

    static getActiveEntityType(activeTab) {
        return this.isEntityTab(activeTab) ? activeTab : null;
    }

    static async getPlacementDraft(type) {
        const config = CONFIGS[type];
        const state = STATES.get(type);
        if (!config || !state) return null;
        if (!state.candidate) {
            const tab = state.controller;
            if (!tab) return null;
            await tab.generateCandidate({ render: false });
        }
        if (!state.candidate) return null;
        return foundry.utils.deepClone({
            type,
            candidate: state.candidate,
            composition: state.composition || { members: [] }
        });
    }

    static completePlacement(type, entity = null) {
        const state = STATES.get(type);
        if (!state) return;
        state.candidate = null;
        state.composition = { members: [] };
        state.lastPlacedEntityId = entity?.id || "";
    }

    async _prepareContext() {
        const state = this._state;
        state.controller = this;
        const allGenerators = this.config.getGenerators();
        state.genreId = GENERATOR_GENRES.some(genre => genre.id === state.genreId) ? state.genreId : "fantasy";
        const genreRequirement = GENRE_MODULE_REQUIREMENTS[state.genreId];
        const genreModuleAvailable = globalThis.game?.modules?.get(genreRequirement.moduleId)?.active === true;
        const genreModuleRequired = !genreModuleAvailable;
        const genreGenerators = genreModuleRequired ? [] : allGenerators.filter(generator => generator.genreId === state.genreId);
        const moduleGenerators = genreGenerators.filter(generator => generator.moduleId !== "augur-nexus");
        const generators = moduleGenerators.length ? moduleGenerators : genreGenerators;
        if (!generators.some(generator => generator.id === state.selectedGeneratorId)) {
            state.selectedGeneratorId = generators[0]?.id || "";
            state.options = {};
            state.candidate = null;
            state.composition = { members: [] };
        }
        const selectedGeneratorId = state.selectedGeneratorId || "";
        const generatorOptions = selectedGeneratorId ? await this.config.getGeneratorOptions(selectedGeneratorId) : {};
        const optionGroups = this._buildOptionGroups(generatorOptions);
        if (selectedGeneratorId && !state.candidate && !state.isGeneratingCandidate) {
            try {
                await this.generateCandidate({ render: false });
            } catch (err) {
                console.warn(`Augur: Nexus | Failed to generate initial ${this.config.label} candidate.`, err);
            }
        }

        const candidateProfileOptions = {
            emptyTraitsLabel: "No personality traits rolled.",
            emptyLabel: `No ${this.config.label.toLowerCase()} profile details selected.`,
            showBody: this.type !== "location"
        };
        if (this.type === "npc") {
            Object.assign(candidateProfileOptions, {
                compactMode: true,
                includeDescriptorInSubtitle: true,
                showDefaultFields: false
            });
        }
        if (this.type === "faction") candidateProfileOptions.showCurrentThread = false;
        const candidateProfile = state.candidate
            ? this.config.viewModel.fromCandidate(state.candidate, candidateProfileOptions)
            : null;
        if (candidateProfile && this.type === "faction") {
            candidateProfile.imageSrc = await FactionEntityModel.getImageAsync(state.candidate);
        }
        const candidateHtml = candidateProfile
            ? await foundry.applications.handlebars.renderTemplate(this.config.profilePartial, { profile: candidateProfile })
            : "";
        const candidateExtensionHtml = state.candidate && this.config.renderCandidatePreview
            ? await this.config.renderCandidatePreview(selectedGeneratorId, state.candidate, state.composition)
            : "";
        const style = this._getStyleContext();

        const context = {
            entityType: this.type,
            isLocation: this.type === "location",
            isNpc: this.type === "npc",
            isShip: this.type === "ship",
            isFaction: this.type === "faction",
            label: this.config.label,
            plural: this.config.plural,
            icon: this.config.icon,
            genreModuleRequired,
            genreModuleRequiresText: `${genreRequirement.label} Module required`,
            genreModuleRequiresUrl: genreRequirement.url,
            hasGenreProvider: generators.length > 0,
            hasMultipleGenreGenerators: generators.length > 1,
            genres: GENERATOR_GENRES.map(genre => ({ ...genre, selected: genre.id === state.genreId })),
            selectedGenreId: state.genreId,
            selectedGenreLabel: GENERATOR_GENRES.find(genre => genre.id === state.genreId)?.label || "Fantasy",
            emptyGenreText: `No ${GENERATOR_GENRES.find(genre => genre.id === state.genreId)?.label || "Fantasy"} ${this.config.label} templates are currently available.`,
            generators: generators.map(generator => ({
                ...generator,
                selected: generator.id === selectedGeneratorId
            })),
            selectedGeneratorId,
            hasGeneratorSelected: !!selectedGeneratorId,
            optionGroups,
            hasOptionGroups: optionGroups.length > 0,
            candidate: state.candidate ? {
                profile: candidateProfile,
                html: candidateHtml,
                extensionHtml: candidateExtensionHtml,
                data: state.candidate
            } : null,
            hasCandidate: !!state.candidate,
            ...style
        };
        context.advancedHtml = await foundry.applications.handlebars.renderTemplate(
            "modules/augur-nexus/templates/site/parts/placement-style-advanced.hbs",
            context
        );
        return context;
    }

    _attachPartListeners(partId, htmlElement) {
        const el = htmlElement instanceof HTMLElement ? htmlElement : htmlElement[0];
        if (!el) return;
        const root = el.querySelector(`[data-entity-placement="${this.type}"]`);
        if (!root) return;

        root.querySelectorAll("[data-entity-genre-id]").forEach(button => {
            button.addEventListener("click", event => {
                event.preventDefault();
                const genreId = event.currentTarget.dataset.entityGenreId === "scifi" ? "scifi" : "fantasy";
                if (this._state.genreId === genreId) return;
                this._state.genreId = genreId;
                this._state.selectedGeneratorId = "";
                this._resetOptionState();
                this._state.candidate = null;
                this._state.composition = { members: [] };
                this.render();
            });
        });

        root.querySelector("[data-action='viewRequiredGenreModule']")?.addEventListener("click", event => {
            event.preventDefault();
            const url = String(event.currentTarget.dataset.requiresUrl || "").trim();
            if (url) window.open(url, "_blank", "noopener,noreferrer");
        });

        root.querySelector("[name='generatorId']")?.addEventListener("change", event => {
            this._state.selectedGeneratorId = event.currentTarget.value || "";
            this._resetOptionState();
            this._state.candidate = null;
            this._state.composition = { members: [] };
            this.render();
        });

        for (const group of this.config.optionGroups) {
            root.querySelector(`[name='${group.name}']`)?.addEventListener("change", event => {
                this._state.options[group.key] = event.currentTarget.value || "random";
                if (this._state.candidate) this.generateCandidate();
            });
        }

        root.querySelector("[data-action='generateCandidate']")?.addEventListener("click", event => {
            event.preventDefault();
            this.generateCandidate();
        });

        root.querySelector("[data-action='rerollCandidate']")?.addEventListener("click", event => {
            event.preventDefault();
            this.generateCandidate();
        });

        root.querySelector("[data-action='editCandidate']")?.addEventListener("click", event => {
            event.preventDefault();
            if (!this._state.candidate) return;
            this.config.editor.show({
                candidate: this._state.candidate,
                onSave: async updated => {
                    this._state.candidate = updated;
                    if (this.config.reconcileCandidate) {
                        this._state.composition = await this.config.reconcileCandidate(
                            this._state.selectedGeneratorId,
                            updated,
                            this._state.composition
                        );
                    }
                    this.render();
                }
            });
        });

        root.querySelector("[data-action='clearCandidate']")?.addEventListener("click", event => {
            event.preventDefault();
            this._state.candidate = null;
            this._state.composition = { members: [] };
            this.render();
        });
        this._attachStyleListeners(root);
    }

    render(force, options) {
        return this.app?.render(force, options);
    }

    async generateCandidate({ render = true } = {}) {
        const generatorId = this._state.selectedGeneratorId || "";
        if (!generatorId) return;
        const options = {};
        for (const group of this.config.optionGroups) {
            options[group.key] = this._state.options[group.key] || "random";
        }
        this._state.isGeneratingCandidate = true;
        try {
            const candidate = await this.config.generate(generatorId, options);
            const composition = candidate && this.config.composeCandidate
                ? await this.config.composeCandidate(generatorId, candidate, options)
                : { members: [] };
            this._state.candidate = candidate;
            this._state.composition = composition || { members: [] };
        } finally {
            this._state.isGeneratingCandidate = false;
        }
        if (render) this.render();
    }

    _buildOptionGroups(generatorOptions = {}) {
        return this.config.optionGroups
            .map(group => {
                const selected = this._state.options[group.key] || generatorOptions.defaults?.[group.defaultKey] || "random";
                this._state.options[group.key] = selected;
                const options = this._buildOptions(generatorOptions[group.source] || [], selected);
                return {
                    ...group,
                    options,
                    hasOptions: options.length > 0
                };
            })
            .filter(group => group.hasOptions);
    }

    _buildOptions(options = [], selectedId = "random") {
        const rows = options.map(option => ({
            ...option,
            selected: option.id === selectedId
        }));
        if (!rows.some(option => option.id === "random")) {
            rows.unshift({ id: "random", label: "Any", selected: selectedId === "random" });
        }
        return rows.map(option => ({
            ...option,
            label: option.id === "random" ? "Any" : option.label
        }));
    }

    _resetOptionState() {
        this._state.options = {};
    }

    getDefaultState() {
        return {
            genreId: "fantasy",
            selectedGeneratorId: null,
            options: {},
            advancedOpen: false,
            iconSizeMode: "scene-default",
            customIconSize: null,
            rotation: 0,
            showLabel: true,
            labelFontSizeMode: "auto",
            customLabelFontSize: null,
            labelFontFamily: SiteLabelManager.FONT_FAMILY,
            labelColor: SiteCreateTab.DEFAULT_COLOR,
            snapToGrid: false,
            candidate: null,
            composition: { members: [] },
            isGeneratingCandidate: false,
            controller: null,
            lastPlacedEntityId: ""
        };
    }

    _getStyleContext() {
        const state = this._state;
        const iconSize = this._getEffectiveIconSize();
        const labelFontSize = this._getEffectiveLabelFontSize(iconSize);
        return {
            advancedOpen: !!state.advancedOpen,
            selectedIconSize: iconSize,
            rotation: EntityPlacementTab._normalizeRotation(state.rotation),
            iconSizeUsesSceneDefault: state.iconSizeMode !== "custom",
            showResetIconSize: true,
            showLabel: state.showLabel !== false,
            showLabelToggleInAdvanced: true,
            selectedLabelFontSize: labelFontSize,
            labelFontSizeUsesAuto: state.labelFontSizeMode !== "custom",
            showResetLabelFontSize: true,
            minLabelFontSize: SiteLabelManager.MIN_FONT_SIZE,
            maxLabelFontSize: SiteLabelManager.MAX_FONT_SIZE,
            selectedLabelFontFamily: SiteLabelManager.resolveFontFamily(state),
            labelFontOptions: SiteLabelManager.FONT_OPTIONS,
            selectedLabelColor: state.labelColor || SiteCreateTab.DEFAULT_COLOR,
            showLabelColorControls: true,
            colors: SiteCreateTab.COLOR_OPTIONS,
            showSnapToGrid: true,
            snapToGrid: !!state.snapToGrid,
            showRandomizeToggle: false
        };
    }

    _attachStyleListeners(root) {
        const state = this._state;
        const advancedDetails = root.querySelector("[data-placement-advanced]");
        const iconSizeInput = root.querySelector("input[name='iconSize']");
        const iconSizeValue = root.querySelector("[data-site-icon-size-value]");
        const labelFontSizeInput = root.querySelector("input[name='labelFontSize']");
        const labelFontSizeValue = root.querySelector("[data-site-label-font-size-value]");
        const resetIconSizeButton = root.querySelector("[data-action='resetIconSize']");
        const resetLabelFontSizeButton = root.querySelector("[data-action='resetLabelFontSize']");
        const labelFontFamilySelect = root.querySelector("select[name='labelFontFamily']");
        const showLabelCheckbox = root.querySelector("input[name='showLabel']");
        const snapToGridCheckbox = root.querySelector("input[name='snapToGrid']");

        advancedDetails?.addEventListener("toggle", event => {
            state.advancedOpen = !!event.currentTarget.open;
        });

        iconSizeInput?.addEventListener("input", event => {
            const nextSize = this._clampIconSize(event.currentTarget.value);
            state.iconSizeMode = "custom";
            state.customIconSize = nextSize;
            state.labelFontSizeMode = "auto";
            state.customLabelFontSize = null;
            const nextLabelFontSize = this._getEffectiveLabelFontSize(nextSize);
            if (iconSizeValue) iconSizeValue.textContent = String(nextSize);
            if (labelFontSizeInput) labelFontSizeInput.value = String(nextLabelFontSize);
            if (labelFontSizeValue) labelFontSizeValue.textContent = String(nextLabelFontSize);
            if (resetIconSizeButton) resetIconSizeButton.disabled = false;
            if (resetLabelFontSizeButton) resetLabelFontSizeButton.disabled = true;
            this._notifyStyleChanged();
        });

        labelFontSizeInput?.addEventListener("input", event => {
            state.labelFontSizeMode = "custom";
            state.customLabelFontSize = SiteLabelManager.clampFontSize(event.currentTarget.value);
            if (labelFontSizeValue) labelFontSizeValue.textContent = String(this._getEffectiveLabelFontSize());
            if (resetLabelFontSizeButton) resetLabelFontSizeButton.disabled = false;
            this._notifyStyleChanged();
        });

        resetIconSizeButton?.addEventListener("click", event => {
            event.preventDefault();
            state.iconSizeMode = "scene-default";
            state.customIconSize = null;
            state.labelFontSizeMode = "auto";
            state.customLabelFontSize = null;
            const nextSize = this._getEffectiveIconSize();
            const nextLabelFontSize = this._getEffectiveLabelFontSize(nextSize);
            if (iconSizeInput) iconSizeInput.value = String(nextSize);
            if (iconSizeValue) iconSizeValue.textContent = String(nextSize);
            if (labelFontSizeInput) labelFontSizeInput.value = String(nextLabelFontSize);
            if (labelFontSizeValue) labelFontSizeValue.textContent = String(nextLabelFontSize);
            resetIconSizeButton.disabled = true;
            if (resetLabelFontSizeButton) resetLabelFontSizeButton.disabled = true;
            this._notifyStyleChanged();
            this.render();
        });

        resetLabelFontSizeButton?.addEventListener("click", event => {
            event.preventDefault();
            state.labelFontSizeMode = "auto";
            state.customLabelFontSize = null;
            const nextSize = this._getEffectiveLabelFontSize();
            if (labelFontSizeInput) labelFontSizeInput.value = String(nextSize);
            if (labelFontSizeValue) labelFontSizeValue.textContent = String(nextSize);
            resetLabelFontSizeButton.disabled = true;
            this._notifyStyleChanged();
            this.render();
        });

        labelFontFamilySelect?.addEventListener("change", event => {
            state.labelFontFamily = SiteLabelManager.resolveFontFamily({ labelFontFamily: event.currentTarget.value });
            this._notifyStyleChanged();
        });

        showLabelCheckbox?.addEventListener("change", event => {
            state.showLabel = !!event.currentTarget.checked;
            this._notifyStyleChanged();
            this.render();
        });

        snapToGridCheckbox?.addEventListener("change", event => {
            state.snapToGrid = !!event.currentTarget.checked;
            this._notifyStyleChanged();
        });

        root.querySelectorAll("[data-placement-label-color]").forEach(button => {
            button.addEventListener("click", event => {
                event.preventDefault();
                state.labelColor = event.currentTarget.dataset.placementLabelColor || SiteCreateTab.DEFAULT_COLOR;
                this._notifyStyleChanged();
                this.render();
            });
        });
    }

    _clampIconSize(value) {
        return EntityPlacementTab._clampIconSizeStatic(value);
    }

    _getSceneDefaultIconSize() {
        return EntityPlacementTab._getSceneDefaultIconSizeStatic();
    }

    _getEffectiveIconSize() {
        return EntityPlacementTab._getEffectiveIconSizeStatic(this._state);
    }

    _getEffectiveLabelFontSize(iconSize = null) {
        return EntityPlacementTab._getEffectiveLabelFontSizeStatic(this._state, iconSize ?? this._getEffectiveIconSize());
    }

    static _clampIconSizeStatic(value) {
        const numeric = Number(value);
        if (!Number.isFinite(numeric)) return 100;
        return Math.min(SiteCreateTab.MAX_ICON_SIZE, Math.max(SiteCreateTab.MIN_ICON_SIZE, Math.round(numeric)));
    }

    static _getEffectiveIconSizeStatic(state = {}) {
        if (state.iconSizeMode !== "custom") return this._getSceneDefaultIconSizeStatic();
        return this._clampIconSizeStatic(state.customIconSize ?? state.iconSize ?? 100);
    }

    static _getEffectiveLabelFontSizeStatic(state = {}, iconSize = null) {
        const resolvedIconSize = iconSize ?? this._getEffectiveIconSizeStatic(state);
        if (state.labelFontSizeMode !== "custom") return SiteLabelManager.getDefaultFontSize(resolvedIconSize);
        return SiteLabelManager.clampFontSize(state.customLabelFontSize ?? state.labelFontSize);
    }

    static _getSceneDefaultIconSizeStatic() {
        const sceneGridSize = canvas.grid?.size ?? canvas.dimensions?.size ?? 100;
        return this._clampIconSizeStatic(sceneGridSize);
    }

    static _normalizeRotation(value) {
        const numeric = Number(value);
        if (!Number.isFinite(numeric)) return 0;
        return ((Math.round(numeric) % 360) + 360) % 360;
    }

    _notifyStyleChanged() {
        Hooks.callAll("augurNexusEntityPlacementStyleChanged", this.type);
    }

    get _state() {
        if (!STATES.has(this.type)) STATES.set(this.type, this.getDefaultState());
        return STATES.get(this.type);
    }
}


