import { Component, Entity, World } from "../core/world";
import type { RPGVariables } from "../types";
import { action, component } from "../utils/decorators";

// ── Faction ───────────────────────────────────────────────────────────────────

@component
class Faction extends Component<{ factionId: string }> {
    constructor(factionId: string) {
        super({ factionId });
    }

    get factionId(): string { return this.state.factionId; }
}

// ── FactionMember ─────────────────────────────────────────────────────────────

@component
class FactionMember extends Component<{ factionId: string }> {
    constructor(factionId: string) {
        super({ factionId });
    }

    get factionId(): string { return this.state.factionId; }

    override getVariables(): RPGVariables {
        return {
            [this.factionId]: true,
        }
    }
}

// ── ReputationOf ──────────────────────────────────────────────────────────────
// Observer's opinion of a faction.

@component
class ReputationOf extends Component<{ factionId: string; score: number }> {
    constructor(factionId: string, score = 0) {
        super({ factionId, score });
    }

    get score(): number { return this.state.score; }

    get factionId(): string { return this.state.factionId; }

    adjust(delta: number): void {
        this.state.score += delta;
    }

    override getVariables(): RPGVariables {
        return {
            [this.factionId]: this.score,
        }
    }
}

// ── ReputationIn ──────────────────────────────────────────────────────────────
// A faction's opinion of a specific entity.

@component
class ReputationIn extends Component<{ factionId: string; score: number }> {
    constructor(factionId: string, score = 0) {
        super({ factionId, score });
    }

    get score(): number { return this.state.score; }

    get factionId(): string { return this.state.factionId; }

    adjust(delta: number): void {
        this.state.score += delta;
    }

    override getVariables(): RPGVariables {
        return {
            [this.factionId]: this.score,
        }
    }
}

//── FactionManager ────────────────────────────────────────────────────────────

@component
class FactionManager extends Component<{}> {
    constructor() { super({}); }

    @action
    join(factionId: string): void {
        Factions.join(this.entity, factionId);
    }

    @action
    leave(factionId: string): void {
        Factions.leave(this.entity, factionId);
    }

    @action
    setReputationOf({ factionId, value }: { factionId: string, value: number }): void {
        Factions.setReputationOf(this.entity, factionId, value);
    }

    @action
    adjustReputationOf({ factionId, value }: { factionId: string, value: number }): void {
        Factions.adjustReputationOf(this.entity, factionId, value);
    }

    @action
    setReputationIn({ factionId, value }: { factionId: string, value: number }): void {
        Factions.setReputationIn(this.entity, factionId, value);
    }

    @action
    adjustReputationIn({ factionId, value }: { factionId: string, value: number }): void {
        Factions.adjustReputationIn(this.entity, factionId, value);
    }
}

// ── Factions namespace ────────────────────────────────────────────────────────

export namespace Factions {
    function addFactionManager(entity: Entity) {
        if (!entity.has(FactionManager)) {
            entity.add(new FactionManager());
        }
    }

    export function getFaction(world: World, factionId: string): Faction {
        const existing = world.findComponent(Faction, (c) => c.factionId === factionId);
        if (existing) {
            return existing;
        }

        const factionEntity = world.createEntity();
        factionEntity.add(new FactionManager());
        return factionEntity.add(new Faction(factionId));
    }

    export function join(entity: Entity, factionId: string): void {
        addFactionManager(entity);
        if (!entity.has(FactionMember, (c) => c.factionId === factionId)) {
            getFaction(entity.world, factionId);
            entity.add(new FactionMember(factionId));
        }
    }

    export function leave(entity: Entity, factionId: string): void {
        addFactionManager(entity);
        entity.removeAll(FactionMember, (c) => c.factionId === factionId);
    }

    export function isMember(entity: Entity, factionId: string): boolean {
        return entity.has(FactionMember, (c) => c.factionId === factionId);
    }

    export function getFactions(entity: Entity): string[] {
        return [
            ...entity.getAll(FactionMember),
            ...entity.getAll(Faction),
        ].map(m => m.factionId);
    }

