type CleanKey<S extends string> = S extends `"${infer K}"` ? K : S;

type KeyName<K extends string> = CleanKey<
  K extends `*${infer N}}` ? N
  : K extends `*${infer N}` ? N
  : K extends `${infer N}}` ? N
  : K
>;

type KeyVal<K extends string> =
  K extends `*${string}` ? string[] : string | number;

/** Parameters for a given path */
export type PathArgs<S extends string> =
  unknown extends S ? any
  : PathKeys<S> extends never ?
    // unknown is identity for intersection ('&')
    unknown
  : KeysToArgs<PathKeys<S>>;

/** Like {@link PathArgs} but widened `path: string` collapses to `unknown`,
 *  preventing `(params, body) | (body)` union overloads in ParamFetchWithBody. */
export type SoftPathArgs<P extends string> =
  unknown extends P ? any
  : string extends P ? unknown
  : PathArgs<P>;

/** Computes the union of keys for a path string */
export type PathKeys<S extends string> =
  string extends S ? string
  : S extends `${infer A}\\${':' | '*' | '}'}${infer B}` ?
    PathKeys<A> | PathKeys<B>
  : Splits<S, ':'> | Splits<S, '*'>;

type Splits<S extends string, M extends ':' | '*'> =
  S extends `${string}${M}${infer K}${M}${infer R}` ?
    Splits<`${M}${K}`, M> | Splits<`${M}${R}`, M>
  : S extends (
    `${string}${M}${infer K}${'/' | '\\' | '%' | '&' | '*' | ':' | '{' | ';' | ',' | '!' | '@'}${infer R}`
  ) ?
    Splits<`${M}${K}`, M> | Splits<R, M>
  : S extends `${string}${M}${infer K}` ?
    M extends '*' ?
      `*${K}`
    : K
  : never;

export type KeysToArgs<Key extends string> = {
  [K in Key as K extends `${string}}` ? KeyName<K> : never]?: KeyVal<K>;
} & (Exclude<Key, `${string}}`> extends never ? unknown
: {
    [K in Key as K extends `${string}}` ? never : KeyName<K>]: KeyVal<K>;
  });

export type PathArgsAndSearch<S extends string> =
  unknown extends S ? any
  : Exclude<PathKeys<S>, `${string}}`> extends never ?
    Record<string, number | string | boolean> | undefined
  : {
      [K in PathKeys<S> as K extends `${string}}` ? never
      : KeyName<K>]: KeyVal<K>;
    } & Record<string, number | string | string[]>;

/** Removes the last :param or *wildcard token */
export type ShortenPath<S extends string> =
  string extends S ? string
  : S extends `${infer B}:${infer R}` ? TrimToken<`${B}:${ShortenPath<R>}`>
  : S extends `${infer B}*${infer R}` ? TrimToken<`${B}*${ShortenPath<R>}`>
  : '';

type TrimToken<S extends string> =
  string extends S ? string
  : S extends `${infer R}:` ? R
  : S extends `${infer R}*` ? R
  : S;

export type ResourcePath = string; // `${string}:${string}`; TODO: Maybe do this in the future? Seems to hard to understand for now
