import { Atomico, DOMProps } from "./dom.js";
import {
    ConstructorType,
    FillObject,
    NoTypeFor,
    SchemaInfer,
    SchemaProps,
    Type,
    TypeCustom,
    TypeForInstance,
    TypeForJsx
} from "./schema.js";

import { Sheets } from "./css.js";

/**
 * Infer the types from `component.props`.
 
 * ```tsx
 * function component({value}: Props<typeof component.props >){
 *      return <host/>
 * }
 *
 * component.props = {value:Number}
 * ```
 */
export type GetProps<P, TypeFor = NoTypeFor> = P extends {
    readonly "##props"?: infer P;
}
    ? P
    : P extends { props: SchemaProps }
      ? GetProps<P["props"], TypeFor>
      : {
            [K in GetKeysWithConfigValue<P>]: GetPropType<P[K], TypeFor>;
        } & {
            [K in GetKeysWithoutConfigValue<P>]?: GetPropType<P[K], TypeFor>;
        };

type GetKeysWithConfigValue<P> = {
    [I in keyof P]-?: P[I] extends {
        value: any;
    }
        ? I
        : never;
}[keyof P];

type GetKeysWithoutConfigValue<P> = {
    [I in keyof P]-?: P[I] extends {
        value: any;
    }
        ? never
        : I;
}[keyof P];

type GetPropType<Value, TypeFor = NoTypeFor> = Value extends {
    type: infer T;
    value: infer V;
}
    ? T extends TypeCustom<any>
        ? ConstructorType<T, TypeFor>
        : FunctionConstructor extends T
          ? V
          : V extends () => infer T
            ? T
            : V
    : Value extends { type: infer T }
      ? ConstructorType<T, TypeFor>
      : Type<any> extends Value // Sometimes TS saturates, this verification limits the effort of TS to infer
        ? Value extends Type<infer R>
            ? R
            : ConstructorType<Value, TypeFor>
        : ConstructorType<Value, TypeFor>;

/**
 * metaProps allow to hide the props assigned by Component<props>
 */
interface MetaProps<Props> {
    readonly "##props"?: Props;
}

/**
 * The MetaComponent type allows to identify as
 * validate types generated by Component<props>
 */
export interface MetaComponent {
    (props: any): any;
    props: MetaProps<any>;
    styles?: Sheets;
    render?: (props: any) => any;
}

/**
 * Infers the props from the component's props object, example:
 * ### Syntax
 * ```tsx
 * const myProps = { message: String }
 * Props<typeof MyProps>;
 * // {message: string}
 * ```
 * ### Usage
 * You can use the `Prop` type on components, objects or constructors, example:
 * ```tsx
 * function component({message}: Props<typeof component>){
 *  return <host></host>
 * }
 *
 * component.props = {message: String}
 * ```
 *
 * ### Advanced use
 *
 * It also allows to replace types of those already inferred, example:
 * ```tsx
 * Props<typeof MyProps, {message: "hello"|"bye bye"}>;
 * // {message?: "hello"|"bye bye"}
 *
 * ```
 */
export type Props<P = null, TypeFor = NoTypeFor> = P extends null
    ? SchemaProps
    : GetProps<P, TypeFor>;

export type Component<Props = null, Meta = any> = Props extends null
    ? {
          (props: FillObject): Host<Meta>;
          props?: SchemaProps;
          styles?: Sheets;
      }
    : {
          (props: DOMProps<Props>): Host<Meta>;
          props: SchemaInfer<Props> &
              MetaProps<
                  Meta extends null ? Props : Props & SyntheticMetaProps<Meta>
              >;
          styles?: Sheets;
      };

export type CreateElement<C, Base, CheckMeta = true> = CheckMeta extends true
    ? C extends (props: any) => Host<infer Meta>
        ? CreateElement<C & { props: SyntheticProps<Meta> }, Base, false>
        : CreateElement<C, Base, false>
    : C extends { props: infer P }
      ? Atomico<Props<P, TypeForJsx>, Props<P, TypeForInstance>, Base>
      : Atomico<{}, {}, Base>;

export type SyntheticProps<Props> = {
    [Prop in keyof Props]: Prop extends `on${string}`
        ? {
              type: Function;
              value: (event: Props[Prop]) => any;
          }
        : {
              type: Function;
              value: Props[Prop];
          };
};

export type SyntheticMetaProps<Meta> = {
    [Prop in keyof Meta]?: Prop extends `on${string}`
        ? (event: Meta[Prop]) => any
        : Meta[Prop];
};

export type Host<Meta> = {};

export type ComponentOptions = {
    props?: SchemaProps;
    styles?: Sheets;
    base?: CustomElementConstructor;
};

export function c<
    Component extends (props: Props<Options["props"]>) => Host<any>,
    Options extends ComponentOptions
>(
    fn: Component,
    options: Options
): CreateElement<
    Component & {
        props: Options["props"];
        sheets: Options["styles"];
    },
    Options extends {
        base: infer BaseElement;
    }
        ? BaseElement
        : typeof HTMLElement
>;

export function c<
    FnComponent extends Component | MetaComponent,
    BaseElement extends typeof HTMLElement
>(
    component: FnComponent,
    baseElement?: BaseElement
): CreateElement<FnComponent, BaseElement>;
