const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const archiver = require('archiver');
const config = require('./resource-bundles.config.cjs');

function ensureDir(dir) {
  fs.mkdirSync(dir, { recursive: true });
}

function listFiles(dir) {
  if (!fs.existsSync(dir)) return [];
  const files = [];
  const walk = (current) => {
    for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
      const full = path.join(current, entry.name);
      if (entry.isDirectory()) {
        walk(full);
      } else if (entry.isFile()) {
        files.push(full);
      }
    }
  };
  walk(dir);
  files.sort();
  return files;
}

function hashDirectory(dir) {
  const hash = crypto.createHash('sha256');
  for (const file of listFiles(dir)) {
    const rel = path.relative(dir, file).replace(/\\/g, '/');
    const stat = fs.statSync(file);
    hash.update(rel);
    hash.update(String(stat.size));
    hash.update(fs.readFileSync(file));
  }
  return hash.digest('hex');
}

function hashFile(file) {
  const hash = crypto.createHash('sha256');
  hash.update(fs.readFileSync(file));
  return hash.digest('hex');
}

function readJson(file, fallback) {
  if (!fs.existsSync(file)) return fallback;
  try {
    return JSON.parse(fs.readFileSync(file, 'utf8'));
  } catch {
    return fallback;
  }
}

function zipDirectory(sourceDir, outFile) {
  return new Promise((resolve, reject) => {
    ensureDir(path.dirname(outFile));
    const output = fs.createWriteStream(outFile);
    const archive = archiver('zip', { zlib: { level: 9 } });
    output.on('close', resolve);
    archive.on('error', reject);
    archive.pipe(output);
    archive.directory(sourceDir, false);
    archive.finalize();
  });
}

async function main() {
  ensureDir(config.outputDir);
  ensureDir(path.dirname(config.releaseManifestPath));
  ensureDir(path.dirname(config.embeddedManifestPath));
  ensureDir(path.dirname(config.buildStatePath));

  const previousState = readJson(config.buildStatePath, { bundles: {} });
  const nextState = { generatedAt: new Date().toISOString(), bundles: {} };
  const manifest = {
    manifestVersion: 1,
    generatedAt: new Date().toISOString(),
    appVersion: process.env.npm_package_version || '',
    bundles: {},
  };

  const changedBundles = [];

  for (const bundle of config.bundles) {
    if (!fs.existsSync(bundle.source)) {
      if (bundle.required) {
        throw new Error(`Required resource bundle source missing: ${bundle.name} (${bundle.source})`);
      }
      continue;
    }

    const sourceFiles = listFiles(bundle.source);
    if (bundle.required && sourceFiles.length === 0) {
      throw new Error(`Required resource bundle source is empty: ${bundle.name} (${bundle.source})`);
    }

    const version = hashDirectory(bundle.source).slice(0, 16);
    const archive = `${bundle.name}-${version}.zip`;
    const archivePath = path.join(config.outputDir, archive);
    const previous = previousState.bundles?.[bundle.name];

    if (!previous || previous.version !== version || !fs.existsSync(archivePath)) {
      await zipDirectory(bundle.source, archivePath);
      changedBundles.push(bundle.name);
    }

    const size = fs.existsSync(archivePath) ? fs.statSync(archivePath).size : 0;
    const sha256 = fs.existsSync(archivePath) ? hashFile(archivePath) : '';
    if (bundle.required && (!fs.existsSync(archivePath) || size <= 0 || !sha256)) {
      throw new Error(`Required resource bundle archive was not created: ${bundle.name} (${archivePath})`);
    }

    const archiveUrl = process.env[config.releaseBaseUrlEnv] || config.releaseBaseUrl
      ? `${(process.env[config.releaseBaseUrlEnv] || config.releaseBaseUrl).replace(/\/$/, '')}/${archive}`
      : archive;

    manifest.bundles[bundle.name] = {
      version,
      archive,
      url: archiveUrl,
      sha256,
      size,
      required: !!bundle.required,
      extractTo: bundle.extractTo,
      source: path.relative(path.join(__dirname, '..'), bundle.source).replace(/\\/g, '/'),
    };

    nextState.bundles[bundle.name] = { version, archive, sha256, size };
  }

  fs.writeFileSync(config.releaseManifestPath, JSON.stringify(manifest, null, 2));
  fs.writeFileSync(config.embeddedManifestPath, JSON.stringify(manifest, null, 2));
  fs.writeFileSync(config.buildStatePath, JSON.stringify(nextState, null, 2));

  const summaryPath = path.join(config.outputDir, 'changed-bundles.json');
  fs.writeFileSync(summaryPath, JSON.stringify({ changedBundles, manifest: path.basename(config.releaseManifestPath) }, null, 2));

  console.log('[resource-build] changed bundles:', changedBundles.join(', ') || 'none');
}

main().catch((error) => {
  console.error('[resource-build] failed:', error);
  process.exit(1);
});
