type Partial<T> = {
    [P in keyof T]?: T[P];
};

type AppConfig = {
  compiledUserContentDir: string;
  missingRefValueReplacement: string,
  mdBlogPostsDir: string;
  packageName: string;
  previewLength: number;
  staticFilesDir: string;
  pagesDir: string;
  viewTemplatesDir: string;
  userCustomConfigPath: string;
  userProjectRootDir: string;
};

type AppConfigFactoryParams = AppConfig & {
  userCustomConfig: Partial<AppConfig>;
};

type RequireOnlyOne<T, Keys extends keyof T = keyof T> =
  Pick<T, Exclude<keyof T, Keys>>
  & {
      [K in Keys]-?:
          Required<Pick<T, K>>
          & Partial<Record<Exclude<Keys, K>, undefined>>
  }[Keys] 

// Internal Placeholders
type ActionParamsDefaultRetType = {};
type RouteConfigDefaultAdditionalPropsType = {};
type RouteUserConfigDefaultAdditionalPropsType = {};
type RouteCtorConfigDefaultAdditionalPropsType = {};

type GetControllerActionParamsMethod<T={}> =
  (props: { request: HttpRequest; }) => T; 

type RouteInterface<A=undefined, U={}> = U & CanResolveRouteInterface & {
  controller: RouteControllerCtorConfig<A>;
  getControllerActionParams?: GetControllerActionParamsMethod
};

// We need two things: 
// 1. A config generated by user passed to DI to create the route
//     > ...UserConfig
// 2. A config used by Di to pass to the route constructor
//     > ...CtorConfig 
// We need two more things: 
// a. Path
// b. RegExp
type DiNamedConfig = { name: string; };

type AsInstanceConfig<I> = { instance: I; };

type RouteControllerTypeBothConfig =
  DiNamedConfig | AsInstanceConfig<ControllerInterface>;

type ActionParamsObjectType<A> = {
  actionParams?: A;
  actionParamsGetter?: (props: { request: HttpRequest; }) => A;
}

type RouteControllerWithActionParamsConfig<A=ActionParamsDefaultRetType> =
  RequireOnlyOne<ActionParamsObjectType<A>, 'actionParams' | 'actionParamsGetter'>

type RouteControllerGenericConfig<T extends RouteControllerTypeBothConfig, A=ActionParamsDefaultRetType> =
  T & RouteControllerWithActionParamsConfig<A>;

type RouteControllerUserConfig<A=ActionParamsDefaultRetType> =
  RouteControllerGenericConfig<RouteControllerTypeBothConfig, A>;

type RouteControllerCtorConfig<A=ActionParamsDefaultRetType> =
  RouteControllerGenericConfig<AsInstanceConfig<ControllerInterface>, A>;

// 1. ...UserConfig
type RouteUserConfig<E=RouteUserConfigDefaultAdditionalPropsType, A=ActionParamsDefaultRetType> =
  AsInstanceConfig<RouteInterface<A>> | (E & DiNamedConfig & {
    controller: RouteControllerUserConfig<A>;
  });
// 2. ...CtorConfig
type RouteCtorConfig<E=RouteCtorConfigDefaultAdditionalPropsType, A=ActionParamsDefaultRetType> =
  E & {
    controller: RouteControllerCtorConfig<A>;
  };
// Our 4 things
// F : Further props passed to route First level config
// A : ActionParams object
type RouteMatchPathUserConfig<F=RouteConfigDefaultAdditionalPropsType, A=ActionParamsDefaultRetType> =
  RouteUserConfig<F & { path: string; }, A>;
type RouteMatchRegExpUserConfig<F=RouteConfigDefaultAdditionalPropsType, A=ActionParamsDefaultRetType> =
  RouteUserConfig<F & { pathRegExp: RegExp; }, A>;
type RouteMatchPathCtorConfig<F=RouteConfigDefaultAdditionalPropsType, A=ActionParamsDefaultRetType> =
  RouteCtorConfig<F & { paths: string[]; }, A>;
type RouteMatchRegExpCtorConfig<F=RouteConfigDefaultAdditionalPropsType, A=ActionParamsDefaultRetType> =
  RouteCtorConfig<F & { pathRegExp: RegExp; }, A>;


type ValidPostSlugListGetterConf = { validPostSlugListGetter: () => string[]; }
type StaticFilePathListGetterConf = { staticFilePathListGetter: () => string[]; }

interface HttpRequest {
  url: string;
  method: 'GET' | 'POST';
}

interface IsValidRequestInterface {
  isValid: (request: HttpRequest) => boolean;
}
interface MatchesRequestRegExpInterface {
  matches: (request: HttpRequest, pathRegExp: RegExp) => boolean;
}
interface MatchesRequestPathInterface {
  matches: (request: HttpRequest, path: string) => boolean;
}

interface CanResolveRouteInterface {
  canResolve: (request: HttpRequest) => boolean;
}

type RegExpRouteSpecConfig<C=any> =
  RouteMatchRegExpConfig & MaybeDiControllerConfig<C>;

type RegExpRouteConstructorConfig<C=any> =
  RouteMatchRegExpConfig & RouteConstructorControllerConfig<C>;

interface RegExpRouteValidatorConstructorInterface<T> {
  new (config: RegExpRouteConstructorConfig): RouteInterface<T>;
}

type PathRouteSpecConfig<C=any> =
  RouteMatchPathConfig & MaybeDiControllerConfig<C>;

type PathRouteConstructorConfig<C=any> =
  RouteMatchPathConfig & RouteConstructorControllerConfig<C>;


interface PathRouteValidatorConstructorInterface<T> {
  new (config: PathRouteConstructorConfig): RouteInterface<T>;
}

interface ResponseInterface {
  code: number;
  headers: { [k: string]: string; };
  body: string;
}

interface ControllerConstructor {
  new (config: any): ControllerInterface;
}
interface ControllerInterface<A={}> {
  action: (params: A) => Promise<ResponseInterface>; 
}