/**
 * WASI preview1 輕量 shim
 * 只實作 stdin/stdout/stderr 所需的最小 syscall 集合。
 * 其餘 syscall 一律回傳 ENOSYS（76），確保零件無法呼叫網路或檔案系統。
 *
 * 不依賴任何外部套件（不使用 @cloudflare/workers-wasi）。
 * Requirements: 3.1, 3.3
 */

/**
 * createArcrunHostFunctions 所需的最小 env 子集。
 * 不直接依賴 cypher-executor 的 Bindings,讓 auth primitive Worker 這類
 * 只綁 CREDENTIALS_KV / RECIPES 的獨立 Worker 也能用。
 */
export interface ArcrunHostEnv {
  CREDENTIALS_KV: KVNamespace;
  RECIPES: KVNamespace;
  /**
   * credential-store-migration T4（§2.5/§5）：CF Workers per-script Secrets 以 env var 形式
   * 注入 worker，值只能靠字串動態索引取得（`env[ref]`，T1.5 spike ② 已證可行）。
   * 用 index signature 讓 `secret_get` host function 能對任意 secret_ref 字串取值，
   * 不需要逐一宣告固定屬性名。
   */
  [secretRef: string]: unknown;
}

const WASI_ESUCCESS = 0;
const WASI_ENOSYS = 76;

// fd 常數
const FD_STDIN = 0;
const FD_STDOUT = 1;
const FD_STDERR = 2;

export interface WasiShim {
  /** WebAssembly.Imports 物件，傳入 WebAssembly.instantiate */
  imports: WebAssembly.Imports;
  /** 取得 stdout 的完整輸出（合併所有 chunks） */
  getStdout(): string;
  /** 取得 stderr 的完整輸出 */
  getStderr(): string;
  /** 注入 WebAssembly.Memory（instantiate 後呼叫） */
  setMemory(memory: WebAssembly.Memory): void;
  /**
   * 執行 WASM _start，自動使用 WebAssembly.promising（JSPI）讓 async host
   * function 能正確 suspend/resume。若 JSPI 不可用則 fallback 同步執行。
   * 必須在 setMemory() 之後呼叫。
   */
  run(instance: WebAssembly.Instance): Promise<void>;
}

/**
 * Host function 注入介面
 * 讓 .wasm 零件能透過 host function 呼叫外部服務，而不需要網路 syscall
 *
 * 嚴格邊界：
 * - `secret_get` 只放行 `CRED_` 前綴，WASM 讀不到 worker 本身的其他機密
 * - `kv_get` 必須在 Worker 側檢查 key 前綴以防越權（見 auth-dispatcher.ts）
 */
export interface WasiHostFunctions {
  /** HTTP 請求 host function：.wasm 呼叫此函數發出 HTTP 請求 */
  http_request?: (url: string, method: string, headers: string, body: string) => Promise<string>;
  /** KV 讀取：key 前綴由 Worker 路由到對應 binding，並做越權檢查 */
  kv_get?: (key: string) => Promise<string | null>;
  /**
   * credential-store-migration T4（§2.5/§5）：讀取 CF Workers per-script Secrets 的值。
   * ref = D1 credentials 表的 secret_ref（env var 名，如 `CRED_TELEGRAM_BOT_TOKEN_A1B2C3D4`）。
   * host 端實作＝`env[ref]` 動態字串索引（零網路呼叫，值已由 CF runtime 以 env var 注入 worker，
   * T1.5 spike ② 已證可行）。找不到該 ref → 回傳 null（與 kv_get 的「找不到」語意一致）。
   */
  secret_get?: (ref: string) => Promise<string | null>;
  /** KV 寫入：用於快取 access_token 等短效值，ttlSeconds=0 表示不設 TTL */
  kv_put?: (key: string, value: string, ttlSeconds: number) => Promise<void>;
  /** RS256 簽章：用 crypto.subtle 做 RSASSA-PKCS1-v1_5 + SHA-256 */
  crypto_sign_rs256?: (data: Uint8Array, pkcs8: Uint8Array) => Promise<Uint8Array>;
}

