/**
 * SML x402 SDK Starter — MIT licensed
 * https://x402.scriptmasterlabs.com/sdk/LICENSE
 *
 * These helpers inspect public x402 responses. They deliberately never hold a
 * private key, sign an authorization, or retry a paid request on your behalf.
 */

export const SML_X402_REGISTRY = "https://x402.scriptmasterlabs.com/api/x402-registry";
export const SML_X402_DISCOVERY = "https://x402.scriptmasterlabs.com/api/x402-discovery";

export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };

export type PaymentChallenge = {
  headerName: string | null;
  raw: string | null;
  decoded: JsonValue | null;
};

export type ProbeResult = {
  url: string;
  status: number;
  statusText: string;
  headers: Record<string, string>;
  body: string;
  payment: PaymentChallenge;
};

function base64Json(value: string): JsonValue | null {
  try {
    const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
    const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
    const bytes = Uint8Array.from(atob(padded), (character) => character.charCodeAt(0));
    return JSON.parse(new TextDecoder().decode(bytes)) as JsonValue;
  } catch {
    return null;
  }
}

export function readPaymentChallenge(headers: Headers): PaymentChallenge {
  for (const name of ["payment-required", "x-payment-required", "x402-payment-required"]) {
    const raw = headers.get(name);
    if (raw) return { headerName: name, raw, decoded: base64Json(raw) };
  }
  return { headerName: null, raw: null, decoded: null };
}

/** Fetches SML's unified live-evidence registry. */
export async function getRegistry(fetcher: typeof fetch = fetch): Promise<JsonValue> {
  const response = await fetcher(SML_X402_REGISTRY, {
    headers: { accept: "application/json" },
    cache: "no-store",
  });
  if (!response.ok) throw new Error(`Registry returned HTTP ${response.status}`);
  return (await response.json()) as JsonValue;
}

/**
 * Makes an unpaid public request and returns a 402 challenge if the provider
 * sends one. Your application decides whether it trusts the terms and whether
 * its own wallet should sign and retry directly with the provider.
 */
export async function probeRoute(
  url: string,
  init: RequestInit = {},
  fetcher: typeof fetch = fetch,
): Promise<ProbeResult> {
  const headers = new Headers(init.headers);
  if (!headers.has("accept")) headers.set("accept", "application/json, text/plain;q=0.9, */*;q=0.5");
  const response = await fetcher(url, {
    ...init,
    headers,
    redirect: "manual",
  });

  return {
    url,
    status: response.status,
    statusText: response.statusText,
    headers: Object.fromEntries(response.headers.entries()),
    body: await response.text(),
    payment: readPaymentChallenge(response.headers),
  };
}
