export class FactionEditorFieldRegistry {
    static #fields = new Map();

    static registerSelect(definition = {}) {
        const id = String(definition.id || "").trim();
        const moduleId = String(definition.moduleId || "").trim();
        const moduleDataKey = String(definition.moduleDataKey || "").trim();
        if (!id || !moduleId || !moduleDataKey) {
            throw new Error("Faction editor select fields require id, moduleId, and moduleDataKey.");
        }
        const options = (Array.isArray(definition.options) ? definition.options : [])
            .map(option => ({
                value: String(option?.value || option?.id || "").trim(),
                label: String(option?.label || option?.value || option?.id || "").trim(),
                description: String(option?.description || "").trim()
            }))
            .filter(option => option.value && option.label);
        if (!options.length) throw new Error(`Faction editor field ${id} requires at least one option.`);

        const normalized = {
            id,
            moduleId,
            moduleDataKey,
            label: String(definition.label || id).trim(),
            description: String(definition.description || "").trim(),
            options,
            isAvailable: typeof definition.isAvailable === "function" ? definition.isAvailable : null,
            normalizeValue: typeof definition.normalizeValue === "function" ? definition.normalizeValue : null,
            getValue: typeof definition.getValue === "function" ? definition.getValue : null,
            applyToCandidate: typeof definition.applyToCandidate === "function" ? definition.applyToCandidate : null,
            applyToEntity: typeof definition.applyToEntity === "function" ? definition.applyToEntity : null
        };
        this.#fields.set(id, normalized);
        return this.#toPublic(normalized);
    }

    static listFor(entity = {}, context = {}) {
        return [...this.#fields.values()]
            .filter(field => !field.isAvailable || field.isAvailable({ entity, context }) !== false)
            .sort((left, right) => left.label.localeCompare(right.label))
            .map(field => this.#buildField(field, entity));
    }

    static applyToCandidate(candidate = {}, values = {}) {
        const next = foundry.utils.deepClone(candidate || {});
        for (const field of this.#fields.values()) {
            if (!Object.hasOwn(values, field.id)) continue;
            const value = this.#normalizeValue(field, values[field.id]);
            if (field.applyToCandidate) {
                const transformed = field.applyToCandidate({ candidate: foundry.utils.deepClone(next), value });
                if (transformed && typeof transformed === "object") Object.assign(next, foundry.utils.deepClone(transformed));
                continue;
            }
            const currentModuleData = foundry.utils.deepClone(next.moduleData?.[field.moduleId] || {});
            next.moduleData = {
                ...(next.moduleData || {}),
                [field.moduleId]: { ...currentModuleData, [field.moduleDataKey]: value }
            };
        }
        return next;
    }

    static async applyToEntity(entity = {}, values = {}, update) {
        let current = entity;
        for (const field of this.#fields.values()) {
            if (!Object.hasOwn(values, field.id)) continue;
            const value = this.#normalizeValue(field, values[field.id]);
            if (field.applyToEntity) {
                const transformed = await field.applyToEntity({ entity: current, value, update });
                if (transformed && typeof transformed === "object") current = transformed;
                continue;
            }
            const currentModuleData = foundry.utils.deepClone(current?.moduleData?.[field.moduleId] || {});
            current = await update(current.id, {
                moduleData: {
                    [field.moduleId]: { ...currentModuleData, [field.moduleDataKey]: value }
                }
            }, { moduleId: field.moduleId });
        }
        return current;
    }

    static #buildField(field, entity) {
        const stored = field.getValue
            ? field.getValue({ entity })
            : entity?.moduleData?.[field.moduleId]?.[field.moduleDataKey];
        const value = this.#normalizeValue(field, stored);
        const selected = field.options.find(option => option.value === value) || field.options[0];
        return {
            id: field.id,
            label: field.label,
            description: field.description,
            value: selected?.value || "",
            selectedDescription: selected?.description || "",
            options: field.options.map(option => ({ ...option, selected: option.value === selected?.value }))
        };
    }

    static #normalizeValue(field, value) {
        const normalized = field.normalizeValue
            ? String(field.normalizeValue(value) || "").trim()
            : String(value || "").trim();
        return field.options.some(option => option.value === normalized)
            ? normalized
            : field.options[0]?.value || "";
    }

    static #toPublic(field) {
        return {
            id: field.id,
            moduleId: field.moduleId,
            moduleDataKey: field.moduleDataKey,
            label: field.label,
            description: field.description,
            options: foundry.utils.deepClone(field.options)
        };
    }
}
