import { computed, Ref } from "vue";
import { isFunction } from "./common";

export const DoNotCache = Symbol("DoNotCache");

export type Computable<T> = T | Ref<T> | (() => T);
export type ProcessedComputable<T> = T | Ref<T>;
export type GetComputableType<T> = T extends { [DoNotCache]: true }
    ? T
    : T extends () => infer S
    ? Ref<S>
    : undefined extends T
    ? undefined
    : T;
export type GetComputableTypeWithDefault<T, S> = undefined extends T
    ? S
    : GetComputableType<NonNullable<T>>;
export type UnwrapComputableType<T> = T extends Ref<infer S> ? S : T extends () => infer S ? S : T;

export type ComputableKeysOf<T> = Pick<
    T,
    {
        [K in keyof T]: T[K] extends Computable<unknown> ? K : never;
    }[keyof T]
>;

// TODO fix the typing of this function, such that casting isn't necessary and can be used to
// detect if a createX function is validly written
export function processComputable<T, S extends keyof ComputableKeysOf<T>>(
    obj: T,
    key: S
): asserts obj is T & { [K in S]: ProcessedComputable<UnwrapComputableType<T[S]>> } {
    const computable = obj[key];
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    if (isFunction(computable) && computable.length === 0 && !(computable as any)[DoNotCache]) {
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        obj[key] = computed(computable.bind(obj));
    }
}

export function convertComputable<T>(obj: Computable<T>): ProcessedComputable<T> {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    if (isFunction(obj) && !(obj as any)[DoNotCache]) {
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        obj = computed(obj);
    }
    return obj as ProcessedComputable<T>;
}