import { ConnectionSidebarModel } from "../../connections/services/ConnectionSidebarModel.js";
import { FactionMembershipService } from "../../connections/services/FactionMembershipService.js";
import { ConnectionTargetResolver } from "../../connections/services/ConnectionTargetResolver.js";
import { FactionEntityModel } from "../models/FactionEntityModel.js";
import { NpcEntityModel } from "../models/NpcEntityModel.js";
import { CampaignEntityIndex } from "./CampaignEntityIndex.js";
import { CampaignEntityVisibilityManager } from "./CampaignEntityVisibilityManager.js";

export class NpcDirectoryModel {
    static build({ search = "", expandedNodeIds = new Set(), isGM = game.user.isGM } = {}) {
        const npcs = CampaignEntityIndex.getNpcs({ search });
        const rows = npcs.filter(entity => CampaignEntityVisibilityManager.canUserSee(entity, game.user)).map(entity => {
            const target = ConnectionTargetResolver.fromCampaignEntity(entity);
            const nodeId = ConnectionTargetResolver.getNodeId(target);
            const expanded = expandedNodeIds.has(nodeId);
            const connections = ConnectionSidebarModel.buildForTarget(target, { expanded });
            const traits = NpcEntityModel.getTraits(entity);
            const isPlayerHidden = !CampaignEntityVisibilityManager.isVisibleToPlayers(entity);
            return {
                id: entity.id,
                entityId: entity.id,
                entityType: "npc",
                name: NpcEntityModel.getName(entity),
                subtitle: NpcEntityModel.getSubtitle(entity),
                searchText: NpcEntityModel.getSearchText(entity),
                roleLabel: entity.role?.roleLabel || "",
                imageSrc: NpcEntityModel.getImage(entity),
                placementImageSrc: NpcEntityModel.getImage(entity),
                accent: NpcEntityModel.getAccent(entity),
                traitCount: traits.length,
                traits: traits.slice(0, 3),
                sourceModule: entity.sourceModule || "",
                tags: entity.tags || [],
                journalEntryId: entity.journalEntryId || "",
                uuid: entity.uuid || "",
                connections,
                isPlayerHidden,
                playerVisibilityValue: CampaignEntityVisibilityManager.getPlayerVisibilityOverride(entity),
                playerVisibilityLabel: CampaignEntityVisibilityManager.getPlayerVisibilityLabel(entity),
                playerVisibilityIcon: CampaignEntityVisibilityManager.getPlayerVisibilityIcon(entity),
                hasOptions: isGM,
                canOpen: true
            };
        });

        return {
            rows,
            hasRows: rows.length > 0,
            isSearching: !!String(search || "").trim(),
            emptyLabel: String(search || "").trim()
                ? "No people match this search."
                : "No people have been created yet."
        };
    }

    static organizeByFaction(directory = {}, {
        grouped = true,
        collapsedFactionIds = new Set(),
        sceneFactionIds = null,
        user = game.user
    } = {}) {
        const sourceRows = Array.isArray(directory?.rows) ? directory.rows : [];
        if (!grouped) {
            return {
                ...directory,
                groups: [],
                grouped: false,
                hasGroups: false,
                hasCollapsibleGroups: false,
                allGroupsCollapsed: false
            };
        }

        const buckets = new Map();
        const unaffiliatedRows = [];

        for (const faction of CampaignEntityIndex.getFactions()) {
            if (!CampaignEntityVisibilityManager.canUserSee(faction, user)) continue;
            const factionId = faction.id;
            buckets.set(factionId, {
                id: factionId,
                entityId: factionId,
                entityType: "faction",
                nodeId: ConnectionTargetResolver.getEntityNodeId(factionId),
                name: FactionEntityModel.getName(faction),
                subtitle: FactionEntityModel.getSubtitle(faction),
                imageSrc: FactionEntityModel.getImage(faction),
                placementImageSrc: FactionEntityModel.getImage(faction),
                accent: FactionEntityModel.getAccent(faction),
                searchText: FactionEntityModel.getSearchText(faction),
                expanded: !collapsedFactionIds.has(factionId),
                isUnaffiliated: false,
                isPlayerHidden: !CampaignEntityVisibilityManager.isVisibleToPlayers(faction),
                playerVisibilityLabel: CampaignEntityVisibilityManager.getPlayerVisibilityLabel(faction),
                playerVisibilityIcon: CampaignEntityVisibilityManager.getPlayerVisibilityIcon(faction),
                hasOptions: user?.isGM === true,
                rows: []
            });
        }

        for (const row of sourceRows) {
            const memberships = FactionMembershipService.getMembershipsForNpc(
                { id: row.connections?.nodeId || ConnectionTargetResolver.getEntityNodeId(row.entityId) },
                { user }
            );
            const seenFactionIds = new Set();

            for (const membership of memberships) {
                const factionId = String(membership.faction?.entityId || "").trim();
                if (!factionId || seenFactionIds.has(factionId)) continue;
                const bucket = buckets.get(factionId);
                if (!bucket) continue;
                seenFactionIds.add(factionId);
                bucket.rows.push({
                    ...row,
                    hasFactionAccent: true,
                    factionAccent: bucket.accent
                });
            }

            if (!seenFactionIds.size) unaffiliatedRows.push(row);
        }

        const groups = [...buckets.values()]
            .filter(group => !(sceneFactionIds instanceof Set)
                || sceneFactionIds.has(group.id)
                || group.rows.length > 0)
            .sort((a, b) => a.name.localeCompare(b.name))
            .map(group => ({
                ...group,
                count: group.rows.length,
                hasMembers: group.rows.length > 0,
                expanded: group.rows.length > 0 && group.expanded
            }));
        if (unaffiliatedRows.length) {
            groups.push({
                id: "unaffiliated",
                nodeId: "",
                name: "Unaffiliated",
                subtitle: "No organization",
                imageSrc: "",
                accent: "",
                searchText: "unaffiliated no organization",
                expanded: !collapsedFactionIds.has("unaffiliated"),
                isUnaffiliated: true,
                rows: unaffiliatedRows,
                count: unaffiliatedRows.length,
                hasMembers: true
            });
        }

        return {
            ...directory,
            rows: sourceRows,
            groups,
            grouped: true,
            hasGroups: groups.length > 0,
            hasCollapsibleGroups: groups.some(group => group.hasMembers),
            allGroupsCollapsed: groups.some(group => group.hasMembers) && groups.filter(group => group.hasMembers).every(group => !group.expanded),
            hasRows: sourceRows.length > 0 || groups.length > 0,
            emptyLabel: sourceRows.length || groups.length
                ? directory.emptyLabel
                : "No people or organizations have been created yet."
        };
    }
}
