// retoor <retoor@molodetz.nl>

const KEYWORDS = new Set([
  "const", "let", "var", "function", "return", "if", "else", "for", "while", "do",
  "switch", "case", "break", "continue", "class", "extends", "new", "import", "from",
  "export", "default", "async", "await", "try", "catch", "finally", "throw", "typeof",
  "instanceof", "in", "of", "this", "super", "yield", "static", "get", "set", "void",
  "delete", "def", "elif", "lambda", "pass", "with", "as", "global", "nonlocal",
  "assert", "raise", "except", "and", "or", "not", "is", "self", "match", "use", "pub",
  "impl", "fn", "struct", "enum", "trait", "mut", "func", "package", "interface", "type",
  "public", "private", "protected", "echo", "then", "fi", "done", "esac",
]);

const LITERALS = new Set([
  "true", "false", "null", "undefined", "None", "True", "False", "nil", "NaN", "Infinity",
]);

const DQUOTE = String.fromCharCode(34);
const SQUOTE = String.fromCharCode(39);
const BTICK = String.fromCharCode(96);
const BSLASH = String.fromCharCode(92);
const ID_START = /[A-Za-z_$]/;
const ID_PART = /[A-Za-z0-9_$]/;
const DIGIT = /[0-9]/;
const NUM_PART = /[0-9_.]/;

export default class Highlighter {
  escape(text) {
    return text
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .split(String.fromCharCode(34))
      .join("&quot;");
  }

  _span(kind, text) {
    return '<span class="tok-' + kind + '">' + this.escape(text) + "</span>";
  }

  highlight(code) {
    const s = String(code);
    const n = s.length;
    let out = "";
    let i = 0;
    while (i < n) {
      const c = s[i];
      if (c === "/" && s[i + 1] === "/") {
        let j = i + 2;
        while (j < n && s[j] !== "\n") j += 1;
        out += this._span("com", s.slice(i, j));
        i = j;
      } else if (c === "#") {
        let j = i + 1;
        while (j < n && s[j] !== "\n") j += 1;
        out += this._span("com", s.slice(i, j));
        i = j;
      } else if (c === "/" && s[i + 1] === "*") {
        let j = i + 2;
        while (j < n && !(s[j] === "*" && s[j + 1] === "/")) j += 1;
        j = Math.min(n, j + 2);
        out += this._span("com", s.slice(i, j));
        i = j;
      } else if (c === DQUOTE || c === SQUOTE || c === BTICK) {
        const quote = c;
        let j = i + 1;
        while (j < n) {
          if (s[j] === BSLASH) {
            j += 2;
          } else if (s[j] === quote) {
            j += 1;
            break;
          } else {
            j += 1;
          }
        }
        out += this._span("str", s.slice(i, j));
        i = j;
      } else if (DIGIT.test(c)) {
        let j = i + 1;
        while (j < n && NUM_PART.test(s[j])) j += 1;
        if (s[j] === "e" || s[j] === "E") {
          j += 1;
          if (s[j] === "+" || s[j] === "-") j += 1;
          while (j < n && DIGIT.test(s[j])) j += 1;
        }
        out += this._span("num", s.slice(i, j));
        i = j;
      } else if (ID_START.test(c)) {
        let j = i + 1;
        while (j < n && ID_PART.test(s[j])) j += 1;
        const word = s.slice(i, j);
        if (KEYWORDS.has(word)) {
          out += this._span("kw", word);
        } else if (LITERALS.has(word)) {
          out += this._span("lit", word);
        } else {
          out += this.escape(word);
        }
        i = j;
      } else {
        out += this.escape(c);
        i += 1;
      }
    }
    return out;
  }
}
