const { transform } = require('esbuild');
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');

const ROOT_DIR = path.resolve(__dirname, '..');
const DIST_DIR = path.join(ROOT_DIR, 'dist');
const BUNDLE_DIR = path.join(DIST_DIR, 'js', 'bundles');
const BRAND_IMAGE_PATHS = [
  path.join(ROOT_DIR, 'Crank.png'),
  path.resolve(ROOT_DIR, '..', '..', 'Crank.png'),
];

const BUNDLES = {
  'protected-core': {
    files: [
      'js/config.js',
      'js/i18n.js',
      'js/dom.js',
      'js/diagnostics.js',
      'js/workspace.js',
      'js/api.js',
      'js/ui-feedback.js',
      'js/auth.js',
    ],
    footer: 'window.CrankAuth.guardProtectedPage();\n',
  },
  login: {
    files: [
      'js/config.js',
      'js/i18n.js',
      'js/diagnostics.js',
      'js/api.js',
      'js/auth.js',
      'js/login.js',
    ],
  },
  operations: {
    files: [
      'node_modules/alpinejs/dist/cdn.min.js',
      'js/catalog.js',
    ],
  },
  agents: {
    files: [
      'node_modules/alpinejs/dist/cdn.min.js',
      'js/agents.js',
    ],
  },
  'api-keys': {
    files: [
      'js/nav.js',
      'js/api-keys.js',
    ],
  },
  logs: {
    files: [
      'js/nav.js',
      'js/logs.js',
    ],
  },
  usage: {
    files: [
      'js/nav.js',
      'js/usage.js',
    ],
  },
  secrets: {
    files: [
      'js/nav.js',
      'js/secrets.js',
    ],
  },
  settings: {
    files: [
      'js/nav.js',
      'js/settings.js',
    ],
  },
  'stream-sessions': {
    files: [
      'js/nav.js',
      'js/stream-sessions.js',
    ],
  },
  'async-jobs': {
    files: [
      'js/nav.js',
      'js/async-jobs.js',
    ],
  },
  'workspace-setup': {
    files: [
      'js/diagnostics.js',
      'js/workspace-setup.js',
    ],
  },
  wizard: {
    files: [
      'node_modules/js-yaml/dist/js-yaml.min.js',
      'js/wizard-state.js',
      'js/wizard-shell.js',
      'js/streaming-form.js',
      'js/stream-test-run.js',
      'js/wizard-grpc.js',
      'js/wizard-artifacts.js',
      'js/wizard-upstreams.js',
      'js/wizard-model.js',
      'js/wizard-live.js',
      'js/wizard.js',
      'js/nav.js',
    ],
  },
};

function sha(input) {
  return crypto.createHash('sha256').update(input).digest('hex').slice(0, 12);
}

function sourcePath(relativePath) {
  return path.join(ROOT_DIR, relativePath);
}

function ensureDirectory(directoryPath) {
  fs.mkdirSync(directoryPath, { recursive: true });
}

function removeDirectory(directoryPath) {
  fs.rmSync(directoryPath, { recursive: true, force: true });
}

function copyFile(source, destination) {
  ensureDirectory(path.dirname(destination));
  fs.copyFileSync(source, destination);
}

function copyDirectory(source, destination) {
  ensureDirectory(destination);
  for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
    const sourceEntry = path.join(source, entry.name);
    const destinationEntry = path.join(destination, entry.name);
    if (entry.isDirectory()) {
      copyDirectory(sourceEntry, destinationEntry);
      continue;
    }
    copyFile(sourceEntry, destinationEntry);
  }
}

function readSource(relativePath) {
  return fs.readFileSync(sourcePath(relativePath), 'utf8');
}

function resolveBrandImage() {
  const match = BRAND_IMAGE_PATHS.find(function(filePath) {
    return fs.existsSync(filePath);
  });
  if (!match) {
    throw new Error('unable to resolve Crank.png for UI build');
  }
  return match;
}

async function buildBundle(name, config) {
  const source = config.files.map(readSource).join('\n\n') + '\n' + (config.footer || '');
  const result = await transform(source, {
    loader: 'js',
    minify: true,
    target: 'es2019',
    legalComments: 'none',
  });
  const hash = sha(result.code);
  const fileName = `${name}.${hash}.js`;
  const relativeOutputPath = path.posix.join('js', 'bundles', fileName);
  fs.writeFileSync(path.join(BUNDLE_DIR, fileName), result.code);
  return relativeOutputPath;
}

function rewriteHtml(relativePath, replacements, prefix) {
  const source = readSource(relativePath);
  const output = source.replace(/%CRANK_BUNDLE_([A-Z0-9_]+)%/g, function(_match, token) {
    const key = token.toLowerCase().replace(/_/g, '-');
    const filePath = replacements[key];
    if (!filePath) {
      throw new Error(`missing bundle placeholder ${key}`);
    }
    return prefix ? path.posix.join(prefix, filePath) : filePath;
  });
  const destination = path.join(DIST_DIR, relativePath);
  ensureDirectory(path.dirname(destination));
  fs.writeFileSync(destination, output);
}

async function main() {
  removeDirectory(DIST_DIR);
  ensureDirectory(BUNDLE_DIR);

  const manifest = {};
  for (const [name, config] of Object.entries(BUNDLES)) {
    manifest[name] = await buildBundle(name, config);
  }

  copyDirectory(path.join(ROOT_DIR, 'css'), path.join(DIST_DIR, 'css'));
  copyDirectory(path.join(ROOT_DIR, 'data'), path.join(DIST_DIR, 'data'));
  copyDirectory(path.join(ROOT_DIR, 'html', 'wizard'), path.join(DIST_DIR, 'html', 'wizard'));
  copyDirectory(path.join(ROOT_DIR, 'icons'), path.join(DIST_DIR, 'icons'));
  copyFile(resolveBrandImage(), path.join(DIST_DIR, 'Crank.png'));

  rewriteHtml('index.html', manifest, '');
  rewriteHtml('html/login.html', manifest, '..');
  rewriteHtml('html/agents.html', manifest, '..');
  rewriteHtml('html/api-keys.html', manifest, '..');
  rewriteHtml('html/async-jobs.html', manifest, '..');
  rewriteHtml('html/logs.html', manifest, '..');
  rewriteHtml('html/secrets.html', manifest, '..');
  rewriteHtml('html/settings.html', manifest, '..');
  rewriteHtml('html/stream-sessions.html', manifest, '..');
  rewriteHtml('html/usage.html', manifest, '..');
  rewriteHtml('html/workspace-setup.html', manifest, '..');
  rewriteHtml('html/wizard/index.html', manifest, '../..');

  fs.writeFileSync(
    path.join(DIST_DIR, 'build-manifest.json'),
    JSON.stringify(manifest, null, 2) + '\n',
  );
}

main().catch(function(error) {
  console.error(error);
  process.exitCode = 1;
});
