// G3 純 WASI 把關（component-gatekeeping SDD，R3）
//
// 解析 WASM import section，取出所有 import 的 module name。
// 把關：只准 wasi_snapshot_preview1 + u6u（host functions）。
// 其他 module → runtime 鎖定風險，退稿（DECISIONS §4：避免 runtime 鎖定債）。
//
// WASM binary 結構（preview1）：
//   magic(4) + version(4) + 一串 section
//   section: id(1 byte) + size(LEB128) + payload
//   import section id = 2。payload: count(LEB128) + 每個 import：
//     module: len(LEB128) + bytes(UTF-8)
//     name:   len(LEB128) + bytes(UTF-8)
//     kind(1) + 之後依 kind 不同（func: typeidx LEB128 / table / mem / global）

const ALLOWED_IMPORT_MODULES = new Set([
  'wasi_snapshot_preview1',
  'u6u',
]);

/** 讀 unsigned LEB128，回 [value, nextOffset] */
function readULEB(buf: Uint8Array, offset: number): [number, number] {
  let result = 0;
  let shift = 0;
  let pos = offset;
  for (;;) {
    const byte = buf[pos++];
    result |= (byte & 0x7f) << shift;
    if ((byte & 0x80) === 0) break;
    shift += 7;
  }
  return [result, pos];
}

/**
 * 解析 wasm import section，回傳所有 import module name 的集合。
 * 解析失敗（非合法 wasm）回 null。
 */
export function parseWasmImportModules(wasmBytes: Uint8Array): Set<string> | null {
  // magic + version
  if (wasmBytes.length < 8) return null;
  if (wasmBytes[0] !== 0x00 || wasmBytes[1] !== 0x61 || wasmBytes[2] !== 0x73 || wasmBytes[3] !== 0x6d) {
    return null; // 不是 \0asm
  }

  const decoder = new TextDecoder('utf-8');
  const modules = new Set<string>();
  let offset = 8;

  try {
    while (offset < wasmBytes.length) {
      const sectionId = wasmBytes[offset++];
      const [sectionSize, afterSize] = readULEB(wasmBytes, offset);
      offset = afterSize;
      const sectionEnd = offset + sectionSize;

      if (sectionId === 2) {
        // import section
        let p = offset;
        const [count, afterCount] = readULEB(wasmBytes, p);
        p = afterCount;
        for (let i = 0; i < count; i++) {
          const [modLen, afterModLen] = readULEB(wasmBytes, p);
          p = afterModLen;
          const moduleName = decoder.decode(wasmBytes.subarray(p, p + modLen));
          p += modLen;
          modules.add(moduleName);

          const [nameLen, afterNameLen] = readULEB(wasmBytes, p);
          p = afterNameLen;
          p += nameLen; // skip import name

          const kind = wasmBytes[p++];
          // 依 kind skip descriptor
          if (kind === 0x00) {
            // func: typeidx (ULEB)
            const [, afterType] = readULEB(wasmBytes, p);
            p = afterType;
          } else if (kind === 0x01) {
            // table: reftype(1) + limits
            p += 1;
            const [flags, afterFlags] = readULEB(wasmBytes, p);
            p = afterFlags;
            const [, afterMin] = readULEB(wasmBytes, p);
            p = afterMin;
            if (flags === 0x01) { const [, afterMax] = readULEB(wasmBytes, p); p = afterMax; }
          } else if (kind === 0x02) {
            // mem: limits
            const [flags, afterFlags] = readULEB(wasmBytes, p);
            p = afterFlags;
            const [, afterMin] = readULEB(wasmBytes, p);
            p = afterMin;
            if (flags === 0x01) { const [, afterMax] = readULEB(wasmBytes, p); p = afterMax; }
          } else if (kind === 0x03) {
            // global: valtype(1) + mut(1)
            p += 2;
          } else {
            // 未知 kind，無法安全 skip → 中止解析
            return modules.size > 0 ? modules : null;
          }
        }
      }

      offset = sectionEnd;
    }
  } catch {
    // 解析越界等 → 回已收集的（或 null）
    return modules.size > 0 ? modules : null;
  }

  return modules;
}

/**
 * G3 把關：確認 wasm 只 import 白名單 module。
 * 回 null = 通過；回字串 = 退稿原因。
 */
export function checkPureWasi(wasmBytes: Uint8Array): string | null {
  const modules = parseWasmImportModules(wasmBytes);
  if (modules === null) {
    return 'WASM import section 無法解析（非合法 WASI preview1 wasm？）';
  }
  for (const mod of modules) {
    if (!ALLOWED_IMPORT_MODULES.has(mod)) {
      return `偵測到非白名單 import module：「${mod}」。零件只准依賴 wasi_snapshot_preview1 + u6u host functions（避免 runtime 鎖定，見 DECISIONS §4）`;
    }
  }
  return null;
}
