import type { Class } from "@common/types";
import { Component, type EvalContext } from "../core/world";
import { evaluateCondition } from "../utils/conditions";
import { action, component, ComponentTag, getComponentMeta, getComponentName, tag, variable } from "../utils/decorators";
import { Stat } from "./stat";

abstract class BaseEffect extends Component<{
    target: string;        // component class ID, e.g. 'Stat'
    targetKey?: string;   // component key, e.g. 'health'
    targetField: 'value' | 'max' | 'min';
    delta: number;
    duration: number | null;   // null = permanent until removed
    remaining: number | null;  // countdown in seconds; null for condition-based/permanent
    condition: string | null;  // keep effect while true; remove when it becomes false
    stacking: 'stack' | 'unique' | 'replace';
    tag: string | null;        // discriminator for stacking; null = no stacking enforcement
}> {
    constructor(opts: {
        target?: Class<Component<any>>,
        targetKey?: string;
        delta: number;
        targetField?: 'value' | 'max' | 'min';
        duration?: number;
        condition?: string;
        stacking?: 'stack' | 'unique' | 'replace';
        tag?: string;
    }) {
        super({
            target: getComponentName(opts.target ?? Stat)!,
            targetKey: opts.targetKey,
            targetField: opts.targetField ?? 'value',
            delta: opts.delta,
            duration: opts.duration ?? null,
            remaining: opts.duration ?? null,
            condition: opts.condition ?? null,
            stacking: opts.stacking ?? 'stack',
            tag: opts.tag ?? null,
        });
    }
}

@tag(ComponentTag.Equippable)
@component
export class Effect extends BaseEffect {
    /** True while the effect's delta is applied to the target stat. */
    @variable('.') active: boolean = false;

    override onAdd(): void {
        const { stacking, tag } = this.state;

        if (tag != null && stacking !== 'stack') {
            const siblings = this.entity.getAll(Effect).filter(e => e !== this && e.state.tag === tag);

            if (stacking === 'unique' && siblings.length > 0) {
                // An effect with this tag is already active — discard the incoming one.
                this.entity.remove(this);
                return;
            }

            if (stacking === 'replace') {
                for (const old of siblings) {
                    this.entity.remove(old);
                }
            }
        }

        const component = getComponentMeta(this.state.target);
        if (component) {
            const stat = this.entity.get(component.ctor, this.state.targetKey);
            if (stat instanceof Stat) {
                stat.applyModifier(this.state.delta, this.state.targetField);
                this.active = true;
            }
        }
    }

    override onRemove(): void {
        if (!this.active) return;
        const component = getComponentMeta(this.state.target);
        if (component) {
            const stat = this.entity.get(component.ctor, this.state.targetKey);
            if (stat instanceof Stat) {
                stat.removeModifier(this.state.delta, this.state.targetField);
            }
        }
        this.active = false;
    }

    @action
    reset(duration?: number): void {
        if (duration != null) {
            this.state.duration = duration;
        }
        this.state.remaining = this.state.duration;
        this.active = true;
    }

    /** Mark as expired. EffectSystem will remove the component, which reverses the delta. */
    @action
    clear(): void {
        this.state.remaining = 0;
        this.emit('expired');
    }

    update(dt: number, ctx: EvalContext): void {
        if (this.state.remaining != null) {
            if (this.state.remaining > 0) {
                this.state.remaining -= dt;
                if (this.state.remaining <= 0) {
                    this.state.remaining = 0;
                    this.clear();
                }
            }
        } else if (this.state.condition != null) {
            if (!evaluateCondition(this.state.condition, ctx)) {
                this.clear();
            }
        }
        // permanent effect (no duration, no condition): nothing to do
    }
}

@component
export class EffectTemplate extends BaseEffect {
}