/**
 * 建立 WASI shim 實例
 * @param stdinData - 要寫入 stdin 的 UTF-8 字串（通常是 JSON.stringify(input)）
 * @param hostFunctions - 可選的 host function 注入（讓 .wasm 呼叫外部服務）
 */
export function createWasiShim(stdinData: string, hostFunctions?: WasiHostFunctions): WasiShim {
  const stdinBytes = new TextEncoder().encode(stdinData);
  let stdinOffset = 0;

  const stdoutChunks: Uint8Array[] = [];
  const stderrChunks: Uint8Array[] = [];

  let memory: WebAssembly.Memory | null = null;

  function getMemoryView(): DataView {
    if (!memory) throw new Error('WASI memory not set — call setMemory() after instantiate');
    return new DataView(memory.buffer);
  }

  // 寫入結果到 WASM 的 outPtr buffer（host function 共用）
  // 回傳 0 = 成功，1 = memory 不可用
  function writeOut(buf: ArrayBuffer, outPtr: number, outLenPtr: number, data: Uint8Array): number {
    try {
      new Uint8Array(buf, outPtr, data.length).set(data);
      new DataView(buf).setUint32(outLenPtr, data.length, true);
      return 0;
    } catch {
      return 1;
    }
  }

  /**
   * fd_write: 將 iovec 陣列的資料寫入 fd（stdout=1 或 stderr=2）
   * iovec 結構：{ buf: i32, buf_len: i32 }（各 4 bytes，little-endian）
   */
  function fd_write(fd: number, iovs: number, iovs_len: number, nwritten_ptr: number): number {
    if (fd !== FD_STDOUT && fd !== FD_STDERR) return WASI_ENOSYS;
    const view = getMemoryView();
    const buf = memory!.buffer;
    let totalWritten = 0;

    for (let i = 0; i < iovs_len; i++) {
      const iov_base = view.getUint32(iovs + i * 8, true);
      const iov_len = view.getUint32(iovs + i * 8 + 4, true);
      if (iov_len === 0) continue;
      const chunk = new Uint8Array(buf, iov_base, iov_len);
      const copy = new Uint8Array(iov_len);
      copy.set(chunk);
      if (fd === FD_STDOUT) stdoutChunks.push(copy);
      else stderrChunks.push(copy);
      totalWritten += iov_len;
    }

    view.setUint32(nwritten_ptr, totalWritten, true);
    return WASI_ESUCCESS;
  }

  /**
   * fd_read: 從 stdin 讀取資料到 iovec 陣列
   */
  function fd_read(fd: number, iovs: number, iovs_len: number, nread_ptr: number): number {
    if (fd !== FD_STDIN) return WASI_ENOSYS;
    const view = getMemoryView();
    const buf = memory!.buffer;
    let totalRead = 0;

    for (let i = 0; i < iovs_len; i++) {
      const iov_base = view.getUint32(iovs + i * 8, true);
      const iov_len = view.getUint32(iovs + i * 8 + 4, true);
      if (iov_len === 0) continue;

      const remaining = stdinBytes.length - stdinOffset;
      if (remaining <= 0) break;

      const toCopy = Math.min(iov_len, remaining);
      const dest = new Uint8Array(buf, iov_base, toCopy);
      dest.set(stdinBytes.subarray(stdinOffset, stdinOffset + toCopy));
      stdinOffset += toCopy;
      totalRead += toCopy;
    }

    view.setUint32(nread_ptr, totalRead, true);
    return WASI_ESUCCESS;
  }

  /**
   * proc_exit: 零件呼叫 exit()，拋出 Error 中止執行
   */
  function proc_exit(code: number): never {
    throw new Error(`wasm exit: ${code}`);
  }

  /**
   * random_get: 填充隨機 bytes（使用 Web Crypto API）
   */
  function random_get(buf_ptr: number, buf_len: number): number {
    const view = new Uint8Array(memory!.buffer, buf_ptr, buf_len);
    crypto.getRandomValues(view);
    return WASI_ESUCCESS;
  }

  // ── Asyncify protocol ──────────────────────────────────────────────────────
  // TinyGo WASI target 永遠使用 asyncify scheduler。Asyncify 讓 WASM 能在呼叫 host
  // function 時「unwind」（保存 call stack），待 async 工作完成後再「rewind」（恢復）。
  //
  // 協議流程（每次 async host function 呼叫）：
  //   1. WASM 呼叫 host import（例如 http_request）
  //   2. Host 檢查 asyncify_get_state()：
  //      - state=1（Unwinding）: 正在展開，host 應直接回傳 0（佔位值）
  //      - state=2（Rewinding）: 正在恢復，host 應回傳上一次 async 結果（已存在 asyncifyResult）
  //      - state=0（Normal）: 正常執行，host 啟動 async 工作並呼叫 asyncify_start_unwind
  //   3. WASM 的 _start 控制流回到 run()（asyncify 讓 _start 提前返回）
  //   4. run() await async 工作，呼叫 asyncify_start_rewind，再次呼叫 _start
  //   5. WASM 從 host import 返回點繼續執行，host 回傳儲存的結果
  //
  // 注意：每次 _start 呼叫只能處理一個 async 中斷點。若 WASM 有多個連續的 async host call，
  // run() 會在 while 迴圈裡重複 rewind 直到 asyncify_get_state() == 0（Normal）。

  // Asyncify 資料緩衝區設定（TinyGo asyncify 用於保存 call stack）
  // 位址在 run() 中設定（WASM memory 末尾分配 1MB）
  let asyncifyDataPtr = 0;
  const ASYNCIFY_BUF_SIZE = 1024 * 1024; // 1MB stack buffer

  // 儲存 async host function 的結果和 Promise
  let asyncifyPendingPromise: Promise<number> | null = null;
  let asyncifyResult: number = 0;

  // asyncify exports（run() 設定後才可用）
  let asyncifyExports: {
    get_state: () => number;
    start_unwind: (ptr: number) => void;
    stop_unwind: () => void;
    start_rewind: (ptr: number) => void;
    stop_rewind: () => void;
  } | null = null;

  // JSPI helper：若環境支援 WebAssembly.Suspending，用它包裝 async import function
  // 用於 scheduler=none 編譯的 WASM（無 asyncify exports）
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  function jspiSuspending<T extends (...args: any[]) => Promise<unknown>>(fn: T): T {
    const SuspendingCtor = (WebAssembly as unknown as Record<string, unknown>)['Suspending'] as
      (new (fn: T) => T) | undefined;
    return SuspendingCtor ? new SuspendingCtor(fn) : fn;
  }

  // 建立一個 asyncify-aware 的 host function wrapper
  // 協議：Normal 時啟動 async 工作並呼叫 start_unwind；Rewinding 時回傳已存的結果
  // 用於 scheduler=asyncify 編譯的 WASM（有 asyncify exports）
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  function asyncifyWrap(fn: (...args: any[]) => Promise<number>): (...args: any[]) => number {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    return (...args: any[]): number => {
      if (!memory) return 1;

      const ax = asyncifyExports;
      if (!ax) return 0; // asyncify 尚未初始化（sync fallback）

      const state = ax.get_state();

      if (state === 2) {
        // Rewinding：回傳上次 async 的真實結果
        return asyncifyResult;
      }

      if (state === 1) {
        // Unwinding 中：直接回傳 0（WASM 在 unwind，不使用此值）
        return 0;
      }

      // Normal（state=0）：啟動 async 工作，觸發 asyncify unwind
      asyncifyPendingPromise = fn(...args);

      // asyncify_start_unwind 設定 WASM 內部 unwind flag；
      // host function 返回後 WASM 開始保存 call stack，最終 _start() 返回
      ax.start_unwind(asyncifyDataPtr);
      return 0; // WASM 忽略此值（正在 unwind）
    };
  }

  // 根據 WASM 是否有 asyncify exports 決定使用哪種包裝方式
  // JSPI mode: scheduler=none WASM + WebAssembly.Suspending
  // asyncify mode: scheduler=asyncify WASM + asyncify protocol
  // 初始化時先用 asyncifyWrap，run() 後若沒有 asyncify exports 就切換到 jspiSuspending
  // 但因為 imports 在 instantiate 前就需要確定，這裡統一先用 asyncifyWrap
  // run() 時若發現沒有 asyncify exports 且有 JSPI，則使用 JSPI 模式
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  function hostWrap(fn: (...args: any[]) => Promise<number>): (...args: any[]) => number | Promise<number> {
    // 嘗試使用 JSPI Suspending（若環境支援）
    const SuspendingCtor = (WebAssembly as unknown as Record<string, unknown>)['Suspending'] as
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      (new (fn: any) => any) | undefined;

    if (SuspendingCtor) {
      // JSPI 可用：包裝為 Suspending，讓 WASM 能 suspend 等待 async 結果
      // 這適用於 scheduler=none 的 WASM（無 asyncify 干擾）
      return new SuspendingCtor(fn);
    }

    // fallback：asyncify 協議（scheduler=asyncify WASM）
    return asyncifyWrap(fn);
  }

  const shim: WasiShim = {
    imports: {
      wasi_snapshot_preview1: {        fd_write,
        fd_read,
        proc_exit,
        random_get,
        // 其餘 syscall 回傳 ENOSYS（不允許網路/檔案系統操作）
        fd_seek: () => WASI_ENOSYS,
        fd_close: () => WASI_ESUCCESS,
        fd_fdstat_get: () => WASI_ENOSYS,
        fd_prestat_get: () => WASI_ENOSYS,
        fd_prestat_dir_name: () => WASI_ENOSYS,
        environ_get: () => WASI_ESUCCESS,
        environ_sizes_get: (count_ptr: number, size_ptr: number) => {
          if (memory) {
            const view = getMemoryView();
            view.setUint32(count_ptr, 0, true);
            view.setUint32(size_ptr, 0, true);
          }
          return WASI_ESUCCESS;
        },
        args_get: () => WASI_ESUCCESS,
        args_sizes_get: (argc_ptr: number, argv_buf_size_ptr: number) => {
          if (memory) {
            const view = getMemoryView();
            view.setUint32(argc_ptr, 0, true);
            view.setUint32(argv_buf_size_ptr, 0, true);
          }
          return WASI_ESUCCESS;
        },
        clock_time_get: (id: number, precision: bigint, time_ptr: number) => {
          if (memory) {
            const view = getMemoryView();
            const now = BigInt(Date.now()) * 1_000_000n;
            view.setBigUint64(time_ptr, now, true);
          }
          return WASI_ESUCCESS;
        },
        clock_res_get: () => WASI_ENOSYS,
        poll_oneoff: () => WASI_ENOSYS,
        sched_yield: () => WASI_ESUCCESS,
        proc_raise: () => WASI_ENOSYS,
        sock_accept: () => WASI_ENOSYS,
        sock_recv: () => WASI_ENOSYS,
        sock_send: () => WASI_ENOSYS,
        sock_shutdown: () => WASI_ENOSYS,
        path_open: () => WASI_ENOSYS,
        path_create_directory: () => WASI_ENOSYS,
        path_remove_directory: () => WASI_ENOSYS,
        path_rename: () => WASI_ENOSYS,
        path_unlink_file: () => WASI_ENOSYS,
        path_filestat_get: () => WASI_ENOSYS,
        path_readlink: () => WASI_ENOSYS,
        path_symlink: () => WASI_ENOSYS,
        path_link: () => WASI_ENOSYS,
      },
      // u6u host functions：讓 .wasm 零件透過 host function 呼叫外部服務
      // .wasm 零件用 //go:wasmimport u6u <name> 宣告
      // 所有 async host function 透過 asyncifyWrap 包裝，實作 asyncify 協議
      u6u: {
        http_request: hostFunctions?.http_request
          ? hostWrap(async (urlPtr: number, urlLen: number, methodPtr: number, methodLen: number,
              headersPtr: number, headersLen: number, bodyPtr: number, bodyLen: number,
              outPtr: number, outLenPtr: number): Promise<number> => {
              if (!memory) return 1;
              // 在 await 前讀完所有輸入（memory.buffer 在 await 後可能因 grow 而失效）
              const snapBuf = memory.buffer;
              const dec = new TextDecoder();
              const url = dec.decode(new Uint8Array(snapBuf, urlPtr, urlLen));
              const method = dec.decode(new Uint8Array(snapBuf, methodPtr, methodLen));
              const headers = dec.decode(new Uint8Array(snapBuf, headersPtr, headersLen));
              const body = dec.decode(new Uint8Array(snapBuf, bodyPtr, bodyLen));
              try {
                const result = await hostFunctions!.http_request!(url, method, headers, body);
                // await 後重新拿 memory.buffer（grow 會產生新的 ArrayBuffer）
                return writeOut(memory.buffer, outPtr, outLenPtr, new TextEncoder().encode(result));
              } catch (e) {
                // t117: 寫錯誤 envelope 到 WASM 輸出（main.go 讀 error key → success:false + 詳情）；
                // 取代只 return 1（WASM 寫無資訊的 "HTTP request failed"）。
                // writeOut 失敗（memory 壞）才 fallback return 1。
                const errDetail = e instanceof Error ? e.message : String(e);
                const errEnv = new TextEncoder().encode(
                  JSON.stringify({ error: `fetch failed: ${errDetail}`, status: 0, body: '' })
                );
                return writeOut(memory.buffer, outPtr, outLenPtr, errEnv);
              }
            })
          : () => 1,

        // kv_get(keyPtr, keyLen, outPtr, outLenPtr) → 0 成功；1 錯誤；2 找不到 key
        kv_get: hostFunctions?.kv_get
          ? hostWrap(async (keyPtr: number, keyLen: number, outPtr: number, outLenPtr: number): Promise<number> => {
              if (!memory) { console.error('[kv_get] memory null'); return 1; }
              const key = new TextDecoder().decode(new Uint8Array(memory.buffer, keyPtr, keyLen));
              console.error(`[kv_get] key="${key}" keyPtr=${keyPtr} keyLen=${keyLen} outPtr=${outPtr} outLenPtr=${outLenPtr}`);
              try {
                const result = await hostFunctions!.kv_get!(key);
                console.error(`[kv_get] result=${result === null ? 'null' : result.slice(0, 80)}`);
                if (result === null) return 2;
                const encoded = new TextEncoder().encode(result);
                const status = writeOut(memory.buffer, outPtr, outLenPtr, encoded);
                console.error(`[kv_get] writeOut status=${status} encodedLen=${encoded.length} memBufLen=${memory.buffer.byteLength}`);
                return status;
              } catch (e) {
                console.error(`[kv_get] error: ${e}`);
                return 1;
              }
            })
          : () => 1,

        // secret_get(refPtr, refLen, outPtr, outLenPtr) → 0 成功；1 錯誤；2 找不到 ref
        // 與 kv_get 同款 pointer/memory-write 機制；差別只在 host 端實作來源（env[ref] 而非 KV.get）。
        secret_get: hostFunctions?.secret_get
          ? hostWrap(async (refPtr: number, refLen: number, outPtr: number, outLenPtr: number): Promise<number> => {
              if (!memory) return 1;
              const ref = new TextDecoder().decode(new Uint8Array(memory.buffer, refPtr, refLen));
              try {
                const result = await hostFunctions!.secret_get!(ref);
                if (result === null) return 2;
                const encoded = new TextEncoder().encode(result);
                return writeOut(memory.buffer, outPtr, outLenPtr, encoded);
              } catch {
                return 1;
              }
            })
          : () => 1,

        // kv_put(keyPtr, keyLen, valPtr, valLen, ttlSeconds) → 0 成功；1 錯誤
        kv_put: hostFunctions?.kv_put
          ? hostWrap(async (keyPtr: number, keyLen: number, valPtr: number, valLen: number, ttlSeconds: number): Promise<number> => {
              if (!memory) return 1;
              const dec = new TextDecoder();
              const key = dec.decode(new Uint8Array(memory.buffer, keyPtr, keyLen));
              const value = dec.decode(new Uint8Array(memory.buffer, valPtr, valLen));
              try {
                await hostFunctions!.kv_put!(key, value, ttlSeconds);
                return 0;
              } catch {
                return 1;
              }
            })
          : () => 1,

        // crypto_decrypt — 已停用，永遠回 1（失敗）。
        //
        // ⚠️ 不能整條移除：現役 auth_static_key / auth_service_account / auth_oauth2 的
        // .wasm 仍宣告 `//go:wasmimport u6u crypto_decrypt`，import 缺項會讓 WASM
        // **instantiate 直接失敗**（不是呼叫才失敗）→ 所有認證零件全掛。故保留成 stub，
        // 讓連結成立。待三個零件的 Go 原始碼移除該 wasmimport 並重編 wasm 後，才可刪掉這條。
        crypto_decrypt: () => 1,

        // crypto_sign_rs256(dataPtr, dataLen, pkcs8Ptr, pkcs8Len, outPtr, outLenPtr) → 0 成功
        crypto_sign_rs256: hostFunctions?.crypto_sign_rs256
          ? hostWrap(async (dataPtr: number, dataLen: number, pkcs8Ptr: number, pkcs8Len: number,
              outPtr: number, outLenPtr: number): Promise<number> => {
              if (!memory) return 1;
              // await 前複製 typed array（避免 memory grow 後 buffer 失效）
              const data = new Uint8Array(new Uint8Array(memory.buffer, dataPtr, dataLen));
              const pkcs8 = new Uint8Array(new Uint8Array(memory.buffer, pkcs8Ptr, pkcs8Len));
              try {
                const sig = await hostFunctions!.crypto_sign_rs256!(data, pkcs8);
                return writeOut(memory.buffer, outPtr, outLenPtr, sig);
              } catch {
                return 1;
              }
            })
          : () => 1,

      },
    },

    setMemory(mem: WebAssembly.Memory) {
      memory = mem;
    },

    async run(instance: WebAssembly.Instance): Promise<void> {
      const exp = instance.exports as Record<string, unknown>;
      const startFn = (exp._start ?? exp.main) as (() => void) | undefined;
      if (typeof startFn !== 'function') throw new Error('WASM missing _start or main export');

      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const promisingFn = (WebAssembly as unknown as Record<string, unknown>)['promising'] as
        ((fn: () => void) => () => Promise<void>) | undefined;

      // 若環境支援 JSPI（Cloudflare Workers 2025+），優先使用 WebAssembly.promising
      // hostWrap() 已將 imports 包裝為 WebAssembly.Suspending，不需要 asyncify 協議
      if (promisingFn) {
        try {
          await promisingFn(startFn)();
        } catch (e) {
          if (!(e instanceof Error && e.message === 'wasm exit: 0')) throw e;
        }
        return;
      }

      // JSPI 不可用：使用 asyncify 協議（需要 WASM 有 asyncify exports）
      const asyncifyGetState = exp.asyncify_get_state as (() => number) | undefined;
      const asyncifyStartUnwind = exp.asyncify_start_unwind as ((ptr: number) => void) | undefined;
      const asyncifyStopUnwind = exp.asyncify_stop_unwind as (() => void) | undefined;
      const asyncifyStartRewind = exp.asyncify_start_rewind as ((ptr: number) => void) | undefined;
      const asyncifyStopRewind = exp.asyncify_stop_rewind as (() => void) | undefined;

      if (asyncifyGetState && asyncifyStartUnwind && asyncifyStopUnwind &&
          asyncifyStartRewind && asyncifyStopRewind) {
        asyncifyExports = {
          get_state: asyncifyGetState,
          start_unwind: asyncifyStartUnwind,
          stop_unwind: asyncifyStopUnwind,
          start_rewind: asyncifyStartRewind,
          stop_rewind: asyncifyStopRewind,
        };

        const mallocFn = exp.malloc as ((size: number) => number) | undefined;
        if (mallocFn && memory) {
          const totalSize = ASYNCIFY_BUF_SIZE;
          asyncifyDataPtr = mallocFn(totalSize);
          const view = new DataView(memory.buffer);
          view.setInt32(asyncifyDataPtr, asyncifyDataPtr + 8, true);
          view.setInt32(asyncifyDataPtr + 4, asyncifyDataPtr + totalSize, true);
        } else if (memory) {
          const memBytes = memory.buffer.byteLength;
          asyncifyDataPtr = memBytes - ASYNCIFY_BUF_SIZE;
          if (asyncifyDataPtr > 8) {
            const view = new DataView(memory.buffer);
            view.setInt32(asyncifyDataPtr, asyncifyDataPtr + 8, true);
            view.setInt32(asyncifyDataPtr + 4, asyncifyDataPtr + ASYNCIFY_BUF_SIZE, true);
          }
        }
      }

      // JSPI 不可用且無 asyncify exports：同步執行（host function 不能 async）
      if (!asyncifyExports) {
        try { startFn(); } catch (e) {
          if (!(e instanceof Error && e.message === 'wasm exit: 0')) throw e;
        }
        return;
      }

      // 主執行迴圈：每次呼叫 _start，若 asyncify 捕捉到 pending promise 就 await 再 rewind
      let rewinding = false;
      while (true) {
        asyncifyPendingPromise = null;

        try {
          if (rewinding) {
            asyncifyExports.start_rewind(asyncifyDataPtr);
            startFn();
            asyncifyExports.stop_rewind();
          } else {
            startFn();
          }
        } catch (e) {
          if (e instanceof Error && e.message === 'wasm exit: 0') break;
          throw e;
        }

        // 若 asyncifyWrap 觸發了 unwind，_start 會因 unwind 返回（沒有 exit）
        // asyncifyWrap 已呼叫 start_unwind，這裡只需 stop_unwind 並 await promise
        if (asyncifyPendingPromise !== null) {
          asyncifyExports.stop_unwind();
          asyncifyResult = await asyncifyPendingPromise;
          asyncifyPendingPromise = null;
          rewinding = true;
          continue;
        }

        // 沒有 pending promise 且沒有 exit → 正常完成
        break;
      }
    },

    getStdout(): string {
      if (stdoutChunks.length === 0) return '';
      const total = stdoutChunks.reduce((n, c) => n + c.length, 0);
      const merged = new Uint8Array(total);
      let offset = 0;
      for (const chunk of stdoutChunks) {
        merged.set(chunk, offset);
        offset += chunk.length;
      }
      return new TextDecoder().decode(merged);
    },

    getStderr(): string {
      if (stderrChunks.length === 0) return '';
      const total = stderrChunks.reduce((n, c) => n + c.length, 0);
      const merged = new Uint8Array(total);
      let offset = 0;
      for (const chunk of stderrChunks) {
        merged.set(chunk, offset);
        offset += chunk.length;
      }
      return new TextDecoder().decode(merged);
    },
  };

  return shim;
}

