// Generated by ReScript, PLEASE EDIT WITH CARE

import * as Fs from "fs";
import * as Path from "path";
import * as Js_exn from "rescript/lib/es6/js_exn.js";
import * as $$Crypto from "crypto";
import * as Process from "process";
import Chokidar from "chokidar";
import * as Readline from "readline";
import FastGlob from "fast-glob";
import * as Core__Array from "@rescript/core/src/Core__Array.mjs";
import * as Core__Option from "@rescript/core/src/Core__Option.mjs";
import * as RescriptCore from "@rescript/core/src/RescriptCore.mjs";
import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js";

var hashContents = (function(createHash, contents) {
    return createHash("md5").update(contents).digest("hex")
  });

function make(pattern) {
  var tmp;
  tmp = pattern.TAG === "Generic" ? new RegExp("(?<!\s*\/\/.*)%generated\\." + pattern._0 + "\\(`([^`]+)`\\)", "g") : new RegExp("(?<!\s*\/\/.*)%" + pattern._0 + "\\(`([^`]+)`\\)", "g");
  return {
          pattern: pattern,
          regExp: tmp
        };
}

function getExtensionName(t) {
  return t.pattern._0;
}

function getFullExtension(t) {
  var extension = t.pattern;
  if (extension.TAG === "Generic") {
    return "%generated." + extension._0;
  } else {
    return "%" + extension._0;
  }
}

function toGeneratedFileName(t, fileName, extensionOpt) {
  var extension = extensionOpt !== undefined ? extensionOpt : "res";
  var fileName$1 = Path.basename(fileName, ".res");
  return fileName$1 + "__" + getExtensionName(t) + "." + extension;
}

function hasArg(args, name) {
  return args.includes(name);
}

function getArgValue(args, names) {
  var argIndex = Core__Array.findIndexOpt(args, (function (item) {
          return names.includes(item);
        }));
  if (argIndex === undefined) {
    return ;
  }
  var maybeArgValue = args[argIndex + 1 | 0];
  if (maybeArgValue !== undefined && !maybeArgValue.startsWith("--")) {
    return maybeArgValue;
  }
  
}

var CliArgs = {
  hasArg: hasArg,
  getArgValue: getArgValue
};

async function defaultSetup(param) {
  
}

function make$1(extensionPattern, setup, generate, cliHelpText, handleOtherCommand, onWatch) {
  return {
          fileName: make(extensionPattern),
          setup: setup,
          generate: generate,
          cliHelpText: cliHelpText,
          handleOtherCommand: handleOtherCommand,
          onWatch: onWatch
        };
}

function filePathInGeneratedDir(t, filePath, outputDir) {
  return Path.join(outputDir, toGeneratedFileName(t, Path.basename(filePath, ".res"), undefined));
}

function getMatches(t, root, includeExtension) {
  return FastGlob.sync([includeExtension ? "**/*__" + getExtensionName(t.fileName) + ".res" : "**/*.res"], {
              cwd: root,
              absolute: true
            });
}

function extractEmbedsFromRescript(t, fileText) {
  var embeds = Array.from(fileText.matchAll(t.fileName.regExp)).map(function (match) {
        if (match.length !== 2) {
          return ;
        } else {
          return match[1];
        }
      });
  return Core__Array.keepSome(embeds);
}

function readFirstLine(filePath) {
  var readStream = Fs.createReadStream(filePath);
  var rl = Readline.createInterface({
        input: readStream,
        crlfDelay: Infinity
      });
  return new Promise((function (resolve, _reject) {
                rl.on("line", (function (line) {
                        rl.close(undefined);
                        readStream.destroy();
                        resolve({
                              TAG: "Ok",
                              _0: line
                            });
                      }));
                rl.on("error", (function (_err) {
                        resolve({
                              TAG: "Error",
                              _0: undefined
                            });
                      }));
              }));
}

