/**
 * Add code branches covering the following invalid cases:
 * - No method was provided.
 * - An unsupported method was provided.
 * - A valid method was provided, but a path is missing. 
 * Feel free to use the following types:
 */
type NoMethodError = "Method missing.";
type UnknownMethodError<M extends string> = `'${M}' isn't a supported method.`
type PathMissingError<T extends string> = `A path is missing after '${T}'.`;
 
type NoSpacesError = "Spaces aren't allowed in route paths.";
type InvalidPathError = "This path is invalid.";
 
type Method = "GET" | "POST" | "PUT" | "PATCH";
type NoPathError<M extends string> = `A path is missing after '${M}'.`
type InvalidMethodError<M extends string> = `'${M}' isn't a supported method.`
 
type ValidateRoute<Path> =
    Path extends `${Method} ${string} ${string}` ? NoSpacesError
  : Path extends `${infer M} ` ? NoPathError<M>
  : Path extends `/${string}` ? "Method missing."
  : Path extends `${Method} ${string}` ? Path
  : Path extends `${infer M} ${string}` ? InvalidMethodError<M>
  : InvalidPathError;
 
type res1 = ValidateRoute<"GET ">;
type test1 = Expect<Equal<res1, "A path is missing after 'GET'.">>;
 
type res2 = ValidateRoute<"POST ">;
type test2 = Expect<Equal<res2, "A path is missing after 'POST'.">>;
 
type res3 = ValidateRoute<"/no/method">;
type test3 = Expect<Equal<res3, "Method missing.">>;
 
type res4 = ValidateRoute<"OOPS /unknown-method">;
type test4 = Expect<Equal<res4, "'OOPS' isn't a supported method.">>;
 
type res5 = ValidateRoute<"GET /spaces spaces">;
type test5 = Expect<Equal<res5, NoSpacesError>>;
 
type res6 = ValidateRoute<"GET /valid-route">;
type test6 = Expect<Equal<res6, "GET /valid-route">>;
 
export {};
/**  
 *  Make the `body` parameter type-safe!
 *  Given the following predicates:
 */
declare const isComment: (x: unknown) => x is Comment;
declare const isVideo: (x: unknown) => x is Video;
 
/* `body` should be correctly narrowed:  */
route("POST /comments")
  .validateBody(isComment)
  .handle(({ body }) => {
    type test = Expect<Equal<typeof body, Comment>>;
    return "✅";
  });
 
route("PUT /video/:id")
  .validateBody(isVideo)
  .handle(({ body }) => {
    type test = Expect<Equal<typeof body, Video>>;
    return "✅";
  });
 
/**  
 * Modify the `route` function and its methods
 * to keep track of the type of `body`! 👇
 */
 
type RouteStateConstraint = {
  method: Method;
  isValid: boolean;
  body: unknown;
};
 
type InitialRouteState<P> = {
  method: GetMethod<P>;
  isValid: GetMethod<P> extends "GET" ? true : false;
  body: GetMethod<P> extends "GET" ? never : unknown;
};
 
declare function route<const P extends string>(
  path: P,
): Route<InitialRouteState<P>>;
 
/* 
   We have extracted the types of `handle` and `validateBody`
   to separate generic types for readability:              */
type Route<State extends RouteStateConstraint> = {
  isAuthenticated: () => Route<State>;
  isAdmin: () => Route<State>;
  
  validateBody: ValidateBodyMethod<State>;
  handle: HandleMethod<State>;
};
 
type ValidateBodyMethod<State extends RouteStateConstraint> =
  State["method"] extends "GET"
    ? GETBodyError
    : <Body>(
        isValid: (body: unknown) => body is Body,
      ) => Route<Assign<State, { isValid: true; body: Body }>>;
 
type ParsedRequest<B> = {
  cookies: Record<string, string>;
  params: Record<string, string>;
  body: B;
};
 
type HandleMethod<State extends RouteStateConstraint> =
  State["isValid"] extends false
    ? UnvalidatedBodyError<State["method"]>
    : (fn: (req: ParsedRequest<State["body"]>) => string | Promise<string>) => Route<State>;
 
/**
 * Helper types and functions
 */
 
type Comment = { content: string, author: string };
 
type Video = { src: string, title: string };
 
type Method = "GET" | "POST" | "PUT" | "PATCH";
 
type GetMethod<P> = P extends `${infer M extends Method} ${string}` ? M : never;
 
type Assign<A, B> = Compute<Omit<A, keyof B> & B>;
 
type GETBodyError = ".validateBody(...) isn't available on GET routes.";
 
type UnvalidatedBodyError<M extends Method> =
  `You must call .validateBody(...) before .handle(...) on ${M} routes.`;
 
