'use strict';

var Installers = module.exports;

var Crypto = require('crypto');
var Fs = require('node:fs/promises');
var path = require('node:path');

var reInstallTpl = /\s*#?\s*{{ installer }}/;

function padScript(txt) {
  return txt.replace(/^/g, '        ');
}

var BAD_SH_RE = /[<>'"`$\\]/;
Installers.renderBash = async function (
  pkgdir,
  rel,
  { baseurl, pkg, tag, ver, os = '', arch = '', libc = '', formats },
) {
  if (!Array.isArray(formats)) {
    formats = [];
  }
  if (!tag) {
    tag = '';
  }
  let installTxt = await Fs.readFile(path.join(pkgdir, 'install.sh'), 'utf8');
  installTxt = padScript(installTxt);
  var vers = rel.version.split('.');
  var v = {
    major: vers.shift() || '',
    minor: vers.shift() || '',
    patch: vers.join('.').replace(/[+\-].*/, ''),
    build: vers
      .join('.')
      .replace(/[^+\-]*/, '')
      .replace(/^-/, ''),
  };
  var pkgFile = rel.filename || rel.name;
  let tplTxt = await Fs.readFile(
    path.join(__dirname, 'package-install.tpl.sh'),
    'utf8',
  );
  // ex: 'node@lts' or 'node'
  var webiPkg = pkg;
  if (ver) {
    webiPkg += `@${ver}`;
  }

  let releaseParams = new URLSearchParams({
    os: rel.os,
    arch: rel.arch,
    libc: rel.libc,
    formats: formats.join(','),
    pretty: true,
  });
  let releaseSearch = releaseParams.toString();
  releaseSearch = releaseSearch.replace(/%2C/g, ',');
  let releaseUrl = `/api/releases/${pkg}@${tag}.tab?${releaseSearch}`;
  let releaseCsv = [
    rel.version,
    rel.lts,
    rel.channel,
    rel.date,
    rel.os,
    rel.arch,
    rel.ext,
    '-',
    rel.download,
    rel.name,
    rel.comment || '',
  ]
    .join(',')
    .replace(/'/g, '');

  let webiChecksum = await Installers.getWebiShChecksum();
  let envReplacements = [
    ['WEBI_CHECKSUM', webiChecksum],
    ['WEBI_PKG', webiPkg],
    ['WEBI_HOST', baseurl],
    ['WEBI_OS', os],
    ['WEBI_ARCH', arch],
    ['WEBI_LIBC', libc],
    ['WEBI_TAG', tag],
    ['WEBI_RELEASES', `${baseurl}${releaseUrl}`],
    ['WEBI_CSV', releaseCsv],
    ['WEBI_VERSION', rel.version],
    ['WEBI_MAJOR', v.major],
    ['WEBI_MINOR', v.minor],
    ['WEBI_PATCH', v.patch],
    ['WEBI_BUILD', v.build],
    ['WEBI_GIT_BRANCH', rel.git_branch || rel.git_tag],
    ['WEBI_GIT_TAG', rel.git_tag], // TODO replace with branch
    ['WEBI_LTS', rel.lts],
    ['WEBI_CHANNEL', rel.channel],
    ['WEBI_EXT', rel.ext],
    ['WEBI_FORMATS', formats.join(',')],
    ['WEBI_PKG_URL', rel.download],
    ['WEBI_PKG_PATHNAME', pkgFile],
    ['WEBI_PKG_FILE', pkgFile], // TODO replace with pathname
    ['PKG_NAME', pkg],
    ['PKG_STABLE', rel.stable],
    ['PKG_LATEST', rel.latest],
    ['PKG_OSES', (rel.oses || []).join(' ')],
    ['PKG_ARCHES', (rel.arches || []).join(' ')],
    ['PKG_LIBCS', (rel.libcs || []).join(' ')],
    ['PKG_FORMATS', (rel.formats || []).join(' ')],
  ];

  for (let env of envReplacements) {
    let name = env[0];
    let value = env[1];
    // Ex:
    // #export WEBI_FOO=xyz => export WEBI_FOO='123'
    // export WEBI_FOO=     => export WEBI_FOO='123'
    let envRe = new RegExp(
      `^[ \\t]*#?[ \\t]*(export[ \\t])?[ \\t]*(${name})=.*`,
      'm',
    );
    if (BAD_SH_RE.test(value)) {
      throw new Error(`key '${name}' has invalid value '${value}'`);
    }
    tplTxt = tplTxt.replace(envRe, `$1$2='${value}'`);
  }

  tplTxt = tplTxt
    .replace(/CHEATSHEET_URL/g, `${baseurl}/${pkg}`)
    // $', $0, ... $9, $`, $&, and $_ all have special meaning
    // (see https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RegExp)
    // However, it can be escaped with $$ (which must be escaped with $$)
    .replace(reInstallTpl, '\n' + installTxt.replace(/\$/g, '$$$$'));
  return tplTxt;
};

Installers.renderPowerShell = async function (
  pkgdir,
  rel,
  { baseurl, pkg, tag, ver, os, arch, libc = '', formats },
) {
  if (!Array.isArray(formats)) {
    formats = [];
  }
  if (!tag) {
    tag = '';
  }
  let installTxt = await Fs.readFile(path.join(pkgdir, 'install.ps1'), 'utf8');
  installTxt = padScript(installTxt);
  /*
    var vers = rel.version.split('.');
    var v = {
      major: vers.shift() || '',
      minor: vers.shift() || '',
      patch: vers.join('.').replace(/[+\-].*$/, ''),
      build: vers
        .join('.')
        .replace(/[^+\-]*()/, '')
        .replace(/^-/, '')
    };
  */
  let tplTxt = await Fs.readFile(
    path.join(__dirname, 'package-install.tpl.ps1'),
    'utf8',
  );
  var pkgver = pkg + '@' + ver;
  return (
    tplTxt
      .replace(
        /^(#)?\$Env:WEBI_LIBC\s*=.*/im,
        "$Env:WEBI_LIBC = '" + libc + "'",
      )
      .replace(
        /^(#)?\$Env:WEBI_HOST\s*=.*/im,
        "$Env:WEBI_HOST = '" + baseurl + "'",
      )
      .replace(
        /^(#)?\$Env:WEBI_PKG\s*=.*/im,
        "$Env:WEBI_PKG = '" + pkgver + "'",
      )
      .replace(/^(#)?\$Env:PKG_NAME\s*=.*/im, "$Env:PKG_NAME = '" + pkg + "'")
      .replace(
        /^(#)?\$Env:WEBI_VERSION\s*=.*/im,
        "$Env:WEBI_VERSION = '" + rel.version + "'",
      )
      .replace(
        /^(#)?\$Env:WEBI_GIT_TAG\s*=.*/im,
        "$Env:WEBI_GIT_TAG = '" + rel.git_tag + "'",
      )
      .replace(
        /^(#)?\$Env:WEBI_PKG_URL\s*=.*/im,
        "$Env:WEBI_PKG_URL = '" + rel.download + "'",
      )
      // TODO replace WEBI_PKG_FILE (which is sometimes a dir)
      .replace(
        /^(#)?\$Env:WEBI_PKG_PATHNAME\s*=.*/im,
        "$Env:WEBI_PKG_PATHNAME = '" + rel.name + "'",
      )
      // TODO deprecate
      .replace(
        /^(#)?\$Env:WEBI_PKG_FILE\s*=.*/im,
        "$Env:WEBI_PKG_FILE = '" + rel.name + "'",
      )
      .replace(reInstallTpl, '\n' + installTxt)
  );
};

var _webiShMeta = {
  stale: 10 * 1000,
  updated_at: 0,
  checksum: '',
  mtime: 0,
};
Installers.getWebiShChecksum = async function () {
  let now = Date.now();
  let ago = now - _webiShMeta.updated_at;
  if (ago <= _webiShMeta.stale) {
    return _webiShMeta.checksum;
  }

  let webiPath = path.join(__dirname, '../webi/webi.sh');
  let stat = await Fs.stat(webiPath);
  if (stat.mtimeMs === _webiShMeta.mtime) {
    return _webiShMeta.checksum;
  }

  let webiBuf = await Fs.readFile(webiPath, null);
  let webiHash = Crypto.createHash('sha1').update(webiBuf).digest('hex');
  let webiChecksum = webiHash.slice(0, 8);

  _webiShMeta.mtime = stat.mtimeMs;
  _webiShMeta.updated_at = now;
  _webiShMeta.checksum = webiChecksum;
  return _webiShMeta.checksum;
};
