#!/usr/bin/env node
/**
 * Build OTA sync tree + channel manifests for Windows printer agent.
 * Output: siraya-pos/frontend/public/downloads/printer/sync/win-x64/
 *         siraya-pos/frontend/public/downloads/printer/channels/{dev,stable}/manifest.json
 */
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, '..');
const POS_FRONTEND = path.resolve(ROOT, '../siraya-pos/frontend');
const PAYLOAD_SRC = path.join(ROOT, 'dist/release/payload/win-x64');
const SYNC_ROOT = path.join(POS_FRONTEND, 'public/downloads/printer/sync/win-x64');
const CHANNELS_ROOT = path.join(POS_FRONTEND, 'public/downloads/printer/channels');
const BASE_URL = 'https://pos.siraya.food/downloads/printer/sync/win-x64';

const EXCLUDE_DIRS = new Set([
  'node_modules',
  '.git',
  'dist',
  'daemon',
  'versions',
]);

const EXCLUDE_FILES = new Set(['.env']);

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

function shouldSkip(relPath) {
  const parts = relPath.split(/[/\\]/);
  if (parts.some((p) => EXCLUDE_DIRS.has(p))) return true;
  const base = parts[parts.length - 1];
  return EXCLUDE_FILES.has(base);
}

function walkFiles(dir, baseDir, out) {
  if (!fs.existsSync(dir)) return;
  for (const name of fs.readdirSync(dir)) {
    const full = path.join(dir, name);
    const rel = path.relative(baseDir, full).split(path.sep).join('/');
    if (shouldSkip(rel)) continue;
    const st = fs.statSync(full);
    if (st.isDirectory()) {
      walkFiles(full, baseDir, out);
    } else if (st.isFile()) {
      out.push({ rel, full, size: st.size });
    }
  }
}

function copyTree(src, dest) {
  fs.mkdirSync(dest, { recursive: true });
  const entries = [];
  walkFiles(src, src, entries);
  for (const { rel, full } of entries) {
    const target = path.join(dest, rel);
    fs.mkdirSync(path.dirname(target), { recursive: true });
    fs.copyFileSync(full, target);
  }
  return entries;
}

function buildManifest(version, channel, files) {
  return {
    version,
    channel,
    releasedAt: new Date().toISOString(),
    baseUrl: BASE_URL,
    installRoot: '%ProgramFiles%\\Siraya\\Printer',
    pathMap: {
      'app/': 'app/',
      'runtime/': 'runtime/',
    },
    files: files
      .map(({ rel, size, sha256 }) => ({
        path: rel,
        sha256,
        size,
      }))
      .sort((a, b) => a.path.localeCompare(b.path)),
  };
}

function main() {
  if (!fs.existsSync(PAYLOAD_SRC)) {
    console.error(`Missing payload: ${PAYLOAD_SRC} — run stage-windows-usb.sh first`);
    process.exit(1);
  }

  const version = JSON.parse(
    fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'),
  ).version;

  if (fs.existsSync(SYNC_ROOT)) {
    fs.rmSync(SYNC_ROOT, { recursive: true, force: true });
  }
  fs.mkdirSync(SYNC_ROOT, { recursive: true });

  const copied = copyTree(PAYLOAD_SRC, SYNC_ROOT);
  const files = copied.map(({ rel, full, size }) => ({
    rel,
    size,
    sha256: sha256File(full),
  }));

  const devManifest = buildManifest(version, 'dev', files);
  const stableManifest = buildManifest(version, 'stable', files);

  fs.mkdirSync(path.join(CHANNELS_ROOT, 'dev'), { recursive: true });
  fs.mkdirSync(path.join(CHANNELS_ROOT, 'stable'), { recursive: true });
  fs.writeFileSync(
    path.join(CHANNELS_ROOT, 'dev/manifest.json'),
    JSON.stringify(devManifest, null, 2) + '\n',
  );
  fs.writeFileSync(
    path.join(CHANNELS_ROOT, 'stable/manifest.json'),
    JSON.stringify(stableManifest, null, 2) + '\n',
  );

  console.log(`OTA sync manifest v${version}`);
  console.log(`  files:   ${files.length}`);
  console.log(`  sync:    ${SYNC_ROOT}`);
  console.log(`  dev:     ${path.join(CHANNELS_ROOT, 'dev/manifest.json')}`);
  console.log(`  stable:  ${path.join(CHANNELS_ROOT, 'stable/manifest.json')}`);
}

main();