// ── Worker 端 host function 實作（Phase 0.6）──────────────────────────────────
//
// 唯一合法位置：RS256 簽章只准出現在本檔（02-forbidden.md §2.2）。
// 由 component-loader 的 WASM runner 路徑呼叫，注入進 createWasiShim。
//
// 安全邊界：
//   1. `secret_get` 只放行 `CRED_` 前綴，WASM 讀不到 worker 本身的其他 env 機密
//   2. `kv_get` 依 key 前綴路由，且 `{api_key}:cred:*` 必須符合 stdin 傳入的 api_key（越權檢查）
//   3. 未知前綴回傳 null（WASM 收到 kv_get 回傳 2 = 找不到）

function hexToUint8Array(hex: string): Uint8Array {
  const bytes = new Uint8Array(hex.length / 2);
  for (let i = 0; i < hex.length; i += 2) bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
  return bytes;
}

function base64ToUint8Array(b64: string): Uint8Array {
  const binary = atob(b64);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
  return bytes;
}

/**
 * 依 key 前綴路由到對應 KV binding，並做越權檢查。
 * - `auth_recipe:{service}` → env.RECIPES
 * - `{apiKey}:cred:{name}`   → env.CREDENTIALS_KV（前綴必須等於 caller 的 apiKey）
 * - 其他前綴 → null（拒絕）
 */
