import { ConnectionCategories } from "../../connections/services/ConnectionCategories.js";
import { ConnectionOpinionService } from "../../connections/services/ConnectionOpinionService.js";
import { ConnectionStore } from "../../connections/services/ConnectionStore.js";
import { ConnectionTargetResolver } from "../../connections/services/ConnectionTargetResolver.js";
import { FactionMembershipService } from "../../connections/services/FactionMembershipService.js";
import { LocationLeadershipService } from "../../connections/services/LocationLeadershipService.js";
import { ShipCaptainService } from "../../connections/services/ShipCaptainService.js";
import { NpcEntityModel } from "./NpcEntityModel.js";

const MAX_PLACES = 6;
const MAX_PEOPLE = 6;
const MAX_SHIPS = 6;
const MAX_RESPONSIBILITIES = 2;

export class NpcLifeViewModel {
    static fromEntity(entity = {}, options = {}) {
        const target = ConnectionTargetResolver.fromCampaignEntity(entity);
        const nodeId = ConnectionTargetResolver.getNodeId(target);
        const traits = NpcEntityModel.getTraits(entity);
        const ideology = NpcEntityModel.getIdeology(entity);
        const currentThread = this.#currentThread(entity);
        if (!target || !nodeId) return this.#empty(entity, traits, ideology, currentThread, options);

        const memberships = FactionMembershipService.getMembershipsForNpc(target)
            .sort((left, right) => Number(right.rankValue || 0) - Number(left.rankValue || 0)
                || Number(right.isLeader) - Number(left.isLeader)
                || left.faction.name.localeCompare(right.faction.name));
        const ledLocations = LocationLeadershipService.getLocationsForLeader(target);
        const ledLocationNodeIds = new Set(ledLocations.map(entry => entry.locationNodeId));
        const captainships = ShipCaptainService.getShipsForCaptain(target);
        const captainEdgeIds = new Set(captainships.map(entry => entry.edgeId));
        const connections = ConnectionStore.getConnectionsForNode(nodeId);
        const featuredCandidates = connections
            .map(({ edge, node }) => this.#featuredCard(edge, node, nodeId, {
                isShipCaptain: captainEdgeIds.has(edge.id)
            }))
            .filter(Boolean);
        const requestedFeaturedId = String(entity?.dossier?.featuredConnectionId || "").trim();
        const leadershipMembership = memberships.find(entry => entry.isLeader) || null;
        const captainFeatured = featuredCandidates
            .filter(card => captainEdgeIds.has(card.edgeId))
            .sort(this.#byFeaturedPriority)[0] || null;
        const featured = leadershipMembership
            ? this.#factionImportance(leadershipMembership, nodeId)
            : captainFeatured
                || featuredCandidates.find(card => card.edgeId === requestedFeaturedId)
                || featuredCandidates.sort(this.#byFeaturedPriority)[0]
                || null;
        const affiliations = memberships
            .map(entry => this.#affiliation(entry, nodeId))
            .filter(Boolean);
        const responsibilityCandidates = ledLocations.map(entry => this.#locationLeadership(entry));
        const secondaryImportanceCandidates = [
            ...(leadershipMembership && captainFeatured ? [captainFeatured] : []),
            ...responsibilityCandidates
        ];
        const importanceCards = secondaryImportanceCandidates.slice(0, MAX_RESPONSIBILITIES);
        const responsibilityOverflow = Math.max(0, secondaryImportanceCandidates.length - importanceCards.length);
        const responsibilities = responsibilityCandidates.slice(0, MAX_RESPONSIBILITIES);
        const importance = [
            ...(featured ? [featured] : []),
            ...importanceCards
        ];

        const places = connections
            .filter(({ node }) => this.#isPlaceNode(node))
            .map(({ edge, node }) => this.#worldTieCard(edge, node, nodeId, {
                fallbackName: "Unknown Place",
                fallbackSubtitle: "Place",
                fallbackRole: "Connected Place"
            }))
            .map(entry => ({ ...entry, isResponsibility: ledLocationNodeIds.has(entry.nodeId) }))
            .sort((left, right) => Number(right.isResponsibility) - Number(left.isResponsibility)
                || this.#byRoleAndName(left, right))
            .slice(0, Math.max(MAX_PLACES, ledLocations.length));
        const people = connections
            .filter(({ node }) => node?.kind === "nexus-entity" && node.entityType === "npc")
            .map(({ edge, node }) => this.#personCard(edge, node, nodeId))
            .sort((left, right) => right.priority - left.priority || left.name.localeCompare(right.name))
            .slice(0, MAX_PEOPLE)
            .map(card => this.#withPrimaryFaction(card));
        const ships = connections
            .filter(({ node }) => node?.kind === "nexus-entity" && node.entityType === "ship")
            .map(({ edge, node }) => this.#worldTieCard(edge, node, nodeId, {
                fallbackName: "Unknown Ship",
                fallbackSubtitle: "Ship",
                fallbackRole: "Connected Ship"
            }))
            .sort(this.#byRoleAndName)
            .slice(0, MAX_SHIPS);
        const hasWorldTies = !!featured
            || affiliations.length > 0
            || responsibilities.length > 0
            || ships.length > 0
            || places.length > 0
            || people.length > 0;
        const worldTieCount = affiliations.length + ships.length + people.length + places.length;

        return {
            identity: this.#identity(entity),
            traits,
            ideology,
            currentThread,
            hasTraits: traits.length > 0,
            hasIdeology: ideology.length > 0,
            hasCurrentThread: currentThread.length > 0,
            canRerollCurrentThread: options.canRerollCurrentThread === true,
            featured,
            hasFeatured: !!featured,
            featuredIsManual: !leadershipMembership && !captainFeatured
                && !!featured && featured.edgeId === requestedFeaturedId,
            importance,
            hasImportance: importance.length > 0,
            affiliations,
            importanceCards,
            responsibilities,
            responsibilityOverflow,
            ships,
            places,
            people,
            hasAffiliations: affiliations.length > 0,
            hasResponsibilities: responsibilities.length > 0,
            hasShips: ships.length > 0,
            hasPlaces: places.length > 0,
            hasPeople: people.length > 0,
            hasWorldTieEntities: worldTieCount > 0,
            worldTieCount,
            hasWorldTies,
            sectionCount: [
                featured ? 1 : 0,
                affiliations.length,
                responsibilities.length,
                ships.length,
                places.length,
                people.length
            ].filter(Boolean).length
        };
    }

    static #currentThread(entity = {}) {
        const hiddenFields = new Set(Array.isArray(entity?.dossier?.hiddenDefaultFields)
            ? entity.dossier.hiddenDefaultFields
            : []);
        return [
            { key: "drive", label: "Wants", icon: "fas fa-bullseye", value: entity?.flavor?.drive },
            { key: "action", label: "Approach", icon: "fas fa-bolt", value: entity?.flavor?.action },
            { key: "theme", label: "Tone", icon: "fas fa-masks-theater", value: entity?.flavor?.theme },
            { key: "focus", label: "Complication", icon: "fas fa-triangle-exclamation", value: entity?.flavor?.focus }
        ].map(entry => ({ ...entry, value: String(entry.value || "").trim() }))
            .filter(entry => entry.value && !hiddenFields.has(entry.key));
    }

    static #identity(entity) {
        return {
            name: NpcEntityModel.getName(entity),
            role: NpcEntityModel.getSubtitle(entity),
            descriptor: String(entity?.flavor?.descriptor || "").trim(),
            description: String(entity?.flavor?.description || "").trim(),
            imageSrc: NpcEntityModel.getImage(entity),
            accent: NpcEntityModel.getAccent(entity)
        };
    }

    static #affiliation(entry, observerNodeId) {
        const edge = ConnectionStore.getConnectionEdge(entry.edgeId);
        const view = ConnectionStore.getConnectionView(edge, observerNodeId);
        const presentation = this.#connectionPresentation(edge, observerNodeId, "Member");
        return {
            edgeId: entry.edgeId,
            nodeId: entry.factionNodeId,
            name: entry.faction.name,
            imageSrc: entry.faction.imageSrc,
            subtitle: entry.faction.subtitle || "Faction Member",
            role: entry.rankLabel || "Member",
            categoryIcon: "fas fa-shield-halved",
            categoryColor: presentation.categoryColor,
            note: String(view.note || "").trim(),
            opinion: this.#opinion(edge, observerNodeId)
        };
    }

    static #factionImportance(entry, observerNodeId) {
        const edge = ConnectionStore.getConnectionEdge(entry.edgeId);
        return {
            edgeId: entry.edgeId,
            nodeId: entry.factionNodeId,
            name: entry.faction.name,
            imageSrc: entry.faction.imageSrc,
            subtitle: entry.faction.subtitle || "Organization",
            role: entry.rankLabel || (entry.isLeader ? "Faction Leader" : "Faction Member"),
            icon: entry.isLeader ? "fas fa-crown" : "fas fa-shield-halved",
            opinion: this.#opinion(edge, observerNodeId),
            priority: 500 + Number(entry.rankValue || 0),
            sort: 0,
            isEmblem: true,
            isSquare: false
        };
    }

    static #locationLeadership(entry) {
        return {
            edgeId: entry.edgeId,
            nodeId: entry.locationNodeId,
            name: entry.location.name,
            imageSrc: entry.location.imageSrc,
            subtitle: entry.location.subtitle || "Location",
            role: entry.presentation?.npcRole || "Location Ruler",
            icon: "fas fa-key",
            opinion: null
        };
    }

    static #worldTieCard(edge, node, currentNodeId, {
        fallbackName = "Unknown Connection",
        fallbackSubtitle = "Connection",
        fallbackRole = "Connection"
    } = {}) {
        const display = ConnectionTargetResolver.resolveDisplayNodeSync(node) || node;
        const view = ConnectionStore.getConnectionView(edge, currentNodeId);
        const presentation = this.#connectionPresentation(edge, currentNodeId, fallbackRole);
        return {
            edgeId: edge.id,
            nodeId: node.id,
            name: display.name || fallbackName,
            imageSrc: display.img || "",
            subtitle: display.subtitle || fallbackSubtitle,
            ...presentation,
            note: String(view.note || "").trim(),
            opinion: this.#opinion(edge, currentNodeId)
        };
    }

    static #personCard(edge, node, currentNodeId) {
        const display = ConnectionTargetResolver.resolveDisplayNodeSync(node) || node;
        const view = ConnectionStore.getConnectionView(edge, currentNodeId);
        const opinion = this.#opinion(edge, currentNodeId);
        const categoryPriority = ["monster", "rival"].includes(view.category) ? 1000 : 0;
        return {
            edgeId: edge.id,
            nodeId: node.id,
            name: display.name || "Unknown Person",
            imageSrc: display.img || "",
            subtitle: display.subtitle || "Person",
            ...this.#connectionPresentation(edge, currentNodeId, "Contact"),
            note: String(view.note || "").trim(),
            opinion,
            priority: categoryPriority + (opinion ? 100 + Math.abs(Number(opinion.score || 0)) : 0)
        };
    }

    static #withPrimaryFaction(card) {
        const node = ConnectionStore.getNode(card.nodeId);
        if (!node) return card;
        const membership = FactionMembershipService.getMembershipsForNpc(node)
            .sort((left, right) => Number(right.rankValue || 0) - Number(left.rankValue || 0)
                || Number(right.isLeader) - Number(left.isLeader)
                || left.faction.name.localeCompare(right.faction.name))[0] || null;
        if (!membership) return card;
        return {
            ...card,
            allegiance: {
                name: membership.faction.name,
                imageSrc: membership.faction.imageSrc,
                rank: membership.rankLabel || (membership.isLeader ? "Faction Leader" : "Faction Member")
            }
        };
    }

    static #connectionPresentation(edge, currentNodeId, fallbackRole = "Connection") {
        const customCategories = ConnectionStore.getCustomCategories();
        const view = ConnectionStore.getConnectionView(edge, currentNodeId, customCategories);
        const category = ConnectionCategories.get(view.category, customCategories);
        const role = String(view.role || "").trim();
        const genericRole = ConnectionCategories.roleFor(view.category);
        return {
            role: category.custom && (!role || role === genericRole)
                ? String(category.singular || category.label || fallbackRole)
                : role || String(category.singular || fallbackRole),
            categoryId: category.id,
            categoryIcon: String(category.icon || "fas fa-link"),
            categoryColor: String(category.color || "#55bdec")
        };
    }

    static #featuredCard(edge, node, currentNodeId, { isShipCaptain = false } = {}) {
        if (!edge || !node) return null;
        const display = ConnectionTargetResolver.resolveDisplayNodeSync(node) || node;
        const view = ConnectionStore.getConnectionView(edge, currentNodeId);
        const membership = FactionMembershipService.getMembership(edge);
        const isFactionLeader = membership?.memberNodeId === currentNodeId && membership.isLeader;
        const isFactionMember = membership?.memberNodeId === currentNodeId && !membership.isLeader;
        const isLocationRuler = LocationLeadershipService.isLeadershipEdge(edge)
            && edge.locationLeadership?.leaderNodeId === currentNodeId;
        const isPlace = this.#isPlaceNode(node);
        const isShip = node.kind === "nexus-entity" && node.entityType === "ship";
        const opinion = this.#opinion(edge, currentNodeId);
        let priority = 0;
        let role = String(view.role || "Connection");
        let icon = "fas fa-link";
        if (isFactionLeader) {
            priority = 400;
            role = membership.rankLabel || "Faction Leader";
            icon = "fas fa-crown";
        } else if (isShipCaptain) {
            priority = 350;
            role = "Ship Captain";
            icon = "fas fa-rocket";
        } else if (isLocationRuler) {
            priority = 300;
            role = LocationLeadershipService.getPresentation({
                authorityType: display.locationAuthorityType || edge.locationLeadership?.authorityType
            }).npcRole;
            icon = "fas fa-key";
        } else if (isFactionMember) {
            priority = 200;
            role = membership.rankLabel || "Faction Member";
            icon = "fas fa-shield-halved";
        } else if (isPlace) {
            priority = 100;
            icon = "fas fa-location-dot";
        } else if (isShip) {
            priority = 75;
            icon = "fas fa-rocket";
        } else if (node.kind === "nexus-entity" && node.entityType === "npc") {
            priority = 50 + Math.abs(Number(opinion?.score || 0));
            icon = "fas fa-user";
        }
        return {
            edgeId: edge.id,
            nodeId: node.id,
            name: display.name || "Unknown Connection",
            imageSrc: display.img || "",
            subtitle: display.subtitle || "Connection",
            role,
            note: String(view.note || "").trim(),
            icon,
            opinion,
            priority,
            sort: Number(view.sort || 100),
            isEmblem: node.kind === "nexus-entity" && node.entityType === "faction",
            isSquare: isPlace || isShip,
            isShip
        };
    }

    static #byFeaturedPriority(left, right) {
        return right.priority - left.priority
            || Math.abs(Number(right.opinion?.score || 0)) - Math.abs(Number(left.opinion?.score || 0))
            || left.sort - right.sort
            || left.name.localeCompare(right.name);
    }

    static #opinion(edge, observerNodeId) {
        const opinion = edge ? ConnectionOpinionService.resolvePerspective(edge, observerNodeId) : null;
        if (!opinion) return null;
        return {
            score: Number(opinion.score || 0),
            scoreLabel: opinion.scoreLabel,
            tierLabel: opinion.tier.label,
            tierColor: opinion.tier.color
        };
    }

    static #isPlaceNode(node) {
        if (!node) return false;
        if (node.kind === "nexus-entity" && node.entityType === "location") return true;
        return ["nexus-site", "nexus-scene"].includes(node.kind);
    }

    static #byRoleAndName(left, right) {
        return left.role.localeCompare(right.role) || left.name.localeCompare(right.name);
    }

    static #empty(entity, traits, ideology, currentThread, options = {}) {
        return {
            identity: this.#identity(entity),
            traits,
            ideology,
            currentThread,
            hasTraits: traits.length > 0,
            hasIdeology: ideology.length > 0,
            hasCurrentThread: currentThread.length > 0,
            canRerollCurrentThread: options.canRerollCurrentThread === true,
            featured: null,
            hasFeatured: false,
            featuredIsManual: false,
            importance: [],
            hasImportance: false,
            affiliations: [],
            importanceCards: [],
            responsibilities: [],
            responsibilityOverflow: 0,
            ships: [],
            places: [],
            people: [],
            hasAffiliations: false,
            hasResponsibilities: false,
            hasShips: false,
            hasPlaces: false,
            hasPeople: false,
            hasWorldTieEntities: false,
            worldTieCount: 0,
            hasWorldTies: false,
            sectionCount: currentThread.length ? 1 : 0
        };
    }
}
