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

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

@component
export 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,
        }
    }
}

// ── Reputation ────────────────────────────────────────────────────────────────

@component
export class Reputation 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
export 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
    setReputation({ factionId, value }: { factionId: string, value: number }): void {
        Factions.setReputation(this.entity, factionId, value);
    }

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

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

export namespace Factions {
    function addFactionManager(entity: Entity) {
        if (!entity.has(FactionManager)) {
            entity.add(new FactionManager());
        }
    }
    export function join(entity: Entity, factionId: string): void {
        addFactionManager(entity);
        const existing = entity.get(FactionMember, (c) => c.factionId === factionId);
        if (!existing) {
            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).map(m => m.factionId);
    }

    export function getReputation(entity: Entity, factionId: string): number {
        return entity.get(Reputation, (c) => c.factionId === factionId)?.score ?? 0;
    }

    export function setReputation(entity: Entity, factionId: string, value: number): void {
        addFactionManager(entity);
        const existing = entity.get(Reputation, (c) => c.factionId === factionId);
        if (existing) {
            existing.state.score = value;
        } else {
            entity.add(new Reputation(factionId, value));
        }
    }

    export function adjustReputation(entity: Entity, factionId: string, delta: number): void {
        addFactionManager(entity);
        const existing = entity.get(Reputation, (c) => c.factionId === factionId);
        if (existing) {
            existing.adjust(delta);
        } else {
            entity.add(new Reputation(factionId, delta));
        }
    }

    /** Returns the observer's minimum reputation across all of target's factions.
     *  Returns null if target has no FactionMember components. */
    export function getReputationBetween(observer: Entity, target: Entity): number | null {
        const factions = getFactions(target);
        if (factions.length === 0) return null;
        return Math.min(...factions.map(f => getReputation(observer, f)));
    }
}