async function routedKvGet(env: ArcrunHostEnv, apiKey: string, key: string): Promise<string | null> {
  if (key.startsWith('auth_recipe:')) {
    return env.RECIPES.get(key);
  }
  const credMatch = key.match(/^([^:]+):cred:.+$/);
  if (credMatch) {
    if (credMatch[1] !== apiKey) {
      // 越權：WASM 嘗試讀其他租戶的 credential
      return null;
    }
    return env.CREDENTIALS_KV.get(key);
  }
  return null;
}

/**
 * 依 key 前綴路由寫入 KV。只允許寫 oauth2 cache key（短效 access_token）。
 * - `{apiKey}:oauth2:{service}:*` → env.CREDENTIALS_KV（越權檢查）
 */
async function routedKvPut(env: ArcrunHostEnv, apiKey: string, key: string, value: string, ttlSeconds: number): Promise<void> {
  const oauth2Match = key.match(/^([^:]+):oauth2:.+$/);
  if (oauth2Match && oauth2Match[1] === apiKey) {
    const opts = ttlSeconds > 0 ? { expirationTtl: ttlSeconds } : undefined;
    await env.CREDENTIALS_KV.put(key, value, opts);
    return;
  }
  // 其他 key 前綴拒絕寫入（安全邊界）
}

