// Generated by bynkc — do not edit by hand.
// bynk runtime — shared by every emitted module.

export type Result<T, E> =
  | { readonly tag: "Ok"; readonly value: T }
  | { readonly tag: "Err"; readonly error: E };

export const Ok = <T>(value: T): Result<T, never> => ({ tag: "Ok", value });
export const Err = <E>(error: E): Result<never, E> => ({ tag: "Err", error });

export type Option<T> =
  | { readonly tag: "Some"; readonly value: T }
  | { readonly tag: "None" };

export const Some = <T>(value: T): Option<T> => ({ tag: "Some", value });
export const None: Option<never> = { tag: "None" };

export interface ValidationError {
  readonly field: string;
  readonly message: string;
  readonly value: unknown;
}

// v0.22b: the JSON-decode error (ADR 0047) — BoundaryError's information
// (the discriminating kind and the tracked field path) flattened into a
// uniform, Bynk-inspectable record. `kind` is "Malformed" for a JSON.parse
// failure, else the BoundaryError kind ("StructuralMismatch",
// "RefinementViolation").
export interface JsonError {
  readonly kind: string;
  readonly path: string;
  readonly message: string;
}

export interface DurableObjectStorage {
  get<T>(key: string): Promise<T | undefined>;
  put(key: string, value: unknown): Promise<void>;
  delete(key: string): Promise<boolean>;
  list<T>(options?: { prefix?: string }): Promise<Map<string, T>>;
}

export interface DurableObjectState {
  readonly storage: DurableObjectStorage;
  readonly id: { readonly name: string };
}

export class InMemoryStorage implements DurableObjectStorage {
  private data = new Map<string, unknown>();

  async get<T>(key: string): Promise<T | undefined> {
    return this.data.get(key) as T | undefined;
  }

  async put(key: string, value: unknown): Promise<void> {
    this.data.set(key, value);
  }

  async delete(key: string): Promise<boolean> {
    return this.data.delete(key);
  }

  async list<T>(options?: { prefix?: string }): Promise<Map<string, T>> {
    const prefix = options?.prefix ?? "";
    const out = new Map<string, T>();
    for (const [k, v] of this.data) {
      if (k.startsWith(prefix)) out.set(k, v as T);
    }
    return out;
  }
}

export function makeTestState(name: string): DurableObjectState {
  return {
    storage: new InMemoryStorage(),
    id: { name },
  };
}

// v0.8: cross-Worker boundary protocol — JSON wire format and error types.

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

export type BoundaryError =
  | { readonly kind: "MalformedJson"; readonly details: string }
  | {
      readonly kind: "StructuralMismatch";
      readonly path: string;
      readonly expected: string;
      readonly actual: string;
    }
  | {
      readonly kind: "RefinementViolation";
      readonly path: string;
      readonly violation: ValidationError;
    }
  | { readonly kind: "Transport"; readonly status: number; readonly details: string };

export function boundaryError(error: BoundaryError): Error {
  const e = new Error(`BoundaryError: ${error.kind}`);
  (e as any).boundaryError = error;
  return e;
}

export interface ServiceBinding {
  fetch(request: Request): Promise<Response>;
}

export interface KVNamespace {
  get(key: string): Promise<string | null>;
  put(key: string, value: string, options?: { expirationTtl?: number }): Promise<void>;
  delete(key: string): Promise<void>;
  // v0.23: the page shape WorkersKv's drain consumes (0050).
  list(options?: { prefix?: string; cursor?: string }): Promise<{
    keys: { name: string }[];
    list_complete: boolean;
    cursor?: string;
  }>;
}

