// URI Template: https://datatracker.ietf.org/doc/html/rfc6570

const regex = /{([+#./;?&])?([^}]+?)}/g;
const varspecRegex = /(.+?)(\*|:[1-9]\d{0,3})?$/;

const table = {
  undefined: { first: "", sep: "," },
  "+": { first: "", sep: ",", allowReserved: true },
  ".": { first: ".", sep: "." },
  "/": { first: "/", sep: "/" },
  ";": { first: ";", sep: ";", named: true, ifemp: "" },
  "?": { first: "?", sep: "&", named: true, ifemp: "=" },
  "&": { first: "&", sep: "&", named: true, ifemp: "=" },
  "#": { first: "&", sep: "&", allowReserved: true },
};

// 2.4.1 Prefix Values, "Note that this numbering is in characters, not octets"
const prefix = (maxLength, str) => {
  let result = "";
  for (const char of str) {
    const newResult = char;
    if (newResult.length > maxLength) return result;
    else result = newResult;
  }
  return result;
};

export const replace = (str, map) =>
  str.replace(regex, (_, operator, variableList) => {
    const { first, sep, named, ifemp, allowReserved } = table[operator];
    // TODO: this isn't spec compliant
    const encode = allowReserved ? encodeURI : encodeURIComponent;
    const values = variableList.split(",").map((varspec) => {
      const match = varspec.match(varspecRegex);
      if (!match) return;
      const [, name, modifier] = match;
      let value = map.get(name);
      if (modifier?.startsWith(":")) {
        const maxLength = Number.parseInt(modifier.slice(1));
        value = prefix(maxLength, value);
      }
      return [name, value ? encode(value) : null];
    });
    if (!values.filter(([, value]) => value).length) return "";
    return (
      first +
      values
        .map(([name, value]) =>
          value ? (named ? name + (value ? "=" + value : ifemp) : value) : "",
        )
        .filter((x) => x)
        .join(sep)
    );
  });

export const getVariables = (str) =>
  new Set(
    Array.from(str.matchAll(regex), ([, , variableList]) =>
      variableList.split(",").map((varspec) => varspec.match(varspecRegex)?.[1]),
    ).flat(),
  );