async function getFileSourceHash(filePath) {
  var firstLine;
  try {
    firstLine = await readFirstLine(filePath);
  }
  catch (raw_exn){
    var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
    if (exn.RE_EXN_ID === Js_exn.$$Error) {
      return ;
    }
    throw exn;
  }
  if (firstLine.TAG === "Ok") {
    return firstLine._0.split("// @sourceHash ")[1];
  }
  
}

async function genereteFileForEmbeds(t, path, fileModulesWithContent, generatedFiles, config, outputDir, debug) {
  try {
    var fileText = Fs.readFileSync(path).toString();
    if (!fileText.includes(getFullExtension(t.fileName) + "(")) {
      return ;
    }
    fileModulesWithContent.add(Path.basename(path, ".res"));
    var embeds = extractEmbedsFromRescript(t, fileText);
    var generatedContent = await Promise.all(embeds.map(async function (content, index) {
              var extraFiles = {};
              var res;
              try {
                res = await t.generate({
                      config: config,
                      content: content,
                      emitExtraFile: (function (extension, content, moduleName) {
                          debug("[emit] Emitting extra file with extension ." + extension);
                          var fileName = Path.basename(path, ".res");
                          var moduleName$1 = Core__Option.getWithDefault(moduleName, "M" + (index + 1 | 0).toString());
                          var emittedFileName = fileName + "__" + getExtensionName(t.fileName) + "." + moduleName$1 + "." + extension;
                          extraFiles[emittedFileName] = content;
                          return {
                                  path: Path.resolve(outputDir, emittedFileName),
                                  fileName: emittedFileName
                                };
                        })
                    });
              }
              catch (raw_err){
                var err = Caml_js_exceptions.internalToOCamlException(raw_err);
                if (err.RE_EXN_ID === Js_exn.$$Error) {
                  console.error(err._1.message);
                  return ;
                }
                throw err;
              }
              if (res.TAG === "Ok") {
                var res$1 = res._0;
                var match;
                match = res$1.kind === "WithModuleName" ? [
                    res$1.content,
                    res$1.moduleName
                  ] : [
                    res$1.content,
                    "M" + (index + 1 | 0).toString()
                  ];
                var extraFiles$1 = Object.entries(extraFiles).map(function (param) {
                      var emittedFileName = param[0];
                      debug("[emit] Emitting file \"" + emittedFileName + "\"");
                      return [
                              emittedFileName,
                              param[1]
                            ];
                    });
                return [
                        match[1],
                        match[0],
                        extraFiles$1
                      ];
              }
              console.error(res._0);
            }));
    var content = generatedContent.map(function (c) {
            if (c !== undefined) {
              return "module " + c[0] + " = {\n  " + c[1].split("\n").join("\n  ") + "\n}";
            } else {
              return "";
            }
          }).join("\n\n");
    var extraFiles = Core__Array.filterMap(generatedContent, (function (c) {
              if (c !== undefined) {
                return c[2];
              }
              
            })).flat();
    var hash = hashContents($$Crypto.createHash, content);
    var pathInGeneratedDir = filePathInGeneratedDir(t.fileName, path, outputDir);
    var contents = "// @sourceHash " + hash + "\n\n" + content;
    var prettyPath = Path.basename(path);
    var sourceHash = await getFileSourceHash(pathInGeneratedDir);
    var shouldWriteFile = sourceHash !== undefined ? sourceHash !== hash : true;
    if (shouldWriteFile) {
      generatedFiles.push([
            prettyPath,
            extraFiles.map(function (param) {
                  return param[0];
                })
          ]);
      Fs.writeFileSync(pathInGeneratedDir, Buffer.from(contents));
    }
    extraFiles.forEach(function (param) {
          var pathInGeneratedDir = Path.join(outputDir, param[0]);
          var content = param[1];
          if (Fs.existsSync(pathInGeneratedDir)) {
            try {
              var currentContent = Fs.readFileSync(pathInGeneratedDir);
              if (currentContent.toString() !== content) {
                Fs.writeFileSync(pathInGeneratedDir, Buffer.from(content));
                return ;
              } else {
                return ;
              }
            }
            catch (raw_exn){
              var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
              if (exn.RE_EXN_ID === Js_exn.$$Error) {
                return ;
              }
              throw exn;
            }
          } else {
            Fs.writeFileSync(pathInGeneratedDir, Buffer.from(content));
            return ;
          }
        });
    return ;
  }
  catch (raw_e){
    var e = Caml_js_exceptions.internalToOCamlException(raw_e);
    if (e.RE_EXN_ID === Js_exn.$$Error) {
      console.log("\x1b[31mError in file\x1b[0m " + Path.basename(path) + ":");
      console.error(e._1);
      return ;
    }
    throw e;
  }
}