export async function callService<T, E>(
  binding: ServiceBinding,
  servicePath: string,
  argsJson: JsonValue,
  deserialiseResult: (json: JsonValue) => Result<Result<T, E>, BoundaryError>,
  // v0.54: the calling context's qualified name, stamped beside the args so the
  // callee's `by c: Caller` handler can present a live `CallerId` (Q7). A
  // compile-time constant; the args body itself is unchanged. The `Internal`
  // channel trusts the binding, so this is identity, not authentication.
  callerContext: string = "",
): Promise<Result<T, E>> {
  const request = new Request(`http://internal/_bynk/call/${servicePath}`, {
    method: "POST",
    headers: { "content-type": "application/json", "X-Bynk-Caller": callerContext },
    body: JSON.stringify(argsJson),
  });
  const response = await binding.fetch(request);
  if (!response.ok) {
    throw boundaryError({
      kind: "Transport",
      status: response.status,
      details: await response.text(),
    });
  }
  let responseJson: JsonValue;
  try {
    responseJson = (await response.json()) as JsonValue;
  } catch (e) {
    throw boundaryError({ kind: "MalformedJson", details: String(e) });
  }
  const result = deserialiseResult(responseJson);
  if (result.tag === "Err") {
    throw boundaryError(result.error);
  }
  return result.value;
}

// v0.9: HttpResult — the built-in HTTP-result sum.

export type HttpResult<T> =
  | { readonly tag: "Ok"; readonly value: T }
  | { readonly tag: "Created"; readonly value: T }
  | { readonly tag: "NoContent" }
  | { readonly tag: "BadRequest"; readonly message: string }
  | { readonly tag: "Unauthorized" }
  | { readonly tag: "Forbidden" }
  | { readonly tag: "NotFound" }
  | { readonly tag: "Conflict"; readonly message: string }
  | { readonly tag: "UnprocessableEntity"; readonly message: string }
  | { readonly tag: "ServerError"; readonly message: string };

export const HttpResult = {
  Ok: <T>(value: T): HttpResult<T> => ({ tag: "Ok", value }),
  Created: <T>(value: T): HttpResult<T> => ({ tag: "Created", value }),
  NoContent: { tag: "NoContent" } as HttpResult<never>,
  BadRequest: (message: string): HttpResult<never> => ({ tag: "BadRequest", message }),
  Unauthorized: { tag: "Unauthorized" } as HttpResult<never>,
  Forbidden: { tag: "Forbidden" } as HttpResult<never>,
  NotFound: { tag: "NotFound" } as HttpResult<never>,
  Conflict: (message: string): HttpResult<never> => ({ tag: "Conflict", message }),
  UnprocessableEntity: (message: string): HttpResult<never> => ({
    tag: "UnprocessableEntity",
    message,
  }),
  ServerError: (message: string): HttpResult<never> => ({ tag: "ServerError", message }),
};

// v0.44: QueueResult — the built-in queue verdict sum (non-generic). `Ack`
// confirms the message; `Retry` redelivers it, carrying a reason for the log.
export type QueueResult =
  | { readonly tag: "Ack" }
  | { readonly tag: "Retry"; readonly reason: string };

export const QueueResult = {
  Ack: { tag: "Ack" } as QueueResult,
  Retry: (reason: string): QueueResult => ({ tag: "Retry", reason }),
};

// Match a path pattern (e.g., "/orders/:id") against a request path.
// Returns the captured parameter map, or null on no match.
export function matchPath(
  pattern: string,
  path: string,
): { params: Record<string, string> } | null {
  const patternSegments = pattern.split("/").filter(Boolean);
  const pathSegments = path.split("/").filter(Boolean);
  if (patternSegments.length !== pathSegments.length) return null;
  const params: Record<string, string> = {};
  for (let i = 0; i < patternSegments.length; i++) {
    const p = patternSegments[i];
    if (p.startsWith(":")) {
      params[p.slice(1)] = decodeURIComponent(pathSegments[i]);
    } else if (p !== pathSegments[i]) {
      return null;
    }
  }
  return { params };
}

