#!/usr/bin/env node
/**
 * sync-resource-rule.mjs — 把「該用哪些資源」這條規則的**唯一原稿**同步給需要打包的呼叫端。
 *
 * 【為什麼需要這支】
 * 規則的原稿在 `shared/resource-rule/rule.mjs`（理由見該檔開頭）。
 * 兩條路取用它的方式不同：
 *
 *   · **安裝器 / 任何 Worker**：本來就會下載這個 repo 的 archive 當部署來源，
 *     直接 import `shared/resource-rule/rule.mjs`。**不需要副本，本支不管它。**
 *
 *   · **`acr` CLI**：`arcrun` 是獨立 npm 套件，`npm pack` 打不進套件目錄外的檔案
 *     ⇒ 套件裡必須有一份。這支就是產生那一份的地方。
 *
 * 【這算不算「第二份實作」】
 * 不算，而且是機械保證的：產生物是**逐位元組副本**，`--check` 一有差就 exit 1，
 * 而 `npm run build` 與 `npm test` 都會先跑 `--check`。
 * 也就是說「有人手改了 CLI 那一份」= build 紅、publish 擋下。
 * ——同 `cli/harness/`（產生物進 repo ＋ `check:harness` 世代閘）的既有慣例，
 *   不是為本票新發明的做法。
 *
 * 用法：
 *   node scripts/sync-resource-rule.mjs           產生／更新副本
 *   node scripts/sync-resource-rule.mjs --check    只檢查，有漂移就 exit 1（不寫檔）
 */
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'node:fs';
import { join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { createHash } from 'node:crypto';

// REPO 一律由本檔位置推導，不吃 cwd（比照 scripts/build-worker-artifacts.mjs）。
const REPO = resolve(fileURLToPath(new URL('.', import.meta.url)), '..');
const SOURCE_DIR = join(REPO, 'shared/resource-rule');

/**
 * 需要「套件內自帶一份」的呼叫端。**整個目錄原樣鏡射**（不是挑檔案）——
 * 檔名與相對位置保持一致，`cf-resource-api.mjs` 裡的 `./rule.mjs` 才不用改寫。
 * 安裝器不在此列：它直接讀 repo archive 裡的原稿，連副本都不需要。
 */
const MIRRORS = ['cli/src/lib/resource-rule'];

const CHECK_ONLY = process.argv.includes('--check');

/** @param {string|Buffer} b */
const sha256 = (b) => createHash('sha256').update(b).digest('hex');

if (!existsSync(SOURCE_DIR)) {
  console.error(`❌ 找不到規則原稿目錄：${SOURCE_DIR}`);
  process.exit(1);
}

/** 原稿目錄裡所有 .mjs（README / 測試不進副本）。 */
const FILES = readdirSync(SOURCE_DIR).filter((f) => f.endsWith('.mjs')).sort();
if (FILES.length === 0) {
  console.error(`❌ ${SOURCE_DIR} 裡沒有任何 .mjs 原稿`);
  process.exit(1);
}

let drifted = 0;
for (const mirror of MIRRORS) {
  for (const file of FILES) {
    const src = readFileSync(join(SOURCE_DIR, file));
    const srcHash = sha256(src);
    const rel = `${mirror}/${file}`;
    const abs = join(REPO, mirror, file);
    const had = existsSync(abs) ? readFileSync(abs) : null;

    if (had !== null && sha256(had) === srcHash) {
      console.log(`✔ ${rel}  = 原稿（sha256 ${srcHash.slice(0, 12)}）`);
      continue;
    }

    if (CHECK_ONLY) {
      drifted++;
      console.error(
        had === null
          ? `✗ ${rel} 不存在——跑 \`node scripts/sync-resource-rule.mjs\` 產生。`
          : `✗ ${rel} 與原稿不一致（副本 ${sha256(had).slice(0, 12)} ≠ 原稿 ${srcHash.slice(0, 12)}）。\n` +
            `  這一份是**產生物**，不要手改：規則要改就改 shared/resource-rule/${file}，` +
            `然後跑 \`node scripts/sync-resource-rule.mjs\`。`,
      );
      continue;
    }

    mkdirSync(join(REPO, mirror), { recursive: true });
    writeFileSync(abs, src);
    console.log(`↻ ${rel}  ← shared/resource-rule/${file}（sha256 ${srcHash.slice(0, 12)}）`);
  }

  // 副本目錄裡多出來的 .mjs = 有人在產生物旁邊自己加了一支（第二份實作的常見長法）。
  const mirrorAbs = join(REPO, mirror);
  const extra = existsSync(mirrorAbs)
    ? readdirSync(mirrorAbs).filter((f) => f.endsWith('.mjs') && !FILES.includes(f))
    : [];
  for (const f of extra) {
    drifted++;
    console.error(`✗ ${mirror}/${f} 在原稿目錄裡不存在——副本目錄不是放自己東西的地方。`);
  }
}

if (drifted > 0) {
  console.error(
    `\n❌ ${drifted} 項與規則原稿脫節。` +
      `\n「該用哪些資源」只能有一份實作（.claude/rules/07-thin-shell.md）——` +
      `副本漂移就是第二份實作偷偷長出來的樣子。`,
  );
  process.exit(1);
}
