import { UPDATE_EVENT_PREFIX } from "./events.js";
import { throttling } from "./utils/throttling.js";

import type { PureScopedStyleSheet } from "./createStylesheet.js";
import type { VarConsumer, VarConsumerPossibleMapValue, VarValue, createVarFactory } from "./createVarFactory.js";
import type { UpdateEvent } from "./events.js";

/**
 * Compute a CSS variable from other CSS variables. Use this only, if native CSS
 * functions like `calc` does not suffice.
 */
type CreateComputedFunction = <Value, Variables extends VarConsumer<any, any>[]>(
    vars: readonly [...Variables],
    calc: (values: {
        [Index in keyof Variables]: Variables[Index] extends VarConsumer<infer X, infer Y> ? X : never;
    }) => Value,
    suffix?: string,
    doThrottling?: Parameters<typeof throttling>[0],
) => (Value extends any[]
    ? VarConsumer<Value, VarConsumerPossibleMapValue<Value>>
    : Value extends Record<string, any>
      ? {
            [P in keyof Value]: VarConsumer<Value[P], VarConsumerPossibleMapValue<Value[P]>>;
        }
      : Value extends VarValue
        ? VarConsumer<Value, VarConsumerPossibleMapValue<Value>>
        : VarConsumer<never, VarConsumerPossibleMapValue<Value>>) & {
    update: () => string;
};

const dispatchVariableUpdateEvent = (variableName: string, { mainElement }: PureScopedStyleSheet) => {
    //const previous = oVarsVal[variableName];
    mainElement.dispatchEvent(
        new CustomEvent<UpdateEvent>(`${UPDATE_EVENT_PREFIX}${variableName}`, {
            detail: {},
        }),
    );
};

const createComputedFactory =
    (
        { mainElement, varsVal }: PureScopedStyleSheet,
        { variable, vars: varsFn }: ReturnType<typeof createVarFactory>,
    ): CreateComputedFunction =>
    (vars, calculate, suffix, doThrottling) => {
        let updater: (value: any) => any;
        const variableNames = vars.map((c) => (typeof c === "function" ? c(false) : undefined));

        // Throttle the "expensive" calculation to avoid multiple executions when
        // e.g. running update of `createVarFactory#vars()`.
        const doCalc = () => calculate(variableNames.map((v) => varsVal.get(v)) as any);
        const throttleCalc = throttling(doThrottling || 0, () => updater(doCalc()));

        // Attach to update events
        for (const variableName of variableNames) {
            mainElement.addEventListener(`${UPDATE_EVENT_PREFIX}${variableName}`, throttleCalc);
        }

        // Write the variable once
        const calculated = doCalc();
        const createdVar =
            typeof calculated === "object" && !Array.isArray(calculated)
                ? (() => {
                      const r = varsFn(calculated as any, undefined);
                      [, updater] = r;
                      return r[0];
                  })()
                : (() => {
                      const r = variable(calculated as any, undefined, suffix);
                      updater = r.update;
                      return r;
                  })();

        // Modify updater functionality to not take any parameter and do a recalculate
        createdVar.update = () => throttleCalc() as unknown as string;
        return createdVar as any;
    };

export { type CreateComputedFunction, dispatchVariableUpdateEvent, createComputedFactory };