// Serialise an HttpResult<T> to a Response. The variant determines the
// HTTP status code; the body (if any) is JSON-encoded.
export function httpResultToResponse<T>(
  result: HttpResult<T>,
  serialiseValue: (v: T) => JsonValue,
): Response {
  switch (result.tag) {
    case "Ok":
      return new Response(JSON.stringify(serialiseValue(result.value)), {
        status: 200,
        headers: { "content-type": "application/json" },
      });
    case "Created":
      return new Response(JSON.stringify(serialiseValue(result.value)), {
        status: 201,
        headers: { "content-type": "application/json" },
      });
    case "NoContent":
      return new Response(null, { status: 204 });
    case "BadRequest":
      return new Response(JSON.stringify({ error: result.message }), {
        status: 400,
        headers: { "content-type": "application/json" },
      });
    case "Unauthorized":
      return new Response(null, { status: 401 });
    case "Forbidden":
      return new Response(null, { status: 403 });
    case "NotFound":
      return new Response(null, { status: 404 });
    case "Conflict":
      return new Response(JSON.stringify({ error: result.message }), {
        status: 409,
        headers: { "content-type": "application/json" },
      });
    case "UnprocessableEntity":
      return new Response(JSON.stringify({ error: result.message }), {
        status: 422,
        headers: { "content-type": "application/json" },
      });
    case "ServerError":
      return new Response(JSON.stringify({ error: result.message }), {
        status: 500,
        headers: { "content-type": "application/json" },
      });
  }
}

// v0.9.2: agent instantiation + per-key state lifecycle.
//
// An agent keyed by some value lowers to a *lookup-or-create* of the durable
// state for that key. In bundle mode the per-agent `StateRegistry` holds an
// in-memory `DurableObjectState` per serialised key (same key → same state
// within a session; reset per test). In workers mode the agent is a Durable
// Object: `makeWorkersAgent` returns a typed proxy over the DO stub whose
// method calls route through `callDurableObjectMethod`. `makeAgent` picks the
// path: a present binding means workers, an absent one means bundle.

// A minimal structural view of the Cloudflare Durable Object namespace/stub
// surface. The real runtime is richer but structurally compatible.
export interface DurableObjectStub {
  fetch(input: string, init?: unknown): Promise<Response>;
}

export interface DurableObjectNamespace {
  idFromName(name: string): unknown;
  get(id: unknown): DurableObjectStub;
}

// Serialise an agent key to a stable string. Two semantically-equal keys must
// serialise identically: a string is itself, a primitive is its JSON form, a
// record is canonical JSON with sorted fields.
export function serialiseAgentKey(value: unknown): string {
  if (typeof value === "string") return value;
  if (typeof value === "number" || typeof value === "boolean" || value === null) {
    return JSON.stringify(value);
  }
  if (Array.isArray(value)) {
    return JSON.stringify(value.map((v) => serialiseAgentKey(v)));
  }
  if (typeof value === "object") {
    const obj = value as { [k: string]: unknown };
    const keys = Object.keys(obj).sort();
    return JSON.stringify(keys.map((k) => [k, serialiseAgentKey(obj[k])]));
  }
  return JSON.stringify(value);
}

// Per-agent registry: serialised key → in-memory durable state. Used in bundle
// mode (and in `bynkc test`). `reset()` clears every state so a fresh test
// sees a clean slate.
export class StateRegistry<K> {
  private states = new Map<string, DurableObjectState>();

  getOrCreate(key: K): DurableObjectState {
    const sk = serialiseAgentKey(key);
    let state = this.states.get(sk);
    if (state === undefined) {
      state = makeTestState(sk);
      this.states.set(sk, state);
    }
    return state;
  }

  reset(): void {
    this.states.clear();
  }
}

// Workers-mode agent method call: route through the DO stub's `fetch` under
// the `/_bynk/agent/<method>` wire protocol.
export async function callDurableObjectMethod<R>(
  stub: DurableObjectStub,
  method: string,
  args: unknown[],
  deps: unknown,
): Promise<R> {
  const response = await stub.fetch(`https://_bynk/_bynk/agent/${method}`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ args, deps }),
  });
  if (!response.ok) throw new Error(await response.text());
  return (await response.json()) as R;
}

