import type { Component } from "../core/world";
import type { RPGVariables } from "../types";

// TC39 stage-3 decorators use Symbol.metadata; polyfill for runtimes that lack it (e.g. Bun).
(Symbol as { metadata?: symbol }).metadata ??= Symbol.for('Symbol.metadata');

export const ACTION_KEYS = Symbol('rpg.actions');
export const VARIABLE_KEYS = Symbol('rpg.variables');
export const STATE_KEYS = Symbol('rpg.state_variables');

export function action<T extends (arg?: any) => unknown>(
    _target: T,
    context: ClassMethodDecoratorContext<unknown, T>
): void {
    const prev = context.metadata[ACTION_KEYS] as Set<string | symbol> | undefined;
    context.metadata[ACTION_KEYS] = new Set(prev).add(context.name);
}

type VariableContext<T extends RPGVariables[string]> =
    | ClassFieldDecoratorContext<unknown, T>
    | ClassGetterDecoratorContext<unknown, T>
    | ClassDecoratorContext;

function registerVariable(context: VariableContext<RPGVariables[string]>, exportName: string, name?: string): void {
    const prev = context.metadata[VARIABLE_KEYS] as Map<string | symbol, string> | undefined;
    context.metadata[VARIABLE_KEYS] = new Map(prev).set(name ?? context.name ?? exportName, exportName);
}
function registerStateVariable(context: VariableContext<RPGVariables[string]>, name: string): void {
    const prev = context.metadata[STATE_KEYS] as Set<string> | undefined;
    context.metadata[STATE_KEYS] = new Set(prev).add(name);
}

export function variable(name: string): <T extends RPGVariables[string]>(target: undefined | (() => T), context: VariableContext<T>) => void;
export function variable<T extends RPGVariables[string]>(target: undefined, context: ClassFieldDecoratorContext<unknown, T>): void;
export function variable<T extends RPGVariables[string]>(target: () => T, context: ClassGetterDecoratorContext<unknown, T>): void;
export function variable(
    nameOrTarget: string | undefined | (() => RPGVariables[string]),
    context?: VariableContext<RPGVariables[string]>
): unknown {
    if (typeof nameOrTarget === 'string') {
        const exportName = nameOrTarget;
        return (_target: unknown, ctx: VariableContext<RPGVariables[string]>) => {
            registerVariable(ctx, exportName);
        };
    }
    registerVariable(context!, String(context!.name));
}
type ComponentConstructor<T> = abstract new (...args: any[]) => Component<T>;
type MigrationFn = (state: Record<string, unknown>) => Record<string, unknown>;

interface ComponentMeta<T> {
    ctor: ComponentConstructor<T>;
    version: number;
}

interface MigrationEntry {
    toVersion: number;
    fn: MigrationFn;
}

export enum ComponentTag {
    /** Equipment system clones component with this tag onto the owner when equipped. */
    Equippable,
}

const registry = new Map<string, ComponentMeta<any>>();
const nameRegistry = new Map<ComponentConstructor<any>, string>();
const tagsRegistry = new Map<ComponentConstructor<any>, Set<ComponentTag>>();
/** migrations[name][fromVersion] → { toVersion, fn } */
const migrations = new Map<string, Map<number, MigrationEntry>>();

function register<T>(name: string, ctor: ComponentConstructor<T>, version: number): void {
    registry.set(name, { ctor, version });
    nameRegistry.set(ctor, name);
}

function registerTags<T>(ctor: ComponentConstructor<T>, tags: Iterable<ComponentTag>): void {
    const set = tagsRegistry.get(ctor) ?? new Set();
    for (const tag of tags) {
        set.add(tag);
    }
    tagsRegistry.set(ctor, set);
}

export function getComponentMeta<T>(component: string | Function | Component<any>): ComponentMeta<T> | undefined {
    const name = typeof component === 'string' ? component : getComponentName(component);
    if (!name) return undefined;
    return registry.get(name);
}

export function getComponentName(ctor: Function | Component<any>): string | undefined {
    return nameRegistry.get(
        (typeof ctor === 'function' ? ctor : ctor.constructor) as ComponentConstructor<any>
    );
}

export function getComponentTags(ctor: Function | Component<any>): Set<ComponentTag> {
    return new Set(tagsRegistry.get(
        (typeof ctor === 'function' ? ctor : ctor.constructor) as ComponentConstructor<any>
    ) ?? []);
}

/**
 * Register a migration that upgrades component state from `fromVersion` to `toVersion`.
 * Migrations are chained automatically: registering 0→1 and 1→2 handles saves at version 0.
 */
export function registerMigration(
    name: string,
    fromVersion: number,
    toVersion: number,
    fn: MigrationFn,
): void {
    if (!migrations.has(name)) migrations.set(name, new Map());
    migrations.get(name)!.set(fromVersion, { toVersion, fn });
}

/**
 * Apply all registered migrations to bring `state` from `fromVersion` up to the current
 * registered version. Returns the migrated state (may be the same object if no migrations ran).
 */
export function migrateState(
    name: string,
    state: Record<string, unknown>,
    fromVersion: number,
): Record<string, unknown> {
    const meta = registry.get(name);
    if (!meta) return state;
    const chain = migrations.get(name);
    if (!chain) return state;
    let current = fromVersion;
    let s = state;
    while (current < meta.version) {
        const entry = chain.get(current);
        if (!entry) throw new Error(
            `[registry] No migration for '${name}' from version ${current} to ${meta.version}. ` +
            `Register one with registerMigration('${name}', ${current}, ...).`
        );
        s = entry.fn(s);
        current = entry.toVersion;
    }
    return s;
}

interface ComponentOptions<T> {
    name?: string;
    version?: number;
    variables?: (keyof T)[];
}

type ComponentDecorator<T> = (target: ComponentConstructor<T>, ctx: ClassDecoratorContext) => void;

export function component<T>(target: ComponentConstructor<T>, ctx: ClassDecoratorContext): void;
export function component<T>(name: string): ComponentDecorator<T>;
export function component<T>(options: ComponentOptions<T>): ComponentDecorator<T>;
export function component<T>(
    nameOrTargetOrOptions: string | ComponentConstructor<T> | ComponentOptions<T>,
    ctx?: ClassDecoratorContext,
): void | ComponentDecorator<T> {
    if (typeof nameOrTargetOrOptions === 'string') {
        const name = nameOrTargetOrOptions;
        return (target: ComponentConstructor<T>) => register(name, target, 0);
    }
    if (typeof nameOrTargetOrOptions === 'object') {
        const { name, version = 0, variables = [] } = nameOrTargetOrOptions;
        return (target: ComponentConstructor<T>, ctx: ClassDecoratorContext) => {
            register(name ?? String(ctx.name), target, version);

            for (const key of variables) {
                registerStateVariable(ctx, String(key));
            }
        }
    }
    // Used as bare @component
    register(String(ctx!.name), nameOrTargetOrOptions, 0);
}

export function tag<T>(...tags: ComponentTag[]): ComponentDecorator<T> {
    return (target: ComponentConstructor<T>) => {
        registerTags(target, tags);
    }
}