export {};
/**  
 *  Make sure `isAuthenticated()` and  `isAdmin()`
 *  are always used correctly!
 */
 
route("GET /tricks/:trickId/details")
  .isAuthenticated()
  // @ts-expect-error ❌ already authenticated.
  .isAuthenticated()
 
route("GET /admin/dashboard")
  // @ts-expect-error ❌ not authenticated.
  .isAdmin()
 
route("GET /admin/dashboard")
  .isAuthenticated()
  .isAdmin()
  // @ts-expect-error ❌ already admin. 
  .isAdmin()
 
route("GET /admin/dashboard")
  .isAuthenticated()
  .isAdmin()
  .handle(() => `📈`); // ✅
  
 
/**  
 * Modify the `route` function and its methods 👇
 */
 
// Use these error messages
type SeveralAuthError = ".isAuthenticated() can only be called once."
type SeveralAdminError = ".isAdmin() can only be called once."
type UnauthenticatedAdminError = "You must call .isAuthenticated() before .isAdmin()."
 
type RouteStateConstraint = {
  method: Method;
  isValid: boolean;
  body: unknown;
  isAuthenticated: boolean;
  isAdminCalled: boolean;
};
 
type InitialRouteState<Path, Body> = {
  method: GetMethod<Path>;
  isValid: GetMethod<Path> extends "GET" ? true : false;
  body: Body;
  isAuthenticated: false;
  isAdminCalled: false;
};
 
declare function route<const Path extends string>(
  path: Path,
): Route<InitialRouteState<Path, unknown>>;
 
type Route<State extends RouteStateConstraint> = {
  isAuthenticated: IsAuthenticatedMethod<State>;
  isAdmin: IsAdminMethod<State>;
  validateBody: ValidateBodyMethod<State>;
  handle: HandleMethod<State>;
};
 
type IsAuthenticatedMethod<State extends RouteStateConstraint> =
   State["isAuthenticated"] extends true
    ? SeveralAuthError
    : () => Route<Assign<State, {isAuthenticated: true}>>
 
 
type IsAdminMethod<State extends RouteStateConstraint> =
  State["isAuthenticated"] extends false
    ? UnauthenticatedAdminError
    : State["isAdminCalled"] extends true 
      ? SeveralAdminError
      : () => Route<Assign<State, {isAdminCalled: true}>>
 
 
/**
 * Helper types and functions
 */
 
type ValidateBodyMethod<State extends RouteStateConstraint> =
  State["method"] extends "GET"
    ? GETBodyError
    : <Body>(
        isValid: (body: unknown) => body is Body,
      ) => Route<Assign<State, { isValid: true; body: Body }>>;
 
 
type ParsedRequest<Body> = {
  cookies: Record<string, string>;
  params: Record<string, string>;
  body: Body;
};
 
type HandleMethod<State extends RouteStateConstraint> =
  State["isValid"] extends false
    ? UnvalidatedBodyError<State["method"]>
    : (
        fn: (req: ParsedRequest<State["body"]>) => string | Promise<string>,
      ) => Route<State>;
 
type Method = "GET" | "POST" | "PUT" | "PATCH";
 
type GetMethod<P> = P extends `${infer M extends Method} ${string}` ? M : never;
 
type Assign<A, B> = Compute<Omit<A, keyof B> & B>;
 
type GETBodyError = ".validateBody(...) isn't available on GET routes.";
 
type UnvalidatedBodyError<M extends Method> =
  `You must call .validateBody(...) before .handle(...) on ${M} routes.`;
 
export {};
/**
 * Implement `IsShadowing`:
 */
export type IsShadowing<Path1, Path2> = Path1 extends Path2
  ? true
  : Path1 extends `${infer A}:${string}`
    ? Path2 extends `${A}:${string}`
      ? true
      : false
    : false;
 
/**
 * Does shadow:
 */
type res1 = IsShadowing<"GET /", "GET /">;
type test1 = Expect<Equal<res1, true>>;
 
type res2 = IsShadowing<"GET /user/:name", "GET /user/:username">;
type test2 = Expect<Equal<res2, true>>;
 
type res3 = IsShadowing<"GET /user/:name", "GET /user/:name/profile">;
type test3 = Expect<Equal<res3, true>>;
 
/**
 * Does not shadow:
 */
type res4 = IsShadowing<"GET /", "GET /users">;
type test4 = Expect<Equal<res4, false>>;
 
type res5 = IsShadowing<"GET /trick/:trickId", "GET /">;
type test5 = Expect<Equal<res5, false>>;
 
type res6 = IsShadowing<"GET /trick/:trickId", "PUT /trick/:trickId">;
type test6 = Expect<Equal<res6, false>>;
 

마지막 문제는 깔끔하게 포기. 이런게 가능하다는 것 까지만 알고.. 나중에 다시 보자.