import { FactionEntityModel } from "../../campaign-entities/models/FactionEntityModel.js";
import { NpcEntityModel } from "../../campaign-entities/models/NpcEntityModel.js";
import { CampaignEntityIndex } from "../../campaign-entities/services/CampaignEntityIndex.js";
import { OpinionInfluenceProviderRegistry } from "./OpinionInfluenceProviderRegistry.js";
import { ConnectionTargetResolver } from "./ConnectionTargetResolver.js";
import { FactionMembershipService } from "./FactionMembershipService.js";

const COMPATIBILITY_VALUE = 10;
const COMPATIBILITY_LIMIT = 30;
let registered = false;

export function registerCoreOpinionInfluenceProviders() {
    if (registered) return;
    registered = true;

    OpinionInfluenceProviderRegistry.register({
        id: "augur-nexus:personality-compatibility",
        label: "Personality Compatibility",
        sort: 10,
        supports: ({ observerNode, subjectNode }) =>
            observerNode?.entityType === "npc" && subjectNode?.entityType === "npc",
        resolve: ({ observerEntity, subjectEntity }) => resolvePersonalityCompatibility(observerEntity, subjectEntity)
    });

    OpinionInfluenceProviderRegistry.register({
        id: "augur-nexus:faction-membership",
        label: "Faction Membership",
        sort: 15,
        supports: ({ observerNode, subjectNode }) => {
            const pair = new Set([observerNode?.entityType, subjectNode?.entityType]);
            return pair.has("npc") && (pair.size === 1 || pair.has("faction"));
        },
        resolve: ({ observerNode, subjectNode }) => resolveFactionMembershipCompatibility(observerNode, subjectNode)
    });

    OpinionInfluenceProviderRegistry.register({
        id: "augur-nexus:ideology-compatibility",
        label: "Ideological Compatibility",
        sort: 20,
        supports: ({ observerNode, subjectNode }) =>
            ["npc", "faction"].includes(observerNode?.entityType)
            && ["npc", "faction"].includes(subjectNode?.entityType),
        resolve: ({ observerEntity, subjectEntity, observerNode, subjectNode }) =>
            resolveIdeologyCompatibility(observerEntity, subjectEntity, observerNode.entityType, subjectNode.entityType)
    });
}

export function resolveOpinionEntity(node = {}) {
    if (node.entityType === "npc") return CampaignEntityIndex.getNpc(node.entityId);
    if (node.entityType === "faction") return CampaignEntityIndex.getFaction(node.entityId);
    return null;
}

function resolvePersonalityCompatibility(observer = {}, subject = {}) {
    const observerTraits = NpcEntityModel.getTraits(observer);
    const subjectTraits = NpcEntityModel.getTraits(subject);
    const subjectById = new Map(subjectTraits.map(trait => [trait.id, trait]));
    const seenPairs = new Set();
    const details = [];

    for (const trait of observerTraits) {
        const pairId = trait.pairId || trait.id;
        if (seenPairs.has(pairId)) continue;
        if (subjectById.has(trait.id)) {
            seenPairs.add(pairId);
            details.push({
                id: `shared:${pairId}:${trait.id}`,
                label: `Both are ${trait.label}`,
                value: COMPATIBILITY_VALUE
            });
            continue;
        }
        if (trait.oppositeId && subjectById.has(trait.oppositeId)) {
            const opposite = subjectById.get(trait.oppositeId);
            seenPairs.add(pairId);
            details.push({
                id: `opposed:${pairId}:${trait.id}:${opposite.id}`,
                label: `${trait.label} conflicts with ${opposite.label}`,
                value: -COMPATIBILITY_VALUE
            });
        }
    }

    return buildCompatibilityResult("personality", "Personality Compatibility", details);
}

