import {
  compile,
  PathFunction,
  parse,
  pathToRegexp,
  Token,
  ParamData,
} from './pathToRegexp.js';
import { ShortenPath } from './pathTypes.js';

const urlBaseCache: Record<string, PathFunction<object>> = Object.create(null);
export function getUrlBase(path: string): PathFunction<ParamData> {
  if (!(path in urlBaseCache)) {
    urlBaseCache[path] = compile(path);
  }
  return urlBaseCache[path];
}

const urlTokensCache: Record<string, Set<string>> = Object.create(null);
export function getUrlTokens(path: string): Set<string> {
  if (!(path in urlTokensCache)) {
    urlTokensCache[path] = tokenMap(parse(path).tokens);
  }
  return urlTokensCache[path];
}

const pathRegexCache: Record<string, RegExp> = Object.create(null);
export function getPathRegex(path: string): RegExp {
  if (!(path in pathRegexCache)) {
    pathRegexCache[path] = pathToRegexp(path).regexp;
  }
  return pathRegexCache[path];
}

function tokenMap(tokens: Token[]): Set<string> {
  const tokenNames = new Set<string>();
  for (const token of tokens) {
    switch (token.type) {
      case 'param':
      case 'wildcard':
        tokenNames.add(token.name);
        break;
      case 'group':
        for (const name of tokenMap(token.tokens)) {
          tokenNames.add(name);
        }
        break;
    }
  }
  return tokenNames;
}

const proto = Object.prototype;
const gpo = Object.getPrototypeOf;

export function isPojo(obj: unknown): obj is Record<string, any> {
  if (obj === null || typeof obj !== 'object') {
    return false;
  }
  return gpo(obj) === proto;
}

export function shortenPath<S extends string>(path: S): ShortenPath<S> {
  const lastTokenIndex = Math.max(path.lastIndexOf(':'), path.lastIndexOf('*'));
  if (lastTokenIndex === -1)
    throw new Error(
      'Resource path requires at least one :parameter or *wildcard',
    );
  let shortUrlRoot: ShortenPath<S> = path.substring(0, lastTokenIndex) as any;
  if (shortUrlRoot[shortUrlRoot.length - 1] === '/')
    shortUrlRoot = shortUrlRoot.substring(
      0,
      shortUrlRoot.length - 1,
    ) as ShortenPath<S>;
  return shortUrlRoot;
}
