import type { PureScopedStyleSheet } from "./createStylesheet.js";
import type { VarConsumer, createVarFactory } from "./createVarFactory.js";

/**
 * Only an empty string is considered as false in CSS together with React. As `""` is
 * falsy and never rendered to the CSS. The same applies to inline styles.
 */
const CSS_VALUE_FALSE = " ";

type BoolCondition = string | VarConsumer<boolean, boolean>;
type BoolConditions = Array<BoolCondition> | BoolCondition;

type BoolIf = {
    when: BoolConditions;
    then: string | BoolIf;
    or?: string | BoolIf;
};

type BoolIfFunction = (booleanVariable: BoolConditions | BoolIf, whenTrue?: string, whenFalse?: string) => string;

type BoolSwitchFunction = (cases: Array<[BoolConditions, string]>, defaultValue?: string) => string;

type BoolNotFunction = (boolean: VarConsumer<boolean, boolean> | string) => string;

type BoolOrFunction = (boolean: BoolCondition[]) => string;

const createUtilsFactory = (
    meta: PureScopedStyleSheet,
    { variable, vars: varsFn }: ReturnType<typeof createVarFactory>,
): {
    boolIf: BoolIfFunction;
    boolSwitch: BoolSwitchFunction;
    boolNot: BoolNotFunction;
    boolOr: BoolOrFunction;
} => {
    const boolIf: BoolIfFunction = (booleanVariable, whenTrue, whenFalse) => {
        let useBooleanVariable: BoolConditions;
        let useWhenTrue: string | BoolIf;
        let useWhenFalse: string | BoolIf;

        if (typeof booleanVariable === "object" && !Array.isArray(booleanVariable)) {
            const { when, then, or } = booleanVariable as unknown as BoolIf;
            useBooleanVariable = when;
            useWhenTrue = then;
            useWhenFalse = or;
        } else {
            useBooleanVariable = booleanVariable;
            useWhenTrue = whenTrue;
            useWhenFalse = whenFalse;
        }
        useWhenFalse = useWhenFalse || CSS_VALUE_FALSE;

        if (Array.isArray(useBooleanVariable)) {
            const obj: BoolIf = {
                when: undefined,
                then: undefined,
                or: undefined,
            };
            let pointer = obj;
            const { length } = useBooleanVariable;
            for (let i = 0; i < length; i++) {
                pointer.when = useBooleanVariable[i];
                pointer.or = useWhenFalse;

                if (i === length - 1) {
                    pointer.then = useWhenTrue;
                } else {
                    pointer.then = {
                        when: undefined,
                        then: undefined,
                        or: useWhenFalse,
                    };
                    pointer = pointer.then;
                }
            }
            return boolIf(obj);
        } else {
            // Direct usage of variable instead of `var(--...)`
            if (typeof useBooleanVariable === "string" && useBooleanVariable.startsWith("--")) {
                useBooleanVariable = `var(${useBooleanVariable})`;
            }

            const [v] = varsFn({
                true: typeof useWhenTrue === "object" ? boolIf(useWhenTrue) : useWhenTrue,
                false: `${typeof useBooleanVariable === "function" ? useBooleanVariable() : useBooleanVariable} ${
                    typeof useWhenFalse === "object" ? boolIf(useWhenFalse) : useWhenFalse
                }`,
            });

            if (useWhenFalse === "inherit") {
                throw new Error(
                    `Due to the nature how conditionals work in CSS, it is not allowed to use "inherit" as a falsy value. Please reverse your condition with the help of "boolNot" or use another value.`,
                );
            }

            return variable(v.false(true, v.true()))();
        }
    };

    const boolSwitch: BoolSwitchFunction = (cases, defaultValue) => {
        const obj: BoolIf = {
            when: undefined,
            then: undefined,
            or: undefined,
        };
        const { length } = cases;
        let pointer = obj;

        for (let i = 0; i < length; i++) {
            const [condition, whenTrue] = cases[i];
            pointer.when = condition;
            pointer.then = whenTrue;
            if (i === length - 1) {
                pointer.or = defaultValue;
            } else {
                pointer.or = {
                    when: undefined,
                    then: undefined,
                    or: undefined,
                };
                pointer = pointer.or;
            }
        }
        return boolIf(obj);
    };

    const boolNot: BoolNotFunction = (consumer) => {
        let useConsumer = consumer;
        if (typeof useConsumer === "string" && useConsumer.startsWith("var(")) {
            useConsumer = useConsumer.slice(4, -1);
        }

        return `var(${typeof useConsumer === "function" ? useConsumer(false) : useConsumer}-not)`;
    };

    const boolOr: BoolOrFunction = (booleans) =>
        boolSwitch(
            booleans.map((condition) => [condition, "initial"]),
            CSS_VALUE_FALSE,
        );

    return { boolIf, boolSwitch, boolNot, boolOr };
};

export {
    type BoolIfFunction,
    type BoolOrFunction,
    type BoolSwitchFunction,
    type BoolNotFunction,
    createUtilsFactory,
    CSS_VALUE_FALSE,
};