function resolveFactionMembershipCompatibility(observerNode = {}, subjectNode = {}) {
    if (observerNode.entityType === "npc" && subjectNode.entityType === "npc") {
        return resolveSharedFactionMembership(observerNode, subjectNode);
    }

    const npcNode = observerNode.entityType === "npc" ? observerNode : subjectNode;
    const factionNode = observerNode.entityType === "faction" ? observerNode : subjectNode;
    if (!npcNode?.id || !factionNode?.id) return null;
    const membership = FactionMembershipService.getMembershipRecordsForNpcNode(npcNode.id)
        .find(entry => entry.factionNodeId === factionNode.id);
    if (!membership) return null;

    const displayFaction = ConnectionTargetResolver.resolveDisplayNodeSync(membership.factionNode)
        || membership.factionNode;
    const factionName = displayFaction?.name || "the faction";
    const value = membership.isLeader ? 100 : 50;
    return {
        id: "faction-membership",
        label: "Faction Membership",
        value,
        details: [{
            id: `${membership.isLeader ? "leader" : "member"}:${factionNode.id}`,
            label: membership.isLeader ? `Leads ${factionName}` : `Belongs to ${factionName}`,
            value
        }]
    };
}

function resolveSharedFactionMembership(observerNode = {}, subjectNode = {}) {
    const subjectMemberships = new Map(
        FactionMembershipService.getMembershipRecordsForNpcNode(subjectNode.id)
            .map(membership => [membership.factionNodeId, membership])
    );
    const details = [];

    for (const observerMembership of FactionMembershipService.getMembershipRecordsForNpcNode(observerNode.id)) {
        const subjectMembership = subjectMemberships.get(observerMembership.factionNodeId);
        if (!subjectMembership) continue;
        const displayFaction = ConnectionTargetResolver.resolveDisplayNodeSync(observerMembership.factionNode)
            || observerMembership.factionNode;
        const factionName = displayFaction?.name || "the same faction";
        details.push({
            id: `shared:${observerMembership.factionNodeId}`,
            label: `Both belong to ${factionName}`,
            value: COMPATIBILITY_VALUE
        });
        if (!observerMembership.isLeader && subjectMembership.isLeader) {
            details.push({
                id: `leader:${observerMembership.factionNodeId}`,
                label: `${subjectNode.name || "Their counterpart"} leads ${factionName}`,
                value: COMPATIBILITY_VALUE
            });
        }
    }

    return buildCompatibilityResult("faction-membership", "Faction Membership", details);
}

function resolveIdeologyCompatibility(observer = {}, subject = {}, observerType = "", subjectType = "") {
    const observerIdeology = resolveEntityIdeology(observer, observerType);
    const subjectByTopic = new Map(resolveEntityIdeology(subject, subjectType).map(selection => [selection.topicId, selection]));
    const details = [];

    for (const selection of observerIdeology) {
        const other = subjectByTopic.get(selection.topicId);
        if (!other) continue;
        const shared = selection.tenetId === other.tenetId;
        details.push({
            id: `${shared ? "shared" : "opposed"}:${selection.topicId}`,
            label: shared
                ? `Both embrace ${selection.tenetLabel}`
                : `${selection.tenetLabel} conflicts with ${other.tenetLabel}`,
            value: shared ? COMPATIBILITY_VALUE : -COMPATIBILITY_VALUE
        });
    }

    return buildCompatibilityResult("ideology", "Ideological Compatibility", details);
}

function resolveEntityIdeology(entity = {}, entityType = "") {
    if (entityType === "npc") return NpcEntityModel.getIdeology(entity);
    if (entityType === "faction") return FactionEntityModel.getIdeology(entity);
    return [];
}

function buildCompatibilityResult(id, label, details = []) {
    const rawValue = details.reduce((sum, detail) => sum + detail.value, 0);
    const value = Math.max(-COMPATIBILITY_LIMIT, Math.min(COMPATIBILITY_LIMIT, rawValue));
    if (!details.length) return null;
    return { id, label, value, details };
}
