import { ConnectionSidebarModel } from "../../connections/services/ConnectionSidebarModel.js";
import { ConnectionTargetResolver } from "../../connections/services/ConnectionTargetResolver.js";
import { PlaceFactionGroupingModel } from "../models/PlaceFactionGroupingModel.js";
import { PlaceHierarchyModel } from "../models/PlaceHierarchyModel.js";
import { PlaceTypeRegistry } from "./PlaceTypeRegistry.js";

export class PlaceDirectoryModel {
    static build({
        lineageRows = null,
        expandedNodeIds = new Set(),
        expandedPlaceIds = new Set(),
        groupLocationsByFaction = false,
        collapsedFactionGroupIds = new Set(),
        focusSceneId = "",
        focusToScene = false,
        isGM = game.user.isGM
    } = {}) {
        const nodes = PlaceTypeRegistry.getNodes({ isGM, lineageRows });
        const allHierarchyRows = PlaceHierarchyModel.buildRows(nodes);
        const targetSceneId = String(focusSceneId || "").trim();
        const focusAvailable = !!targetSceneId && allHierarchyRows.some(row => row.sceneId === targetSceneId);
        const hierarchyRows = focusToScene && focusAvailable
            ? this.#focusRows(allHierarchyRows, targetSceneId)
            : allHierarchyRows;
        const treeVisibility = new Map();
        const rows = hierarchyRows.map(row => {
            const connectionNodeId = row.lineageNode
                ? ConnectionSidebarModel.getNodeIdForLineageRow(row)
                : ConnectionTargetResolver.getNodeId(row.connectionTarget);
            const connections = row.lineageNode
                ? ConnectionSidebarModel.buildForLineageRow(row, { expanded: expandedNodeIds.has(connectionNodeId) })
                : row.connectionTarget
                    ? ConnectionSidebarModel.buildForTarget(row.connectionTarget, { expanded: expandedNodeIds.has(connectionNodeId) })
                    : null;
            const parentVisible = !row.parentNodeId || treeVisibility.get(row.parentNodeId) === true;
            const treeVisible = parentVisible && (!row.parentNodeId || expandedPlaceIds.has(row.parentNodeId));
            treeVisibility.set(row.key, treeVisible);
            return {
                ...row,
                connections,
                indent: `${row.depth * 15}px`,
                isTreeVisible: treeVisible,
                isCollapsed: row.hasChildren && !expandedPlaceIds.has(row.key),
                hasOptions: row.lineageNode ? false : isGM,
                canOpen: row.lineageNode ? row.canOpen : true
            };
        });
        const organizedRows = groupLocationsByFaction
            ? PlaceFactionGroupingModel.organize(rows, {
                collapsedGroupIds: collapsedFactionGroupIds,
                user: game.user
            })
            : rows.map(row => ({
                ...row,
                viewParentNodeId: row.parentNodeId || "",
                viewDepth: row.depth
            }));
        return {
            rows: organizedRows,
            hasRows: rows.length > 0,
            focusAvailable,
            focusApplied: focusToScene && focusAvailable,
            emptyLabel: "No scenes, sites, locations, or shops have been created yet."
        };
    }

    static #focusRows(rows, sceneId) {
        const targetSceneId = String(sceneId || "").trim();
        if (!targetSceneId) return rows;

        const targets = rows.filter(row => row.sceneId === targetSceneId);
        if (!targets.length) return rows;

        const rowsById = new Map(rows.map(row => [row.id, row]));
        const childrenByParent = new Map();
        for (const row of rows) {
            const parentId = String(row.parentNodeId || "");
            const children = childrenByParent.get(parentId) || [];
            children.push(row.id);
            childrenByParent.set(parentId, children);
        }

        const visibleIds = new Set();
        for (const target of targets) {
            let cursor = target;
            while (cursor && !visibleIds.has(cursor.id)) {
                visibleIds.add(cursor.id);
                cursor = rowsById.get(cursor.parentNodeId) || null;
            }

            const descendants = [...(childrenByParent.get(target.id) || [])];
            while (descendants.length) {
                const childId = descendants.pop();
                if (!childId || visibleIds.has(childId)) continue;
                visibleIds.add(childId);
                descendants.push(...(childrenByParent.get(childId) || []));
            }
        }

        return rows.filter(row => visibleIds.has(row.id));
    }
}