// Workers-mode agent: a typed proxy over the DO stub. Each method access
// returns a function that splits its final argument off as `deps` and routes
// the rest as method args through `callDurableObjectMethod`. The `as C` cast
// gives call sites the agent's real method signatures, so user code reads
// identically to bundle mode.
export function makeWorkersAgent<C>(binding: DurableObjectNamespace, key: unknown): C {
  const stub = binding.get(binding.idFromName(serialiseAgentKey(key)));
  const proxy = new Proxy(
    {},
    {
      get(_target, prop: string | symbol) {
        if (typeof prop !== "string") return undefined;
        return (...callArgs: unknown[]) => {
          const deps = callArgs.length > 0 ? callArgs[callArgs.length - 1] : {};
          const args = callArgs.length > 0 ? callArgs.slice(0, -1) : [];
          return callDurableObjectMethod(stub, prop, args, deps);
        };
      },
    },
  );
  return proxy as C;
}

// Single agent-construction helper. A present DO binding selects the workers
// path; otherwise the bundle registry path. Call sites are identical across
// targets.
export function makeAgent<C>(
  registry: StateRegistry<unknown>,
  binding: DurableObjectNamespace | undefined,
  key: unknown,
  constructBundle: (state: DurableObjectState) => C,
): C {
  if (binding !== undefined) {
    return makeWorkersAgent<C>(binding, key);
  }
  const state = registry.getOrCreate(key);
  return constructBundle(state);
}

// v0.16: an in-process Durable-Object namespace for multi-Worker integration
// tests. `construct` builds the emitted DO class from a fresh in-memory state;
// one instance is kept per key (so state accumulates within a test case). The
// returned stub bridges the stub-side `fetch(url, init)` the agent runtime
// speaks to the DO class's server-side `fetch(request)`.
export function makeIntegrationDoNamespace(
  construct: (state: DurableObjectState) => { fetch(request: Request): Promise<Response> },
): DurableObjectNamespace {
  const instances = new Map<string, { fetch(request: Request): Promise<Response> }>();
  return {
    idFromName(name: string): unknown {
      return name;
    },
    get(id: unknown): DurableObjectStub {
      const k = String(id);
      let inst = instances.get(k);
      if (inst === undefined) {
        inst = construct(makeTestState(k));
        instances.set(k, inst);
      }
      const target = inst;
      return {
        fetch: (input: string, init?: unknown): Promise<Response> =>
          target.fetch(new Request(input, init as RequestInit)),
      };
    },
  };
}

// v0.47: Bearer-token verification (the actors slice-2 seam). Verifies a JWT's
// HS256 signature against `secret` using WebCrypto (constant-time
// `crypto.subtle.verify`), enforces `exp`/`nbf`, and returns the `sub` claim.
// Any failure is an `Err` the caller maps to 401 — fail-closed. The raw token
// never leaves this function; only the verified `sub` flows out.
function __bynkB64UrlToBytes(s: string): Uint8Array {
  const b64 = s.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(s.length / 4) * 4, "=");
  const bin = atob(b64);
  const out = new Uint8Array(bin.length);
  for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
  return out;
}

