const fs = require("fs");
const Terser = require("terser");
const glob = require("glob");
const path = require("path");
const build_config = require("../build_config.js");

const packageJSON = require("../../package.json");
const REQUIRES_REGEX = /\/\*.*?Requires: (.*?)\r?\n/s;
const CATEGORY_REGEX = /\/\*.*?Category: (.*?)\r?\n/s;
const LANGUAGE_REGEX = /\/\*.*?Language: (.*?)\r?\n/s;
const { rollupCode } = require("./bundling.js");
const { getThirdPartyPackages } = require("./external_language.js");

class Language {
  constructor(name, path) {
    this.name = name;
    this.prettyName = name;
    this.requires = [];
    this.categories = [];
    this.third_party = false;

    // compiled code
    this.module = "";
    this.minified = "";

    this.path = path;
    this.data = fs.readFileSync(path, { encoding: "utf8" });
    this.loadMetadata();
  }

  async compile(options) {
    await compileLanguage(this, options);
    return this;
  }

  get sample() {
    if (this._sample) return this._sample;

    this._sample = "";
    if (fs.existsSync(this.samplePath)) { this._sample = fs.readFileSync(this.samplePath, { encoding: "utf8" }); }
    return this._sample;
  }

  get samplePath() {
    if (this.moduleDir) {
      // this is the 'extras' case.
      return `${this.moduleDir}/test/detect/${this.name}/default.txt`;
    } else {
      // this is the common/built-in case.
      return `./test/detect/${this.name}/default.txt`;
    }
  }

  loadMetadata() {
    const requiresMatch = REQUIRES_REGEX.exec(this.data);
    const categoryMatch = CATEGORY_REGEX.exec(this.data);
    const languageMatch = LANGUAGE_REGEX.exec(this.data);

    if (requiresMatch) {
      this.requires = requiresMatch[1]
        .split(", ")
        .map((n) => n.replace(".js", ""))
        .filter(n => n.trim() !== "");
    }

    if (categoryMatch) { this.categories = categoryMatch[1].split(/,\s?/); }

    if (languageMatch) { this.prettyName = languageMatch[1]; }
  }

  static fromFile(filename) {
    return new Language(
      path.basename(filename).replace(".js", ""),
      filename
    );
  }
}


async function compileLanguage(language, options) {
  const HEADER = `/*! \`${language.name}\` grammar compiled for Highlight.js ${packageJSON.version} */`;

  // TODO: cant we use the source we already have?
  const input = { ...build_config.rollup.browser_iife.input, input: language.path };
  const output = { ...build_config.rollup.browser_iife.output, name: `hljsGrammar`, file: "out.js" };
  output.footer = null;

  const data = await rollupCode(input, output);
  const iife = `
  ${HEADER}
  (function(){
    ${data}
    hljs.registerLanguage('${language.name}', hljsGrammar);
  })();
  `.trim();
  const esm = `${HEADER}\n${data};\nexport default hljsGrammar;`;

  language.module = iife;
  language.esm = esm;
  const miniESM = await Terser.minify(esm, options.terser);
  const miniIIFE = await Terser.minify(iife, options.terser);
  language.minified = miniIIFE.code;
  language.minifiedESM = miniESM.code;
}

async function getLanguages() {
  const languages = [];
  glob.sync("./src/languages/*.js").forEach((file) => {
    languages.push(Language.fromFile(file));
  });
  const extraPackages = await getThirdPartyPackages();
  for (const ext of extraPackages) {
    for (const file of ext.files) {
      const l = Language.fromFile(file);
      l.loader = ext.loader;
      l.third_party = true;
      l.moduleDir = ext.dir;
      languages.push(l);
    }
  }
  return languages;
}

module.exports = { Language, getLanguages };