function cleanUpExtraFiles(t, outputDir, sourceFileModuleNameOpt, keepTheseOpt) {
  var sourceFileModuleName = sourceFileModuleNameOpt !== undefined ? sourceFileModuleNameOpt : "*";
  var keepThese = keepTheseOpt !== undefined ? keepTheseOpt : [];
  var extraFiles = FastGlob.sync(["**/" + sourceFileModuleName + "__" + getExtensionName(t.fileName) + ".*.*"], {
        cwd: outputDir,
        absolute: true
      });
  extraFiles.forEach(function (filePath) {
        if (!keepThese.includes(filePath) && Fs.existsSync(filePath)) {
          Fs.unlinkSync(filePath);
          return ;
        }
        
      });
}

async function runCli(t, args) {
  var debugging = {
    contents: false
  };
  var debug = function (msg) {
    if (debugging.contents) {
      console.debug(msg);
      return ;
    }
    
  };
  var args$1 = Core__Option.getWithDefault(args, Core__Array.keepSome(process.argv.slice(2)));
  debugging.contents = args$1.includes("--debug");
  var otherCommand = args$1[0];
  if (otherCommand !== undefined) {
    if (otherCommand === "generate") {
      var config = await t.setup({
            args: args$1
          });
      var watch = args$1.includes("--watch");
      var outputDir = getArgValue(args$1, ["--output"]);
      var pathToGeneratedDir;
      if (outputDir !== undefined) {
        var joined = Path.join(Process.cwd(), outputDir);
        pathToGeneratedDir = Path.resolve(joined);
      } else {
        pathToGeneratedDir = RescriptCore.panic("--output must be set. It controls into what directory all generated files are emitted.");
      }
      var src = getArgValue(args$1, ["--src"]);
      var src$1;
      if (src !== undefined) {
        var joined$1 = Path.join(Process.cwd(), src);
        src$1 = Path.resolve(joined$1);
      } else {
        src$1 = RescriptCore.panic("--src must be set. It controls where to look for source ReScript files.");
      }
      try {
        await Fs.promises.access(pathToGeneratedDir);
      }
      catch (raw_exn){
        var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
        if (exn.RE_EXN_ID === Js_exn.$$Error) {
          console.log("Output directory did not exist. Creating now...");
          await Fs.promises.mkdir(pathToGeneratedDir, {
                recursive: true
              });
        } else {
          throw exn;
        }
      }
      var runGeneration = async function (files) {
        var match = files !== undefined ? [
            files,
            []
          ] : [
            getMatches(t, src$1, false),
            getMatches(t, pathToGeneratedDir, true)
          ];
        var filesInOutputDir = match[1];
        var matches = match[0];
        var fileModulesWithContent = new Set();
        var generatedFiles = [];
        if (matches.length === 0) {
          console.log("No .res files found");
        } else {
          console.log("Generating files...");
          await Promise.all(matches.map(function (match) {
                    return genereteFileForEmbeds(t, match, fileModulesWithContent, generatedFiles, config, pathToGeneratedDir, debug);
                  }));
          if (generatedFiles.length === 0) {
            
          } else if (generatedFiles.length > 5) {
            console.log("Generated " + generatedFiles.length.toString() + " files.");
          } else {
            console.log("Generated:\n  " + generatedFiles.map(function (param) {
                        return param[0];
                      }).join("\n  "));
          }
          if (filesInOutputDir.length > 0) {
            var hasLoggedCleaningUnusedFiles = {
              contents: false
            };
            var logCleanUnusedFiles = function () {
              if (!hasLoggedCleaningUnusedFiles.contents) {
                console.log("Cleaning up unused files...");
              }
              console.time("Cleaning unused files");
              hasLoggedCleaningUnusedFiles.contents = true;
            };
            filesInOutputDir.forEach(function (filePath) {
                  var fileModuleName = Path.basename(filePath, "__" + getExtensionName(t.fileName) + ".res");
                  if (!fileModulesWithContent.has(fileModuleName)) {
                    console.log("Deleting unused file " + filePath + "...");
                    logCleanUnusedFiles(undefined);
                    Fs.unlinkSync(filePath);
                    return ;
                  }
                  
                });
            if (hasLoggedCleaningUnusedFiles.contents) {
              console.timeEnd("Cleaning unused files");
            }
            
          }
          
        }
        return generatedFiles;
      };
      var runGeneration$1 = async function (files) {
        console.time("Generated files in");
        var generatedFiles = await runGeneration(files);
        console.timeEnd("Generated files in");
        generatedFiles.forEach(function (param) {
              cleanUpExtraFiles(t, pathToGeneratedDir, Path.basename(param[0], ".res"), param[1].map(function (fileName) {
                        return Path.resolve(pathToGeneratedDir, fileName);
                      }));
            });
      };
      if (!watch) {
        return await runGeneration$1(undefined);
      }
      await runGeneration$1(undefined);
      console.log("Watching for changes in " + src$1 + "...");
      Chokidar.watch(src$1 + "/**/*.res", {
                  ignored: [
                    "**/node_modules",
                    pathToGeneratedDir
                  ],
                  ignoreInitial: true
                }).on("change", (async function (file) {
                  debug("[changed]: " + file);
                  return await runGeneration$1([file]);
                })).on("add", (async function (file) {
                debug("[added]: " + file);
                return await runGeneration$1([file]);
              })).on("unlink", (async function (file) {
              debug("[deleted]: " + file);
              var asGeneratedFile = toGeneratedFileName(t.fileName, file, undefined);
              var potentialGeneratedFile = filePathInGeneratedDir(t.fileName, asGeneratedFile, pathToGeneratedDir);
              var fileBaseName = Path.basename(file, ".res");
              if (Fs.existsSync(potentialGeneratedFile)) {
                console.log("Deleting generated file \"" + asGeneratedFile + "\" that belonged to " + fileBaseName + ".res...");
                Fs.unlinkSync(potentialGeneratedFile);
                return cleanUpExtraFiles(t, pathToGeneratedDir, undefined, undefined);
              }
              
            }));
      var onWatch = t.onWatch;
      if (onWatch !== undefined) {
        return await onWatch({
                    config: config,
                    runGeneration: runGeneration$1,
                    debug: debug
                  });
      } else {
        return ;
      }
    }
    var handleOtherCommand = t.handleOtherCommand;
    if (handleOtherCommand !== undefined) {
      return await handleOtherCommand({
                  command: otherCommand,
                  args: args$1,
                  config: await t.setup({
                        args: args$1
                      })
                });
    } else {
      console.log(t.cliHelpText);
      return ;
    }
  }
  console.log(t.cliHelpText);
}

var CodegenUtils;

export {
  CodegenUtils ,
  CliArgs ,
  defaultSetup ,
  make$1 as make,
  runCli ,
}
/* fs Not a pure module */
