import type { CSSProperties, VNodeChild, PropType } from 'vue';
import type { VueTypeValidableDef, VueTypesInterface } from 'vue-types';
import type { RouteComponent, RouteRecordRaw } from 'vue-router';
import { createTypes } from 'vue-types';

type VueNode = VNodeChild | JSX.Element;

const propTypes = createTypes({
  func: undefined,
  bool: undefined,
  string: undefined,
  number: undefined,
  array: undefined,
  object: undefined,
  integer: undefined,
});
//
// propTypes.extend([
//   {
//     name: 'looseBool',
//     getter: true,
//     type: Boolean,
//     default: undefined,
//   },
//   {
//     name: 'style',
//     getter: true,
//     type: [String, Object],
//     default: undefined,
//   },
//   {
//     name: 'VNodeChild',
//     getter: true,
//     type: undefined,
//   },
// ]);
//
//
function withUndefined<T extends { default?: any }>(type: T): T {
  type.default = undefined;
  return type;
}
const PropTypes = propTypes as VueTypesInterface & {
  readonly looseBool: VueTypeValidableDef<boolean>;
  readonly style: VueTypeValidableDef<CSSProperties>;
  readonly VNodeChild: VueTypeValidableDef<VueNode>;
};

// https://stackoverflow.com/questions/46176165/ways-to-get-string-literal-type-of-array-values-without-enum-overhead
export const tuple = <T extends string[]>(...args: T) => args;

export const tupleNum = <T extends number[]>(...args: T) => args;

/**
 * https://stackoverflow.com/a/59187769
 * Extract the type of an element of an array/tuple without performing indexing
 */
export type ElementOf<T> = T extends (infer E)[] ? E : T extends readonly (infer F)[] ? F : never;

/**
 * https://github.com/Microsoft/TypeScript/issues/29729
 */
export type LiteralUnion<T extends U, U> = T | (U & {});

type DefaultFactory<T> = (props: Data) => T | null | undefined;

export interface PropOptions<T = any, D = T> {
  type?: PropType<T> | true | null;
  required?: boolean;
  default?: D | DefaultFactory<D> | null | undefined | object;
  validator?(value: unknown): boolean;
}

interface RouteMeta {
  title: string; // 标题
  icon?: string; // 图标
  requiresAuth?: boolean;
  hideChildren?: boolean; // 是否不显示孩子菜单
}
declare type Lazy<T> = () => Promise<T>;

interface NsRouteRecordRaw extends Omit<Omit<RouteRecordRaw, 'children'>, 'meta'> {
  name: string;
  meta: RouteMeta;
  component?: Lazy<RouteComponent>;
  children?: NsRouteRecordRaw[];
  props?: Recordable;
}

export { VueNode, PropTypes, NsRouteRecordRaw, withUndefined };