// Reference payer. A muse (or any program) wraps fetch with this: on 402 it reads the challenge, signs a
// Permit2 transfer to the merchant for the exact amount, and retries. Requires one prior USDG approval to
// Permit2 from the payer's account. Only dependency: viem.
import type { Account, Hex, WalletClient } from 'viem';
import { HEADERS, SPENDER, USDG, encode, decode, type Requirement, type Authorization } from './protocol.ts';
import { typedData } from './permit2.ts';

const randomNonce = () => BigInt('0x' + Array.from(crypto.getRandomValues(new Uint8Array(32))).map((b) => b.toString(16).padStart(2, '0')).join(''));

export async function signPayment(wallet: WalletClient, account: Account, req: Requirement) {
  const now = Math.floor(Date.now() / 1000);
  const authorization: Authorization = {
    permitted: { token: USDG, amount: req.amount },
    from: account.address, spender: SPENDER, nonce: randomNonce().toString(),
    deadline: String(now + (req.maxTimeoutSeconds || 300)),
    witness: { to: req.payTo, validAfter: String(now - 30) },
  };
  const signature: Hex = await wallet.signTypedData({ account, ...typedData(authorization) } as any);
  return { rh777: 1 as const, accepted: req, payload: { signature, authorization } };
}

export function payingFetch(wallet: WalletClient, account: Account, opts: { maxUsdg?: string } = {}) {
  const cap = opts.maxUsdg ? BigInt(Math.round(Number(opts.maxUsdg) * 1e6)) : null;
  return async (input: string | URL, init: RequestInit = {}): Promise<Response> => {
    const first = await fetch(input, init);
    if (first.status !== 402) return first;
    const raw = first.headers.get(HEADERS.challenge);
    const body = raw ? decode<any>(raw) : await first.clone().json().catch(() => null);
    const req: Requirement | undefined = body?.accepts?.find((a: Requirement) => a.network === 'eip155:4663' && a.asset?.toLowerCase() === USDG.toLowerCase());
    if (!req) return first;
    if (cap !== null && BigInt(req.amount) > cap) throw new Error(`price ${req.amount} exceeds the payer cap`);
    const payment = await signPayment(wallet, account, req);
    const headers = new Headers(init.headers || {});
    headers.set(HEADERS.payment, encode(payment));
    return fetch(input, { ...init, headers });
  };
}