    // How observer sees a faction. Aggregates cross-faction scores when observer
    // is not itself a faction entity.
    export function getReputationOf(observer: Entity | Component<any>, factionId: string, skipAggregation = false): number {
        if (!(observer instanceof Entity)) {
            observer = observer.entity;
        }
        const personal = observer.get(ReputationOf, (c) => c.factionId === factionId)?.score ?? 0;

        if (observer.has(Faction) || skipAggregation) {
            return personal;
        }

        const crossFaction = getFactions(observer)
            .map(myFactionId => getFaction(observer.world, myFactionId).entity.get(ReputationOf, (c) => c.factionId === factionId)?.score ?? 0)
            .reduce((a, b) => a + b, 0);

        return personal + crossFaction;
    }

    // How a specific faction sees target. Aggregates across target's faction memberships.
    // Faction-faction reputation is canonical on ReputationOf of the observer faction.
    export function getReputationIn(target: Entity | Component<any>, factionId: string, skipAggregation = false): number {
        if (!(target instanceof Entity)) {
            target = target.entity;
        }
        const targetFaction = target.get(Faction);
        const observer = getFaction(target.world, factionId).entity;

        if (targetFaction) {
            const targetFactionId = targetFaction.factionId;
            return getReputationOf(observer, targetFactionId);
        }

        const personal = target.get(ReputationIn, (c) => c.factionId === factionId)?.score ?? 0;

        if (skipAggregation) {
            return personal;
        }

        const crossFaction = getFactions(target)
            .map(memberFactionId => getReputationOf(observer, memberFactionId))
            .reduce((a, b) => a + b, 0);

        return personal + crossFaction;
    }

    // How observer sees target. Routes to getReputationOf when target is a faction
    // entity; otherwise sums observer's opinion of each faction target belongs to.
    export function getReputation(observer: Entity | Component<any>, target: Entity | Component<any>): number {
        if (!(observer instanceof Entity)) {
            observer = observer.entity;
        }
        if (!(target instanceof Entity)) {
            target = target.entity;
        }
        const targetFaction = target.get(Faction);

        if (targetFaction) {
            return getReputationOf(observer, targetFaction.factionId);
        }

        return [
            ...getFactions(target).map(fid => getReputationOf(observer, fid)),
            ...getFactions(observer).map(fid => getReputationIn(target, fid, true)),
        ].reduce((a, b) => a + b, 0);
    }

    export function setReputationOf(observer: Entity | Component<any>, factionId: string, value: number): void {
        if (!(observer instanceof Entity)) {
            observer = observer.entity;
        }
        addFactionManager(observer);
        getFaction(observer.world, factionId);

        const existing = observer.get(ReputationOf, (c) => c.factionId === factionId);
        if (existing) {
            existing.state.score = value;
        } else {
            observer.add(new ReputationOf(factionId, value));
        }
    }

    export function adjustReputationOf(observer: Entity | Component<any>, factionId: string, delta: number): void {
        if (!(observer instanceof Entity)) {
            observer = observer.entity;
        }
        addFactionManager(observer);
        getFaction(observer.world, factionId);

        const existing = observer.get(ReputationOf, (c) => c.factionId === factionId);
        if (existing) {
            existing.adjust(delta);
        } else {
            observer.add(new ReputationOf(factionId, delta));
        }
    }

    export function setReputationIn(target: Entity | Component<any>, factionId: string, value: number): void {
        if (!(target instanceof Entity)) {
            target = target.entity;
        }
        const targetFaction = target.get(Faction);
        if (targetFaction) {
            const targetFactionId = targetFaction.factionId;
            const observer = getFaction(target.world, factionId).entity;
            setReputationOf(observer, targetFactionId, value);
            return;
        }

        addFactionManager(target);
        getFaction(target.world, factionId);

        const existing = target.get(ReputationIn, (c) => c.factionId === factionId);
        if (existing) {
            existing.state.score = value;
        } else {
            target.add(new ReputationIn(factionId, value));
        }
    }

    export function adjustReputationIn(target: Entity | Component<any>, factionId: string, delta: number): void {
        if (!(target instanceof Entity)) {
            target = target.entity;
        }
        const targetFaction = target.get(Faction);
        if (targetFaction) {
            const targetFactionId = targetFaction.factionId;
            const observer = getFaction(target.world, factionId).entity;
            adjustReputationOf(observer, targetFactionId, delta);
            return;
        }

        addFactionManager(target);
        getFaction(target.world, factionId);

        const existing = target.get(ReputationIn, (c) => c.factionId === factionId);
        if (existing) {
            existing.adjust(delta);
        } else {
            target.add(new ReputationIn(factionId, delta));
        }
    }
}