export async function verifyBearerJwtHs256(
  token: string,
  secret: string,
): Promise<Result<{ readonly sub: string; readonly claims: Record<string, unknown> }, string>> {
  const parts = token.split(".");
  if (parts.length !== 3) return Err("malformed token");
  const [headerB64, payloadB64, sigB64] = parts;
  let header: { alg?: unknown };
  try {
    header = JSON.parse(new TextDecoder().decode(__bynkB64UrlToBytes(headerB64)));
  } catch {
    return Err("malformed header");
  }
  // Reject algorithm confusion / `alg: none` — this seam only verifies HS256.
  if (header.alg !== "HS256") return Err("unsupported alg");
  const enc = new TextEncoder();
  const key = await crypto.subtle.importKey(
    "raw",
    enc.encode(secret) as BufferSource,
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["verify"],
  );
  let ok: boolean;
  try {
    ok = await crypto.subtle.verify(
      "HMAC",
      key,
      __bynkB64UrlToBytes(sigB64) as BufferSource,
      enc.encode(`${headerB64}.${payloadB64}`) as BufferSource,
    );
  } catch {
    return Err("verify failed");
  }
  if (!ok) return Err("bad signature");
  let payload: { sub?: unknown; exp?: unknown; nbf?: unknown };
  try {
    payload = JSON.parse(new TextDecoder().decode(__bynkB64UrlToBytes(payloadB64)));
  } catch {
    return Err("malformed payload");
  }
  const now = Math.floor(Date.now() / 1000);
  // RFC 7519: `exp`/`nbf` are NumericDate (a number). A present-but-non-number
  // claim is malformed — reject rather than silently skip the time check.
  if (payload.exp !== undefined && typeof payload.exp !== "number") return Err("malformed exp");
  if (payload.exp !== undefined && (payload.exp as number) < now) return Err("token expired");
  if (payload.nbf !== undefined && typeof payload.nbf !== "number") return Err("malformed nbf");
  if (payload.nbf !== undefined && (payload.nbf as number) > now) return Err("token not yet valid");
  if (typeof payload.sub !== "string" || payload.sub.length === 0) return Err("missing sub");
  // v0.53: surface the full verified claims for refinement-actor authorisation
  // (`actor Admin = User where hasClaim(...)`). The identity stays `sub`-minted
  // and sealed; claims are an authorisation-time input, checked at the boundary.
  return Ok({ sub: payload.sub, claims: payload as Record<string, unknown> });
}

// v0.51: Signature (webhook) verification — recompute an HMAC-SHA256 over the
// raw body (or `<timestamp>.<body>` when a timestamp is bound) and compare it,
// constant-time (`crypto.subtle.verify`), against the request's signature
// header (accepting a `sha256=<hex>` prefix or a bare hex digest). When a
// timestamp is bound, it is signed (so it cannot be forged) and checked within
// `toleranceSecs` for replay defence. Returns `true` iff the request is
// authentic; the caller maps `false` to 401. The body never reaches the handler
// unverified.
function __bynkHexToBytes(hex: string): Uint8Array {
  const clean = hex.startsWith("sha256=") ? hex.slice(7) : hex;
  if (clean.length === 0 || clean.length % 2 !== 0 || /[^0-9a-fA-F]/.test(clean)) {
    return new Uint8Array(0);
  }
  const out = new Uint8Array(clean.length / 2);
  for (let i = 0; i < out.length; i++) out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
  return out;
}

export async function verifySignatureHmacSha256(
  body: string,
  secret: string,
  signatureHeader: string | null,
  timestamp: string | null,
  toleranceSecs: number | null,
): Promise<boolean> {
  if (signatureHeader === null) return false;
  // When a timestamp is bound it is part of the signed string (so it cannot be
  // forged); a tolerance, if set, bounds replay.
  let signingString = body;
  if (timestamp !== null) {
    const ts = Number(timestamp);
    if (!Number.isFinite(ts)) return false;
    if (toleranceSecs !== null) {
      const now = Math.floor(Date.now() / 1000);
      if (Math.abs(now - ts) > toleranceSecs) return false;
    }
    signingString = `${timestamp}.${body}`;
  }
  const sigBytes = __bynkHexToBytes(signatureHeader);
  if (sigBytes.length === 0) return false;
  const enc = new TextEncoder();
  const key = await crypto.subtle.importKey(
    "raw",
    enc.encode(secret) as BufferSource,
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["verify"],
  );
  try {
    return await crypto.subtle.verify(
      "HMAC",
      key,
      sigBytes as BufferSource,
      enc.encode(signingString) as BufferSource,
    );
  } catch {
    return false;
  }
}
