import { Pattern } from "../../types";
import pathToRegexp, { Key } from "path-to-regexp";

/**
 * Extract the parameters of the `path-to-regexp` pattern from the link.
 *
 * @param link - The link that was matched.
 * @param regexp - The regexp from `path-to-regexp` that will be used.
 * @param keys - The array with keys generated by `path-to-regexp`.
 *
 * @returns An object with the parameter names and its corresponding values
 * from the link.
 */
const extractParameters = (
  link: string,
  regexp: RegExp,
  keys: Key[]
): Record<string, string> =>
  link
    // First remove the query and hash part.
    // These parameters are extracted from the pathname.
    .replace(/\?.*/, "")
    .match(regexp)
    .slice(1)
    .reduce((result, value, index) => {
      result[keys[index].name] = value;
      return result;
    }, {});

/**
 * The return object for the {@link getMatch} function.
 */
interface GetMatchReturn<
  Func extends (...args: any) => any,
  Patt extends Pattern<Func>
> {
  /**
   * The params extracted from the `path-to-regexp` pattern or the named capture
   * groups of the regular expresion.
   */
  params: Record<string, string>;

  /**
   * The handler/redirection function that was matched.
   */
  func: Patt["func"];

  /**
   * The name of the handler/redirection function that was matched.
   */
  name: string;

  /**
   * The pattern of the handler/redirection function that was matched.
   */
  pattern: string;
}

/**
 * The `source` object that contains a link and a route.
 */
interface Source {
  /**
   * The full link with `queryString`.
   */
  link?: string;

  /**
   * The link without the query and pagination part.
   */
  route?: string;
}

/**
 * The regular expression flag.
 */
const REGULAR_EXPRESSION = "RegExp:";

/**
 * Match a link with a list of handler/redirection objects.
 *
 * @param source - The source to be matched.
 * @param list - The list of handler/redirection objects.
 *
 * @returns An object containing the matched handler/redirection. Defined in
 * {@link GetMatchReturn}.
 */
export const getMatch = <
  Func extends (...args: any) => any,
  Patt extends Pattern<Func>
>(
  source: Source,
  list: Patt[]
): GetMatchReturn<Func, Patt> | null => {
  const result = list
    .sort(({ priority: p1 }, { priority: p2 }) => p1 - p2)
    .map(({ name, priority, pattern, func }) => {
      let url: string;
      const keys = [];

      // Otherwise, we need to pick the value based on the pattern
      if (pattern.startsWith(REGULAR_EXPRESSION)) {
        url = source.link;
      } else {
        url = source.route;
      }

      const regexp = pattern.startsWith(REGULAR_EXPRESSION)
        ? new RegExp(pattern.replace(REGULAR_EXPRESSION, ""))
        : pathToRegexp(pattern, keys);
      return { name, priority, pattern, regexp, keys, url, func };
    })
    .find(({ regexp, url }) => regexp.test(url));

  return result
    ? {
        func: result.func,
        params: result.pattern.startsWith(REGULAR_EXPRESSION)
          ? result.regexp.exec(result.url).groups
          : extractParameters(result.url, result.regexp, result.keys),
        name: result.name,
        pattern: result.pattern,
      }
    : null;
};
