import type { Entity, EvalContext } from "../core/world";
import { World } from "../core/world";
import type { RPGAction, RPGActions, RPGVariables } from "../types";
import { getComponentName } from "./decorators";

export function resolveVariables(target: Entity | World): RPGVariables {
    const result: RPGVariables = {};
    if (target instanceof World) {
        for (const entity of target) {
            for (const [key, value] of Object.entries(resolveVariables(entity))) {
                result[`${entity.id}.${key}`] = value;
            }
        }
    } else {
        for (const [componentKey, component] of target) {
            const componentName = getComponentName(component.constructor);
            const key = componentName && componentName !== componentKey
                ? `${componentName}(${componentKey})`
                : componentKey;

            for (const [varKey, value] of Object.entries(component.getVariables())) {
                if (value != null) {
                    if (varKey && varKey !== '.') {
                        result[`${key}.${varKey}`] = value;
                    } else {
                        result[key] = value;
                    }
                }
            }
        }
    }
    return result;
}

export function resolveVariable(name: string, ctx: EvalContext): RPGVariables[string] {
    // $. prefix → world globals
    if (name.startsWith('$.')) {
        return ctx.world.globals[name.slice(2)];
    }
    // @entityId.component.variable → another entity
    if (name.startsWith('@')) {
        const dotIdx = name.indexOf('.', 1);
        if (dotIdx === -1) return undefined;
        const entityId = name.slice(1, dotIdx);
        const varName = name.slice(dotIdx + 1);
        const entity = ctx.world.getEntity(entityId);
        if (!entity) {
            console.warn(`[resolveVariable] entity '${entityId}' not found (referenced in '${name}')`);
            return undefined;
        }
        return resolveVariables(entity)[varName];
    }
    // bare name → self entity
    return resolveVariables(ctx)[name];
}
export function resolveActions(target: Entity | World): RPGActions {
    const result: RPGActions = {};
    if (target instanceof World) {
        for (const entity of target) {
            for (const [key, value] of Object.entries(resolveActions(entity))) {
                result[`${entity.id}.${key}`] = value;
            }
        }
    } else {
        for (const [componentKey, component] of target) {
            const componentName = getComponentName(component.constructor);
            const key = componentName && componentName !== componentKey
                ? `${componentName}(${componentKey})`
                : componentKey;

            for (const [actionKey, fn] of Object.entries(component.getActions())) {
                result[`${key}.${actionKey}`] = fn;
            }
        }
    }
    return result;
}

export function executeAction(action: RPGAction, ctx: EvalContext): unknown {
    if (typeof action === 'string') {
        action = { type: action };
    }
    if (!action.type) {
        throw new TypeError(`[executeAction] action object is missing a 'type' property`);
    }
    let entity = ctx;
    let actionType = action.type;

    // @entityId.component.action → dispatch to another entity
    if (action.type.startsWith('@')) {
        const dotIdx = action.type.indexOf('.', 1);
        if (dotIdx === -1) {
            throw new Error(`[executeAction] malformed cross-entity action '${action.type}': missing '.' after entity id`);
        }
        const entityId = action.type.slice(1, dotIdx);
        const found = ctx.world.getEntity(entityId);
        if (!found) {
            // Entity may have been destroyed legitimately — warn and skip rather than throw.
            console.warn(`[executeAction] entity '${entityId}' not found (action '${action.type}')`);
            return;
        }
        entity = found;
        actionType = action.type.slice(dotIdx + 1);
    }

    const actions = resolveActions(entity);
    if (!(actionType in actions)) {
        throw new Error(`[executeAction] action '${actionType}' not found on entity '${entity instanceof World ? 'world' : entity.id}'`);
    }
    return actions[actionType](action.arg, ctx);
}