/**
 * RSASSA-PKCS1-v1_5 + SHA-256 簽章。private key 以 PKCS8 bytes 傳入（由 WASM 零件解析 PEM 後送進來）。
 */
async function rsaPkcs1Sha256Sign(data: Uint8Array, pkcs8: Uint8Array): Promise<Uint8Array> {
  const cryptoKey = await crypto.subtle.importKey(
    'pkcs8',
    pkcs8,
    { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },
    false,
    ['sign'],
  );
  const sig = await crypto.subtle.sign('RSASSA-PKCS1-v1_5', cryptoKey, data);
  return new Uint8Array(sig);
}

/**
 * 讀取 CF Workers per-script Secrets 的值：`env[ref]` 動態字串索引
 * （credential-store-migration T4，§2.5/§5；T1.5 spike ② 已證此場景成立，零網路呼叫）。
 *
 * 安全邊界（比照 `routedKvGet` 的前綴檢查精神；design.md §2.3「secret_ref 命名需以 CRED_
 * 前綴隔離命名空間」）：只允許讀 `CRED_` 開頭的 ref。WASM 不該、也不需要讀到 worker 本身的
 * 其他機密（`CF_SECRETS_API_TOKEN` 等非 credential 用途的 env var）。
 * 不符前綴或值非字串 → 回傳 null（與 `kv_get` 的「拒絕/找不到」語意一致）。
 */
function secretGet(env: ArcrunHostEnv, ref: string): string | null {
  if (!/^CRED_/.test(ref)) return null;
  const value = env[ref];
  return typeof value === 'string' ? value : null;
}

/**
 * 建立 arcrun host function 組合（kv_get / kv_put / crypto_sign_rs256 / secret_get）。
 * 由 WASM runner（component-loader 的 WASM 路徑）呼叫，與 api_key 綁定以做越權檢查。
 *
 * http_request 不由本 factory 提供 — auth primitive WASM 與 API WASM 零件若需要
 * 發 HTTP，由呼叫者（component-loader）另外注入，以便個別限制可達主機。
 */
export function createArcrunHostFunctions(env: ArcrunHostEnv, apiKey: string): WasiHostFunctions {
  return {
    kv_get: (key: string) => routedKvGet(env, apiKey, key),
    kv_put: (key: string, value: string, ttlSeconds: number) => routedKvPut(env, apiKey, key, value, ttlSeconds),
    crypto_sign_rs256: (data: Uint8Array, pkcs8: Uint8Array) => rsaPkcs1Sha256Sign(data, pkcs8),
    secret_get: async (ref: string) => secretGet(env, ref),
  };
}
