import { core } from "./core";
import type { ApprovalRecord, ContractlessNetwork, StoredSettings, WalletStatus } from "./types";

const WALLET_KEY = "contractlessWallet";
const WALLETS_KEY = "contractlessWallets";
const SETTINGS_KEY = "contractlessSettings";
const LOCK_ALARM = "contractlessAutoLock";
const UNLOCKED_SESSION_KEY = "contractlessUnlockedWallet";
const APP_SESSIONS_KEY = "contractlessApplicationSessions";
const APPROVAL_TIMEOUT_MS = 120_000;
const MAX_PENDING_APPROVALS = 10;
const DEFAULT_SETTINGS: StoredSettings = {
  selectedNetwork: "testnet",
  testnetApiUrl: "https://api.contractless.dev",
  mainnetApiUrl: "",
  apiUrl: "https://api.contractless.dev",
  autoLockMinutes: 60
};

type UnlockedWallet = {
  walletId: string;
  sessionId: string;
  short_address: string;
  public_key: string;
  private_key: string;
  network: ContractlessNetwork;
  expiresAt: number;
};

let unlocked: UnlockedWallet | undefined;
const PENDING_APPROVAL_KEY = "pending-provider-approvals";

type ApplicationSession = {
  origin: string;
  appId: string;
  accessKey: string;
  walletId: string;
  unlockSessionId: string;
  createdAt: number;
};

type StoredWallet = {
  id: string;
  label: string;
  address: string;
  publicKey: string;
  walletJson: string;
  network: ContractlessNetwork;
};

type StoredWalletCollection = {
  version: 2;
  activeWalletIds: Partial<Record<ContractlessNetwork, string>>;
  wallets: StoredWallet[];
};

async function settings(): Promise<StoredSettings> {
  const stored = await chrome.storage.local.get(SETTINGS_KEY);
  const previous = (stored[SETTINGS_KEY] ?? {}) as Partial<StoredSettings>;
  const selectedNetwork = previous.selectedNetwork === "mainnet" ? "mainnet" : "testnet";
  const testnetApiUrl = previous.testnetApiUrl ?? previous.apiUrl ?? DEFAULT_SETTINGS.testnetApiUrl;
  const mainnetApiUrl = previous.mainnetApiUrl ?? "";
  return {
    selectedNetwork,
    testnetApiUrl,
    mainnetApiUrl,
    apiUrl: selectedNetwork === "mainnet" ? mainnetApiUrl : testnetApiUrl,
    autoLockMinutes: previous.autoLockMinutes ?? DEFAULT_SETTINGS.autoLockMinutes
  };
}

function requireApiUrl(configured: StoredSettings): string {
  if (!configured.apiUrl) {
    throw new Error(`No ${configured.selectedNetwork} API is configured.`);
  }
  return configured.apiUrl;
}

function networkFromAddress(address: string): ContractlessNetwork {
  if (address.endsWith(".cltc")) return "testnet";
  if (address.endsWith(".clc")) return "mainnet";
  throw new Error("The wallet address does not identify a supported Contractless network.");
}

function walletMetadata(walletJson: string): { address: string; publicKey: string; network: ContractlessNetwork } {
  const wallet = JSON.parse(walletJson) as {
    short_address?: string;
    public_key?: string;
  };
  if (typeof wallet.short_address !== "string" || typeof wallet.public_key !== "string") {
    throw new Error("The encrypted wallet metadata is invalid.");
  }
  return {
    address: wallet.short_address,
    publicKey: wallet.public_key,
    network: networkFromAddress(wallet.short_address)
  };
}

async function walletCollection(): Promise<StoredWalletCollection> {
  const stored = await chrome.storage.local.get([WALLETS_KEY, WALLET_KEY]);
  const existing = stored[WALLETS_KEY] as (
    StoredWalletCollection
    | { version: 1; activeWalletId?: string; wallets: Array<Omit<StoredWallet, "network">> }
    | undefined
  );
  if (existing?.version === 2 && Array.isArray(existing.wallets)) {
    return existing;
  }
  if (existing?.version === 1 && Array.isArray(existing.wallets)) {
    const old = existing;
    const wallets = old.wallets.map((wallet) => ({
      ...wallet,
      network: networkFromAddress(wallet.address)
    }));
    const active = wallets.find((wallet) => wallet.id === old.activeWalletId);
    const migrated: StoredWalletCollection = {
      version: 2,
      activeWalletIds: active ? { [active.network]: active.id } : {},
      wallets
    };
    await saveWalletCollection(migrated);
    return migrated;
  }

  const legacyWallet = stored[WALLET_KEY];
  if (typeof legacyWallet !== "string") {
    return { version: 2, activeWalletIds: {}, wallets: [] };
  }

  const metadata = walletMetadata(legacyWallet);
  const migrated: StoredWallet = {
    id: crypto.randomUUID(),
    label: "Contractless Account 1",
    address: metadata.address,
    publicKey: metadata.publicKey,
    walletJson: legacyWallet,
    network: metadata.network
  };
  const collection: StoredWalletCollection = {
    version: 2,
    activeWalletIds: { [metadata.network]: migrated.id },
    wallets: [migrated]
  };
  await chrome.storage.local.set({ [WALLETS_KEY]: collection });
  await chrome.storage.local.remove(WALLET_KEY);
  return collection;
}

async function saveWalletCollection(collection: StoredWalletCollection): Promise<void> {
  await chrome.storage.local.set({ [WALLETS_KEY]: collection });
}

function activeStoredWallet(
  collection: StoredWalletCollection,
  network: ContractlessNetwork
): StoredWallet | undefined {
  const candidates = collection.wallets.filter((wallet) => wallet.network === network);
  return candidates.find((wallet) => wallet.id === collection.activeWalletIds[network])
    ?? candidates[0];
}

async function addWallet(walletJson: string): Promise<StoredWallet> {
  const collection = await walletCollection();
  const metadata = walletMetadata(walletJson);
  const duplicate = collection.wallets.find((wallet) => wallet.address === metadata.address);

  if (duplicate) {
    duplicate.walletJson = walletJson;
    duplicate.publicKey = metadata.publicKey;
    collection.activeWalletIds[metadata.network] = duplicate.id;
    await saveWalletCollection(collection);
    return duplicate;
  }

  const wallet: StoredWallet = {
    id: crypto.randomUUID(),
    label: `Contractless Account ${collection.wallets.filter((candidate) => candidate.network === metadata.network).length + 1}`,
    address: metadata.address,
    publicKey: metadata.publicKey,
    walletJson,
    network: metadata.network
  };
  collection.wallets.push(wallet);
  collection.activeWalletIds[metadata.network] = wallet.id;
  await saveWalletCollection(collection);
  return wallet;
}

async function status(): Promise<WalletStatus> {
  await restoreUnlockedWallet();
  const collection = await walletCollection();
  const configured = await settings();
  const active = activeStoredWallet(collection, configured.selectedNetwork);
  const wallets = collection.wallets.filter((wallet) => wallet.network === configured.selectedNetwork);
  return {
    exists: wallets.length > 0,
    unlocked: Boolean(unlocked && unlocked.walletId === active?.id && unlocked.network === configured.selectedNetwork),
    activeWalletId: active?.id,
    activeWalletLabel: active?.label,
    address: unlocked?.short_address,
    publicKey: unlocked?.public_key,
    wallets: wallets.map((wallet) => ({
      id: wallet.id,
      label: wallet.label,
      address: wallet.address,
      publicKey: wallet.publicKey,
      network: wallet.network,
      active: wallet.id === active?.id
    })),
    selectedNetwork: configured.selectedNetwork,
    networkName: configured.selectedNetwork === "testnet" ? "Testnet" : "Mainnet",
    networkSymbol: configured.selectedNetwork === "testnet" ? "CLTC" : "CLC",
    testnetApiUrl: configured.testnetApiUrl,
    mainnetApiUrl: configured.mainnetApiUrl,
    apiUrl: configured.apiUrl,
    autoLockMinutes: configured.autoLockMinutes
  };
}

async function resetLockAlarm(): Promise<void> {
  if (!unlocked) return;
  const configured = await settings();
  unlocked.expiresAt = Date.now() + (configured.autoLockMinutes * 60_000);
  await chrome.storage.session.set({ [UNLOCKED_SESSION_KEY]: unlocked });
  await chrome.alarms.clear(LOCK_ALARM);
  await chrome.alarms.create(LOCK_ALARM, {
    when: unlocked.expiresAt
  });
}

function arrayBufferDataUrl(buffer: ArrayBuffer, contentType: string): string {
  const bytes = new Uint8Array(buffer);
  let binary = "";
  const chunkSize = 0x8000;
  for (let offset = 0; offset < bytes.length; offset += chunkSize) {
    binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize));
  }
  return `data:${contentType};base64,${btoa(binary)}`;
}

async function rememberUnlockedWallet(
  wallet: Omit<UnlockedWallet, "sessionId" | "expiresAt">
): Promise<void> {
  await clearApplicationSessions();
  unlocked = {
    ...wallet,
    sessionId: crypto.randomUUID(),
    expiresAt: 0
  };
  await resetLockAlarm();
}

async function registerWalletKeys(
  wasm: Awaited<ReturnType<typeof core>>,
  wallet: Pick<UnlockedWallet, "short_address" | "public_key" | "private_key">
): Promise<unknown> {
  const registration = JSON.parse(
    wasm.create_wallet_registration_from_keys(
      wallet.short_address,
      wallet.public_key,
      wallet.private_key
    )
  );
  const handshakeSignature = wasm.create_handshake_proof(
    wallet.public_key,
    wallet.private_key
  );
  const configured = await settings();
  if (!configured.apiUrl) {
    throw new Error(`No ${configured.selectedNetwork} API is configured.`);
  }
  const response = await fetch(`${requireApiUrl(configured)}/api/v1/addresses/register`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Contractless-Address": wallet.short_address,
      "X-Contractless-Public-Key": wallet.public_key,
      "X-Contractless-Signature": handshakeSignature
    },
    body: JSON.stringify({
      address: registration.short_address,
      public_key: registration.public_key,
      signature: registration.signature
    })
  });
  const result = await response.json().catch(() => undefined);
  if (!response.ok || result?.success !== true) {
    throw new Error(result?.error ?? `Wallet registration failed with HTTP ${response.status}.`);
  }
  return result.data;
}

async function ensureWalletRegistered(
  wasm: Awaited<ReturnType<typeof core>>,
  wallet: Pick<UnlockedWallet, "short_address" | "public_key" | "private_key">
): Promise<void> {
  const configured = await settings();
  if (!configured.apiUrl) return;
  const handshakeSignature = wasm.create_handshake_proof(
    wallet.public_key,
    wallet.private_key
  );
  const response = await fetch(
    `${requireApiUrl(configured)}/api/v1/addresses/registration?address=${encodeURIComponent(wallet.short_address)}`,
    {
      headers: {
        "X-Contractless-Address": wallet.short_address,
        "X-Contractless-Public-Key": wallet.public_key,
        "X-Contractless-Signature": handshakeSignature
      }
    }
  );
  const result = await response.json().catch(() => undefined);
  if (!response.ok || result?.success !== true) {
    throw new Error(
      result?.error ?? `Wallet registration lookup failed with HTTP ${response.status}.`
    );
  }
  if (result.data?.registered !== true) {
    await registerWalletKeys(wasm, wallet);
  }
}

async function restoreUnlockedWallet(): Promise<void> {
  const configured = await settings();
  if (unlocked) {
    if (unlocked.expiresAt > Date.now() && unlocked.network === configured.selectedNetwork) return;
    await lock();
    return;
  }

  const stored = await chrome.storage.session.get(UNLOCKED_SESSION_KEY);
  const candidate = stored[UNLOCKED_SESSION_KEY] as UnlockedWallet | undefined;
  if (!candidate || candidate.expiresAt <= Date.now()) {
    await chrome.storage.session.remove(UNLOCKED_SESSION_KEY);
    return;
  }
  if (candidate.network !== configured.selectedNetwork) {
    await chrome.storage.session.remove(UNLOCKED_SESSION_KEY);
    await clearApplicationSessions();
    return;
  }
  if (!candidate.sessionId) {
    candidate.sessionId = crypto.randomUUID();
    await clearApplicationSessions();
    await chrome.storage.session.set({ [UNLOCKED_SESSION_KEY]: candidate });
  }
  unlocked = candidate;
}

async function lock(): Promise<void> {
  if (unlocked) {
    unlocked.private_key = "";
    unlocked = undefined;
  }
  await chrome.storage.session.remove([
    UNLOCKED_SESSION_KEY,
    APP_SESSIONS_KEY
  ]);
  await chrome.alarms.clear(LOCK_ALARM);
}

async function applicationSessions(): Promise<ApplicationSession[]> {
  const stored = await chrome.storage.session.get(APP_SESSIONS_KEY);
  const sessions = stored[APP_SESSIONS_KEY];
  return Array.isArray(sessions) ? sessions as ApplicationSession[] : [];
}

async function saveApplicationSessions(
  sessions: ApplicationSession[]
): Promise<void> {
  await chrome.storage.session.set({ [APP_SESSIONS_KEY]: sessions });
}

async function clearApplicationSessions(): Promise<void> {
  await chrome.storage.session.remove(APP_SESSIONS_KEY);
}

function randomHex(byteLength: number): string {
  const bytes = crypto.getRandomValues(new Uint8Array(byteLength));
  return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
}

async function sha256Hex(value: string): Promise<string> {
  const digest = await crypto.subtle.digest(
    "SHA-256",
    new TextEncoder().encode(value)
  );
  return Array.from(
    new Uint8Array(digest),
    (byte) => byte.toString(16).padStart(2, "0")
  ).join("");
}

function providerParameters(request: ApprovalRecord): Record<string, unknown> {
  if (!request.params || typeof request.params !== "object") {
    return {};
  }
  return request.params as Record<string, unknown>;
}

function validatedApplicationId(value: unknown): string {
  const appId = String(value ?? "").trim();
  if (!/^[A-Za-z0-9_-]{5,32}$/.test(appId)) {
    throw new Error(
      "The application ID must contain 5 to 32 URL-safe characters."
    );
  }
  return appId;
}

function validateClaimedOrigin(
  params: Record<string, unknown>,
  actualOrigin: string
): void {
  if (String(params.origin ?? "") !== actualOrigin) {
    throw new Error("The application origin does not match the requesting website.");
  }
}

function connectionTimestamp(value: unknown): number {
  const timestamp = Number(value);
  const now = Math.floor(Date.now() / 1_000);
  if (
    !Number.isInteger(timestamp)
    || Math.abs(now - timestamp) > 300
  ) {
    throw new Error(
      "The application connection timestamp must be within five minutes."
    );
  }
  return timestamp;
}

function connectionNonce(value: unknown): string {
  const nonce = String(value ?? "").trim().toLowerCase();
  if (!/^[0-9a-f]{32,128}$/.test(nonce) || nonce.length % 2 !== 0) {
    throw new Error(
      "The application nonce must contain 16 to 64 random hexadecimal bytes."
    );
  }
  return nonce;
}

function connectionMessage(value: unknown): string {
  const message = String(value ?? "").trim();
  if (!message || new TextEncoder().encode(message).length > 75) {
    throw new Error(
      "The application connection message must contain 1 to 75 bytes."
    );
  }
  return message;
}

function signedTextMessage(value: unknown): string {
  const message = String(value ?? "").trim();
  if (!message || new TextEncoder().encode(message).length > 75) {
    throw new Error("A signed website message must contain 1 to 75 bytes.");
  }
  return message;
}

function approvalRisk(
  method: string,
  params: Record<string, unknown>
): ApprovalRecord["risk"] {
  if (
    method === "contractless_connect"
    || method === "contractless_signMessage"
  ) {
    return "safe";
  }
  const transaction = params.transaction;
  if (!transaction || typeof transaction !== "object") return "high";
  const txtype = Number((transaction as Record<string, unknown>).txtype);
  return Number.isInteger(txtype) && txtype >= 100 && txtype <= 113
    ? "low"
    : "high";
}

function websiteTransaction(value: unknown): Record<string, unknown> {
  if (!value || typeof value !== "object") {
    throw new Error("A complete transaction object is required.");
  }
  const transaction = value as Record<string, unknown>;
  const txtype = Number(transaction.txtype);
  if (!Number.isInteger(txtype)) {
    throw new Error("The transaction type is invalid.");
  }
  if (txtype === 6 || txtype === 7) {
    throw new Error(
      "Swaps and loan creation require a staged signing workflow."
    );
  }
  return transaction;
}

function websiteDualTransaction(value: unknown): Record<string, unknown> {
  if (!value || typeof value !== "object") {
    throw new Error("A complete or partially signed transaction object is required.");
  }
  const transaction = value as Record<string, unknown>;
  const unsigned = transaction.unsigned_swap
    ?? transaction.unsigned_loan_contract
    ?? transaction;
  if (!unsigned || typeof unsigned !== "object") {
    throw new Error("The unsigned transaction fields are missing.");
  }
  const txtype = Number((unsigned as Record<string, unknown>).txtype);
  if (txtype !== 6 && txtype !== 7) {
    throw new Error("Staged signing is only available for swaps and loan creation.");
  }
  return transaction;
}

async function ensureRegisteredTransactionParticipants(
  wasm: Awaited<ReturnType<typeof core>>,
  transaction: Record<string, unknown>
): Promise<void> {
  if (!unlocked) throw new Error("Wallet is locked.");
  const unsignedValue = transaction.unsigned_swap
    ?? transaction.unsigned_loan_contract
    ?? transaction;
  if (!unsignedValue || typeof unsignedValue !== "object") return;
  const unsigned = unsignedValue as Record<string, unknown>;
  const txtype = Number(unsigned.txtype);
  const participants: Array<[string, string]> = txtype === 2
    ? [["Receiving", String(unsigned.receiver ?? "")]]
    : txtype === 6
      ? [
          ["First swap participant", String(unsigned.sender1 ?? "")],
          ["Second swap participant", String(unsigned.sender2 ?? "")]
        ]
      : txtype === 7
        ? [
            ["Lender", String(unsigned.lender ?? "")],
            ["Borrower", String(unsigned.borrower ?? "")]
          ]
        : [];
  if (participants.length === 0) return;

  // Inspection performs canonical field validation before any API request.
  wasm.inspect_transaction_request(JSON.stringify(transaction));
  const configured = await settings();
  const signature = wasm.create_handshake_proof(
    unlocked.public_key,
    unlocked.private_key
  );
  const headers = {
    "X-Contractless-Address": unlocked.short_address,
    "X-Contractless-Public-Key": unlocked.public_key,
    "X-Contractless-Signature": signature
  };
  const checked = new Map<string, boolean>();
  for (const [label, address] of participants) {
    const canonical = address.trim().toLowerCase();
    let registered = checked.get(canonical);
    if (registered === undefined) {
      const response = await fetch(
        `${requireApiUrl(configured)}/api/v1/addresses/registration?address=${encodeURIComponent(canonical)}`,
        { headers }
      );
      const reply = await response.json().catch(() => undefined);
      if (!response.ok || reply?.success !== true) {
        throw new Error(
          reply?.error ?? `Address registration lookup failed with HTTP ${response.status}.`
        );
      }
      registered = reply.data?.registered === true;
      checked.set(canonical, registered);
    }
    if (!registered) throw new Error(`${label} wallet is not registered.`);
  }
}

async function broadcastSignedTransaction(
  wasm: Awaited<ReturnType<typeof core>>,
  signed: Record<string, unknown>
) {
  if (
    signed.complete !== true
    || typeof signed.bytes_hex !== "string"
    || typeof signed.hash !== "string"
  ) {
    throw new Error("The transaction could not be completed.");
  }
  if (!unlocked) throw new Error("Wallet is locked.");

  const configured = await settings();
  const handshakeSignature = wasm.create_handshake_proof(
    unlocked.public_key,
    unlocked.private_key
  );
  const response = await fetch(
    `${requireApiUrl(configured)}/api/v1/transactions/broadcast`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Contractless-Address": unlocked.short_address,
        "X-Contractless-Public-Key": unlocked.public_key,
        "X-Contractless-Signature": handshakeSignature
      },
      body: JSON.stringify({ transaction_hex: signed.bytes_hex })
    }
  );
  const reply = await response.json().catch(() => undefined);
  if (!response.ok || reply?.success !== true) {
    throw new Error(
      reply?.error ?? `Transaction broadcast failed with HTTP ${response.status}.`
    );
  }
  await resetLockAlarm();
  return {
    txid: signed.hash,
    bytes: signed.bytes_hex,
    broadcast: reply.data
  };
}

async function authorizedApplication(
  params: Record<string, unknown>,
  actualOrigin: string
): Promise<ApplicationSession> {
  if (!unlocked) throw new Error("Wallet is locked.");
  validateClaimedOrigin(params, actualOrigin);
  const appId = validatedApplicationId(params.appId);
  const accessKey = String(params.accessKey ?? "");
  const session = (await applicationSessions()).find((candidate) =>
    candidate.origin === actualOrigin
    && candidate.appId === appId
    && candidate.accessKey === accessKey
    && candidate.walletId === unlocked?.walletId
    && candidate.unlockSessionId === unlocked?.sessionId
  );
  if (!session) {
    throw new Error(
      "The application session is invalid or has expired. Reconnect the wallet."
    );
  }
  return session;
}

async function approvalQueue(): Promise<string[]> {
  const stored = await chrome.storage.session.get(PENDING_APPROVAL_KEY);
  const value = stored[PENDING_APPROVAL_KEY];
  const ids = Array.isArray(value)
    ? value.filter((id): id is string => typeof id === "string" && id.length > 0)
    : typeof value === "string" && value.length > 0
      ? [value]
      : [];
  if (ids.length === 0) return [];

  const approvalKeys = ids.map((id) => `approval:${id}`);
  const approvals = await chrome.storage.session.get(approvalKeys);
  const now = Date.now();
  const active: string[] = [];
  const expiredKeys: string[] = [];
  for (const id of ids) {
    const key = `approval:${id}`;
    const request = approvals[key] as ApprovalRecord | undefined;
    if (!request || now - request.createdAt >= APPROVAL_TIMEOUT_MS) {
      expiredKeys.push(key);
    } else {
      active.push(id);
    }
  }
  if (expiredKeys.length > 0 || active.length !== ids.length) {
    await chrome.storage.session.set({ [PENDING_APPROVAL_KEY]: active });
    if (expiredKeys.length > 0) await chrome.storage.session.remove(expiredKeys);
  }
  return active;
}

async function removeApprovalFromQueue(id: string): Promise<string[]> {
  const remaining = (await approvalQueue()).filter((candidate) => candidate !== id);
  await chrome.storage.session.set({ [PENDING_APPROVAL_KEY]: remaining });
  return remaining;
}

async function openApproval(request: ApprovalRecord): Promise<void> {
  const queue = await approvalQueue();
  const queuedRecords = await chrome.storage.session.get(
    queue.map((id) => `approval:${id}`)
  );
  const duplicateConnection = request.method === "contractless_connect"
    && queue.some((id) => {
      const candidate = queuedRecords[`approval:${id}`] as ApprovalRecord | undefined;
      if (!candidate || candidate.method !== "contractless_connect") return false;
      const candidateParams = providerParameters(candidate);
      const requestParams = providerParameters(request);
      return candidate.origin === request.origin
        && candidateParams.appId === requestParams.appId;
    });
  if (duplicateConnection) {
    throw new Error("This application already has a wallet connection request pending.");
  }
  if (queue.length >= MAX_PENDING_APPROVALS) {
    throw new Error("The wallet approval queue is full. Resolve a pending request first.");
  }
  await chrome.storage.session.set({
    [PENDING_APPROVAL_KEY]: [...queue, request.id],
    [`approval:${request.id}`]: request
  });
  try {
    await chrome.action.openPopup();
  } catch {
    // Browsers that cannot open the action automatically retain the request
    // so the user can open the wallet from the toolbar and approve it.
  }
}

async function waitForApproval(id: string): Promise<boolean> {
  const resultKey = `approval-result:${id}`;
  const expiresAt = Date.now() + APPROVAL_TIMEOUT_MS;
  while (Date.now() < expiresAt) {
    const stored = await chrome.storage.session.get(resultKey);
    if (stored[resultKey]) {
      await chrome.storage.session.remove(resultKey);
      return stored[resultKey].approved === true;
    }
    await new Promise((resolve) => setTimeout(resolve, 150));
  }
  await chrome.storage.session.remove([
    `approval:${id}`,
    resultKey
  ]);
  await removeApprovalFromQueue(id);
  throw new Error("Wallet approval request expired.");
}

function requestOrigin(sender: chrome.runtime.MessageSender): string {
  const candidate = sender.url ?? sender.origin;
  if (!candidate) throw new Error("The requesting website origin is unavailable.");
  const origin = new URL(candidate).origin;
  if (origin === "null") throw new Error("Requests from opaque origins are not supported.");
  return origin;
}

async function executeApprovedRequest(
  wasm: Awaited<ReturnType<typeof core>>,
  request: ApprovalRecord
) {
  if (!unlocked) throw new Error("Wallet is locked.");
  await resetLockAlarm();

  switch (request.method) {
    case "contractless_connect": {
      const params = providerParameters(request);
      validateClaimedOrigin(params, request.origin);
      const requestedSessionId = String(params.walletSessionId ?? "");
      if (requestedSessionId && requestedSessionId !== unlocked.sessionId) {
        throw new Error(
          "The active wallet changed while the connection was being approved."
        );
      }
      const appId = validatedApplicationId(params.appId);
      const requestedAt = connectionTimestamp(params.timestamp);
      const nonce = connectionNonce(params.nonce);
      const message = connectionMessage(params.message);
      const accessKey = randomHex(32);
      const accessKeyHash = await sha256Hex(accessKey);
      const now = Math.floor(Date.now() / 1_000);
      const proofExpiresAt = Math.min(
        now + 15 * 60,
        Math.floor(unlocked.expiresAt / 1_000)
      );
      const signedMessage = [
        "Contractless application connection",
        `Application ID: ${appId}`,
        `Origin: ${request.origin}`,
        `Requested timestamp: ${requestedAt}`,
        `Message: ${message}`,
        `Session key hash: ${accessKeyHash}`
      ].join("\n");
      const proof = JSON.parse(
        wasm.sign_message_request(
          JSON.stringify({
            origin: request.origin,
            message: signedMessage,
            nonce,
            issued_at: now,
            expires_at: proofExpiresAt
          }),
          unlocked.private_key,
          unlocked.public_key
        )
      ) as Record<string, unknown>;
      const session: ApplicationSession = {
        origin: request.origin,
        appId,
        accessKey,
        walletId: unlocked.walletId,
        unlockSessionId: unlocked.sessionId,
        createdAt: Date.now()
      };
      const sessions = await applicationSessions();
      sessions.push(session);
      await saveApplicationSessions(sessions);
      return {
        address: unlocked.short_address,
        publicKey: unlocked.public_key,
        signature: proof.signature,
        proof,
        accessKey,
        appId,
        origin: request.origin,
        sessionExpiresAt: unlocked.expiresAt
      };
    }
    case "contractless_signMessage": {
      const params = providerParameters(request);
      await authorizedApplication(params, request.origin);
      const now = Math.floor(Date.now() / 1000);
      const expiresAt = Number(params.expires_at ?? now + 300);
      const appId = validatedApplicationId(params.appId);
      const messageRequest = {
        origin: request.origin,
        message: [
          "Contractless application message",
          `Application ID: ${appId}`,
          `Message: ${signedTextMessage(params.message)}`
        ].join("\n"),
        nonce: String(params.nonce ?? ""),
        issued_at: now,
        expires_at: expiresAt
      };
      return JSON.parse(
        wasm.sign_message_request(
          JSON.stringify(messageRequest),
          unlocked.private_key,
          unlocked.public_key
        )
      );
    }
    case "contractless_sendTransaction": {
      const params = providerParameters(request);
      await authorizedApplication(params, request.origin);
      const transaction = websiteTransaction(params.transaction);
      const signed = JSON.parse(
        wasm.sign_transaction_request(
          JSON.stringify(transaction),
          unlocked.private_key,
          unlocked.public_key,
          1
        )
      ) as Record<string, unknown>;
      return broadcastSignedTransaction(wasm, signed);
    }
    case "contractless_signDualTransaction": {
      const params = providerParameters(request);
      await authorizedApplication(params, request.origin);
      const transaction = websiteDualTransaction(params.transaction);
      const signerSlot = Number(params.signerSlot);
      if (signerSlot !== 1 && signerSlot !== 2) {
        throw new Error("The signer slot must be 1 or 2.");
      }
      const signed = JSON.parse(
        wasm.sign_transaction_request(
          JSON.stringify(transaction),
          unlocked.private_key,
          unlocked.public_key,
          signerSlot
        )
      ) as Record<string, unknown>;
      return {
        complete: false,
        txid: signed.hash,
        unsignedBytes: signed.unsigned_bytes_hex,
        canonicalUnsignedJson: JSON.parse(
          wasm.inspect_transaction_request(JSON.stringify(transaction))
        ).canonical_unsigned_json,
        signedTransaction: signed.signed_transaction,
        signature: signerSlot === 1 ? signed.signature1 : signed.signature2,
        signature1: signed.signature1,
        signature2: signed.signature2,
        signerSlot,
        signerPublicKey: unlocked.public_key
      };
    }
    default:
      throw new Error("The website requested an unsupported wallet method.");
  }
}

async function handle(message: Record<string, unknown>, sender: chrome.runtime.MessageSender) {
  const configured = await settings();
  if (message.type === "switch-network") {
    const selectedNetwork = message.network === "mainnet" ? "mainnet" : "testnet";
    await lock();
    const next = {
      ...configured,
      selectedNetwork,
      apiUrl: selectedNetwork === "mainnet"
        ? configured.mainnetApiUrl
        : configured.testnetApiUrl
    };
    await chrome.storage.local.set({ [SETTINGS_KEY]: next });
    return status();
  }
  const wasm = await core(configured.selectedNetwork);
  await restoreUnlockedWallet();
  switch (message.type) {
    case "status":
      return status();
    case "create-wallet": {
      const password = String(message.password);
      const walletJson = wasm.generate_browser_wallet(password);
      const opened = JSON.parse(wasm.unlock_browser_wallet(walletJson, password));
      const storedWallet = await addWallet(walletJson);
      await rememberUnlockedWallet({
        walletId: storedWallet.id,
        network: configured.selectedNetwork,
        ...opened
      });
      if (configured.apiUrl) {
        try {
          await registerWalletKeys(wasm, opened);
        } catch (error) {
          console.warn("Automatic wallet registration failed during creation.", error);
        }
      }
      return status();
    }
    case "import-wallet": {
      const walletJson = wasm.import_legacy_wallet(
        String(message.walletJson),
        String(message.legacyPassword),
        String(message.password)
      );
      const storedWallet = await addWallet(walletJson);
      const opened = JSON.parse(
        wasm.unlock_browser_wallet(walletJson, String(message.password))
      );
      await rememberUnlockedWallet({ walletId: storedWallet.id, network: configured.selectedNetwork, ...opened });
      return status();
    }
    case "import-wallet-image": {
      const password = String(message.password);
      const walletJson = wasm.import_private_key_image(
        String(message.imageBase64),
        String(message.imagePassword),
        password
      );
      const storedWallet = await addWallet(walletJson);
      const opened = JSON.parse(wasm.unlock_browser_wallet(walletJson, password));
      await rememberUnlockedWallet({ walletId: storedWallet.id, network: configured.selectedNetwork, ...opened });
      return status();
    }
    case "import-private-key": {
      const password = String(message.password);
      const walletJson = wasm.import_private_key(String(message.privateKey), password);
      const storedWallet = await addWallet(walletJson);
      const opened = JSON.parse(wasm.unlock_browser_wallet(walletJson, password));
      await rememberUnlockedWallet({ walletId: storedWallet.id, network: configured.selectedNetwork, ...opened });
      return status();
    }
    case "unlock": {
      const collection = await walletCollection();
      const wallet = activeStoredWallet(collection, configured.selectedNetwork);
      if (!wallet) throw new Error("No wallet exists.");
      const opened = JSON.parse(
        wasm.unlock_browser_wallet(wallet.walletJson, String(message.password))
      );
      await rememberUnlockedWallet({ walletId: wallet.id, network: configured.selectedNetwork, ...opened });
      try {
        await ensureWalletRegistered(wasm, opened);
      } catch (error) {
        console.warn("Automatic wallet registration failed during unlock.", error);
      }
      return status();
    }
    case "switch-wallet": {
      const collection = await walletCollection();
      const walletId = String(message.walletId);
      const selected = collection.wallets.find((wallet) => wallet.id === walletId);
      if (!selected || selected.network !== configured.selectedNetwork) {
        throw new Error("The selected wallet does not exist.");
      }
      await lock();
      collection.activeWalletIds[configured.selectedNetwork] = walletId;
      await saveWalletCollection(collection);
      return status();
    }
    case "rename-wallet": {
      const collection = await walletCollection();
      const wallet = collection.wallets.find(
        (candidate) => candidate.id === String(message.walletId)
      );
      if (!wallet) throw new Error("The selected wallet does not exist.");
      const label = String(message.label).trim();
      if (label.length < 1 || label.length > 40) {
        throw new Error("Wallet labels must contain between 1 and 40 characters.");
      }
      wallet.label = label;
      await saveWalletCollection(collection);
      return status();
    }
    case "lock":
      await lock();
      return status();
    case "save-settings": {
      const testnetApiUrl = String(message.testnetApiUrl ?? configured.testnetApiUrl).replace(/\/+$/, "");
      const mainnetApiUrl = String(message.mainnetApiUrl ?? configured.mainnetApiUrl).replace(/\/+$/, "");
      const next: StoredSettings = {
        selectedNetwork: configured.selectedNetwork,
        testnetApiUrl,
        mainnetApiUrl,
        apiUrl: configured.selectedNetwork === "mainnet" ? mainnetApiUrl : testnetApiUrl,
        autoLockMinutes: Math.max(1, Number(message.autoLockMinutes))
      };
      await chrome.storage.local.set({ [SETTINGS_KEY]: next });
      await resetLockAlarm();
      return status();
    }
    case "register-wallet": {
      if (!unlocked) throw new Error("Wallet is locked.");
      const result = await registerWalletKeys(wasm, unlocked);
      await resetLockAlarm();
      return result;
    }
    case "wallet-balances": {
      if (!unlocked) throw new Error("Wallet is locked.");
      const configured = await settings();
      const signature = wasm.create_handshake_proof(
        unlocked.public_key,
        unlocked.private_key
      );
      const headers = {
        "X-Contractless-Address": unlocked.short_address,
        "X-Contractless-Public-Key": unlocked.public_key,
        "X-Contractless-Signature": signature
      };
      const address = encodeURIComponent(unlocked.short_address);
      const [balancesResponse, tokensResponse, nftsResponse] = await Promise.all([
        fetch(`${requireApiUrl(configured)}/api/v1/balances?address=${address}`, { headers }),
        fetch(`${requireApiUrl(configured)}/api/v1/tokens`, { headers }),
        fetch(`${requireApiUrl(configured)}/api/v1/nfts`, { headers })
      ]);
      const [balancesReply, tokensReply, nftsReply] = await Promise.all([
        balancesResponse.json().catch(() => undefined),
        tokensResponse.json().catch(() => undefined),
        nftsResponse.json().catch(() => undefined)
      ]);
      if (!balancesResponse.ok || balancesReply?.success !== true) {
        throw new Error(
          balancesReply?.error ?? `Balance lookup failed with HTTP ${balancesResponse.status}.`
        );
      }
      await resetLockAlarm();
      return {
        address: unlocked.short_address,
        balances: balancesReply.data?.balances ?? [],
        tokens: tokensResponse.ok && tokensReply?.success === true
          ? tokensReply.data?.tokens ?? []
          : [],
        nfts: nftsResponse.ok && nftsReply?.success === true
          ? nftsReply.data?.nfts ?? []
          : [],
        tokenCatalogError: tokensResponse.ok && tokensReply?.success === true
          ? undefined
          : tokensReply?.error ?? `Token lookup failed with HTTP ${tokensResponse.status}.`,
        nftCatalogError: nftsResponse.ok && nftsReply?.success === true
          ? undefined
          : nftsReply?.error ?? `NFT lookup failed with HTTP ${nftsResponse.status}.`
      };
    }
    case "wallet-nft-details": {
      if (!unlocked) throw new Error("Wallet is locked.");
      const name = String(message.name ?? "").trim();
      const series = Number(message.series ?? 0);
      if (
        !name
        || name.length > 15
        || !Number.isInteger(series)
        || series < 0
        || series > 4_294_967_295
      ) {
        throw new Error("A valid NFT or RWA identifier is required.");
      }

      const configured = await settings();
      const signature = wasm.create_handshake_proof(
        unlocked.public_key,
        unlocked.private_key
      );
      const headers = {
        "X-Contractless-Address": unlocked.short_address,
        "X-Contractless-Public-Key": unlocked.public_key,
        "X-Contractless-Signature": signature
      };
      const query = `name=${encodeURIComponent(name)}&series=${series}`;
      const [detailsResponse, mediaResponse] = await Promise.all([
        fetch(`${requireApiUrl(configured)}/api/v1/nfts/details?${query}`, { headers }),
        fetch(`${requireApiUrl(configured)}/api/v1/nfts/media?${query}`, { headers })
      ]);
      const [detailsReply, mediaReply] = await Promise.all([
        detailsResponse.json().catch(() => undefined),
        mediaResponse.json().catch(() => undefined)
      ]);
      if (!detailsResponse.ok || detailsReply?.success !== true) {
        throw new Error(
          detailsReply?.error
            ?? `NFT details lookup failed with HTTP ${detailsResponse.status}.`
        );
      }

      let imageDataUrl = "";
      let imageAvailable = false;
      let metadata: Record<string, unknown> = {};
      let mediaError: string | undefined;
      if (mediaResponse.ok && mediaReply?.success === true) {
        metadata = mediaReply.data?.metadata
          && typeof mediaReply.data.metadata === "object"
          ? mediaReply.data.metadata
          : {};
        imageAvailable = mediaReply.data?.image_available === true;
        const imagePath = String(mediaReply.data?.image_url ?? "");
        if (imagePath) {
          try {
            const imageResponse = await fetch(
              new URL(imagePath, `${requireApiUrl(configured)}/`).toString(),
              { headers }
            );
            if (imageResponse.ok) {
              imageDataUrl = arrayBufferDataUrl(
                await imageResponse.arrayBuffer(),
                imageResponse.headers.get("Content-Type") ?? "image/png"
              );
            } else {
              mediaError = `NFT image returned HTTP ${imageResponse.status}.`;
            }
          } catch {
            mediaError = "NFT image could not be retrieved.";
          }
        }
      } else {
        mediaError = mediaReply?.error
          ?? `NFT media lookup failed with HTTP ${mediaResponse.status}.`;
      }

      await resetLockAlarm();
      return {
        ...detailsReply.data,
        metadata,
        image_data_url: imageDataUrl,
        image_available: imageAvailable,
        media_error: mediaError
      };
    }
    case "wallet-receive-address": {
      if (!unlocked) throw new Error("Wallet is locked.");
      const configured = await settings();
      const signature = wasm.create_handshake_proof(
        unlocked.public_key,
        unlocked.private_key
      );
      const address = encodeURIComponent(unlocked.short_address);
      const response = await fetch(
        `${requireApiUrl(configured)}/api/v1/addresses/vanity?address=${address}`,
        {
          headers: {
            "X-Contractless-Address": unlocked.short_address,
            "X-Contractless-Public-Key": unlocked.public_key,
            "X-Contractless-Signature": signature
          }
        }
      );
      const reply = await response.json().catch(() => undefined);
      if (!response.ok || reply?.success !== true) {
        throw new Error(
          reply?.error ?? `Vanity address lookup failed with HTTP ${response.status}.`
        );
      }
      await resetLockAlarm();
      const vanityAddress =
        typeof reply.data?.vanity_address === "string" &&
        reply.data.vanity_address.trim() !== ""
          ? reply.data.vanity_address
          : undefined;
      return {
        shortAddress: unlocked.short_address,
        receiveAddress: vanityAddress ?? unlocked.short_address,
        vanity: Boolean(vanityAddress)
      };
    }
    case "wallet-prepare-transfer": {
      if (!unlocked) throw new Error("Wallet is locked.");
      const configured = await settings();
      const signature = wasm.create_handshake_proof(
        unlocked.public_key,
        unlocked.private_key
      );
      const headers = {
        "X-Contractless-Address": unlocked.short_address,
        "X-Contractless-Public-Key": unlocked.public_key,
        "X-Contractless-Signature": signature
      };
      const receiverInput = String(message.receiver ?? "").trim().toLowerCase();
      const suffix = unlocked.short_address.endsWith(".cltc") ? "cltc" : "clc";
      let receiver = receiverInput;

      if (!new RegExp(`^[0-9a-f]{40}\\.${suffix}$`).test(receiverInput)) {
        if (!new RegExp(`^[a-z]{1,20}\\.${suffix}$`).test(receiverInput)) {
          throw new Error(`Enter a valid Contractless ${suffix.toUpperCase()} address.`);
        }
        const response = await fetch(
          `${requireApiUrl(configured)}/api/v1/addresses/vanity/resolve?address=${encodeURIComponent(receiverInput)}`,
          { headers }
        );
        const reply = await response.json().catch(() => undefined);
        if (!response.ok || reply?.success !== true) {
          throw new Error(
            reply?.error ?? `Vanity address lookup failed with HTTP ${response.status}.`
          );
        }
        receiver = String(reply.data?.owner_address ?? "");
        if (!receiver) throw new Error("That vanity address is not registered.");
      } else {
        const response = await fetch(
          `${requireApiUrl(configured)}/api/v1/addresses/registration?address=${encodeURIComponent(receiver)}`,
          { headers }
        );
        const reply = await response.json().catch(() => undefined);
        if (!response.ok || reply?.success !== true) {
          throw new Error(
            reply?.error ?? `Address lookup failed with HTTP ${response.status}.`
          );
        }
        if (reply.data?.registered !== true) {
          throw new Error("The receiving wallet is not registered.");
        }
      }

      const nftSeries = Number(message.nftSeries ?? 0);
      if (
        !Number.isInteger(nftSeries)
        || nftSeries < 0
        || nftSeries > 4_294_967_295
      ) {
        throw new Error("The NFT/RWA series number is invalid.");
      }
      const transaction = {
        txtype: 2,
        time: Math.floor(Date.now() / 1_000),
        value: String(message.valueAtomic ?? ""),
        coin: String(message.asset ?? "").trim().padEnd(15, " "),
        nft_series: nftSeries,
        sender: unlocked.short_address,
        receiver,
        txfee: String(message.feeAtomic ?? "")
      };
      const inspection = JSON.parse(
        wasm.inspect_transaction_request(JSON.stringify(transaction))
      );
      await resetLockAlarm();
      return {
        transaction,
        review: inspection.review,
        hash: inspection.hash,
        receiverInput
      };
    }
    case "wallet-broadcast-transfer": {
      if (!unlocked) throw new Error("Wallet is locked.");
      const transaction = message.transaction;
      if (!transaction || typeof transaction !== "object") {
        throw new Error("The reviewed transfer is unavailable.");
      }
      const signed = JSON.parse(
        wasm.sign_transaction_request(
          JSON.stringify(transaction),
          unlocked.private_key,
          unlocked.public_key,
          1
        )
      );
      return broadcastSignedTransaction(
        wasm,
        signed as Record<string, unknown>
      );
    }
    case "wallet-activity": {
      if (!unlocked) throw new Error("Wallet is locked.");
      const configured = await settings();
      const signature = wasm.create_handshake_proof(
        unlocked.public_key,
        unlocked.private_key
      );
      const address = encodeURIComponent(unlocked.short_address);
      const headers = {
        "X-Contractless-Address": unlocked.short_address,
        "X-Contractless-Public-Key": unlocked.public_key,
        "X-Contractless-Signature": signature
      };
      const [response, heightResponse] = await Promise.all([
        fetch(
          `${requireApiUrl(configured)}/api/v1/addresses/latest?address=${address}&limit=25`,
          { headers }
        ),
        fetch(`${requireApiUrl(configured)}/api/v1/height`, { headers })
      ]);
      const [reply, heightReply] = await Promise.all([
        response.json().catch(() => undefined),
        heightResponse.json().catch(() => undefined)
      ]);
      if (!response.ok || reply?.success !== true) {
        throw new Error(
          reply?.error ?? `Activity lookup failed with HTTP ${response.status}.`
        );
      }
      if (!heightResponse.ok || heightReply?.success !== true) {
        throw new Error(
          heightReply?.error ?? `Block height lookup failed with HTTP ${heightResponse.status}.`
        );
      }
      await resetLockAlarm();
      return {
        address: unlocked.short_address,
        current_height: Number(heightReply.data?.height ?? 0),
        transactions: Array.isArray(reply.data?.transactions)
          ? reply.data.transactions
          : []
      };
    }
    case "wallet-transaction": {
      if (!unlocked) throw new Error("Wallet is locked.");
      const txid = String(message.txid);
      if (!/^[0-9a-f]{64}$/i.test(txid)) {
        throw new Error("A valid transaction ID is required.");
      }
      const configured = await settings();
      const signature = wasm.create_handshake_proof(
        unlocked.public_key,
        unlocked.private_key
      );
      const headers = {
        "X-Contractless-Address": unlocked.short_address,
        "X-Contractless-Public-Key": unlocked.public_key,
        "X-Contractless-Signature": signature
      };
      const [response, heightResponse] = await Promise.all([
        fetch(
          `${requireApiUrl(configured)}/api/v1/transactions/lookup?txid=${encodeURIComponent(txid)}`,
          { headers }
        ),
        fetch(`${requireApiUrl(configured)}/api/v1/height`, { headers })
      ]);
      const [reply, heightReply] = await Promise.all([
        response.json().catch(() => undefined),
        heightResponse.json().catch(() => undefined)
      ]);
      if (!response.ok || reply?.success !== true) {
        throw new Error(
          reply?.error ?? `Transaction lookup failed with HTTP ${response.status}.`
        );
      }
      if (!heightResponse.ok || heightReply?.success !== true) {
        throw new Error(
          heightReply?.error ?? `Block height lookup failed with HTTP ${heightResponse.status}.`
        );
      }
      await resetLockAlarm();
      return {
        ...reply.data,
        current_height: Number(heightReply.data?.height ?? 0)
      };
    }
    case "export-wallet": {
      const wallet = activeStoredWallet(await walletCollection(), configured.selectedNetwork);
      if (!wallet) throw new Error("No wallet exists.");
      return wallet.walletJson;
    }
    case "export-legacy-wallet": {
      const wallet = activeStoredWallet(await walletCollection(), configured.selectedNetwork);
      if (!wallet) throw new Error("No wallet exists.");
      return wasm.export_legacy_wallet(
        wallet.walletJson,
        String(message.password),
        String(message.legacyPassword)
      );
    }
    case "export-private-key-image": {
      const wallet = activeStoredWallet(await walletCollection(), configured.selectedNetwork);
      if (!wallet) throw new Error("No wallet exists.");
      const legacy = JSON.parse(
        wasm.export_legacy_wallet(
          wallet.walletJson,
          String(message.password),
          String(message.imagePassword)
        )
      ) as { private_key?: string };
      if (typeof legacy.private_key !== "string") {
        throw new Error("The wallet image could not be created.");
      }
      return legacy.private_key;
    }
    case "reveal-private-key": {
      const wallet = activeStoredWallet(await walletCollection(), configured.selectedNetwork);
      if (!wallet) throw new Error("No wallet exists.");
      const opened = JSON.parse(
        wasm.unlock_browser_wallet(wallet.walletJson, String(message.password))
      ) as { private_key?: string };
      if (typeof opened.private_key !== "string") {
        throw new Error("The private key could not be decrypted.");
      }
      await resetLockAlarm();
      return {
        label: wallet.label,
        address: wallet.address,
        privateKey: opened.private_key
      };
    }
    case "provider-request": {
      const origin = requestOrigin(sender);
      const method = String(message.method);
      const params = message.params && typeof message.params === "object"
        ? message.params as Record<string, unknown>
        : {};

      if (method === "contractless_detect") {
        return {
          installed: true,
          providerVersion: "1.0.0"
        };
      }

      if (method === "contractless_connect") {
        validateClaimedOrigin(params, origin);
        const appId = validatedApplicationId(params.appId);
        const timestamp = connectionTimestamp(params.timestamp);
        connectionNonce(params.nonce);
        const appMessage = connectionMessage(params.message);
        const request: ApprovalRecord = {
          id: crypto.randomUUID(),
          method,
          params: {
            ...params,
            walletSessionId: unlocked?.sessionId
          },
          origin,
          createdAt: Date.now(),
          risk: approvalRisk(method, params),
          display: {
            request: "Connect application",
            application_id: appId,
            website: origin,
            wallet_address: unlocked?.short_address ?? "Unlock wallet to continue",
            message: appMessage,
            requested_timestamp: timestamp,
            access: "Address access and future wallet requests until this wallet locks"
          }
        };
        await openApproval(request);
        if (!(await waitForApproval(request.id))) {
          throw new Error("The wallet connection request was rejected.");
        }
        return executeApprovedRequest(wasm, request);
      }

      if (method === "contractless_accounts") {
        try {
          await authorizedApplication(params, origin);
          return unlocked ? [unlocked.short_address] : [];
        } catch {
          return [];
        }
      }

      if (method === "contractless_balances") {
        await authorizedApplication(params, origin);
        if (!unlocked) throw new Error("Wallet is locked.");
        const configured = await settings();
        const signature = wasm.create_handshake_proof(
          unlocked.public_key,
          unlocked.private_key
        );
        const response = await fetch(
          `${requireApiUrl(configured)}/api/v1/balances?address=${encodeURIComponent(unlocked.short_address)}`,
          {
            headers: {
              "X-Contractless-Address": unlocked.short_address,
              "X-Contractless-Public-Key": unlocked.public_key,
              "X-Contractless-Signature": signature
            }
          }
        );
        const reply = await response.json().catch(() => undefined);
        if (!response.ok || reply?.success !== true) {
          throw new Error(
            reply?.error ?? `Balance lookup failed with HTTP ${response.status}.`
          );
        }
        await resetLockAlarm();
        return reply.data;
      }

      if (method === "contractless_disconnect") {
        const session = await authorizedApplication(params, origin);
        const remaining = (await applicationSessions()).filter((candidate) =>
          !(
            candidate.origin === session.origin
            && candidate.appId === session.appId
            && candidate.accessKey === session.accessKey
          )
        );
        await saveApplicationSessions(remaining);
        return { disconnected: true };
      }

      if (method === "contractless_completeDualTransaction") {
        await authorizedApplication(params, origin);
        const transaction = websiteDualTransaction(params.transaction);
        return JSON.parse(wasm.complete_dual_transaction_request(
          JSON.stringify(transaction),
          String(params.signature1 ?? ""),
          String(params.publicKey1 ?? ""),
          String(params.signature2 ?? ""),
          String(params.publicKey2 ?? "")
        ));
      }

      if (![
        "contractless_signMessage",
        "contractless_sendTransaction",
        "contractless_signDualTransaction"
      ].includes(method)) {
        throw new Error("The website requested an unsupported wallet method.");
      }
      await authorizedApplication(params, origin);
      if (method === "contractless_sendTransaction") {
        const transaction = websiteTransaction(params.transaction);
        await ensureRegisteredTransactionParticipants(wasm, transaction);
      } else if (method === "contractless_signDualTransaction") {
        const transaction = websiteDualTransaction(params.transaction);
        await ensureRegisteredTransactionParticipants(wasm, transaction);
      }
      const request: ApprovalRecord = {
        id: crypto.randomUUID(),
        method,
        params,
        origin,
        createdAt: Date.now(),
        risk: approvalRisk(method, params),
        display: method === "contractless_signMessage"
          ? {
              request: "Sign a text message",
              application_id: validatedApplicationId(params.appId),
              website: origin,
              message: signedTextMessage(params.message),
              warning: "This is a text signature. It cannot move wallet assets."
            }
          : method === "contractless_signDualTransaction"
          ? (() => {
              const inspection = JSON.parse(
                wasm.inspect_transaction_request(JSON.stringify(params.transaction))
              ) as Record<string, unknown>;
              return {
                application_id: validatedApplicationId(params.appId),
                website: origin,
                transaction_type: inspection.transaction_name,
                signer_slot: params.signerSlot,
                transaction_fields: inspection.review,
                action: "Sign and return this signature to the application without broadcasting."
              };
            })()
          : (() => {
              const inspection = JSON.parse(
                wasm.inspect_transaction_request(JSON.stringify(params.transaction))
              ) as Record<string, unknown>;
              return {
                application_id: validatedApplicationId(params.appId),
                website: origin,
                transaction_type: inspection.transaction_name,
                transaction_fields: inspection.review,
                action: "Sign and broadcast this transaction."
              };
            })()
      };
      await openApproval(request);
      if (!(await waitForApproval(request.id))) {
        throw new Error("The wallet request was rejected.");
      }
      return executeApprovedRequest(wasm, request);
    }
    case "pending-provider-approval": {
      const queue = await approvalQueue();
      const id = queue[0] ?? "";
      if (!id) return null;
      const approvalKey = `approval:${id}`;
      const approvalStored = await chrome.storage.session.get(approvalKey);
      const request = approvalStored[approvalKey] as ApprovalRecord | undefined;
      if (!request) {
        await removeApprovalFromQueue(id);
        return null;
      }
      if (!unlocked) return { locked: true };

      if (request.method === "contractless_connect") {
        request.params = {
          ...(request.params as Record<string, unknown>),
          walletSessionId: unlocked.sessionId
        };
        request.display = {
          ...(request.display ?? {}),
          wallet_address: unlocked.short_address
        };
        await chrome.storage.session.set({ [approvalKey]: request });
      }
      return { locked: false, request, position: 1, total: queue.length };
    }
    case "resolve-provider-approval": {
      if (!unlocked) throw new Error("Unlock the wallet before approving this request.");
      const id = String(message.id ?? "");
      const queue = await approvalQueue();
      if (!id || queue[0] !== id) {
        throw new Error("The wallet approval request has expired.");
      }
      const approvalKey = `approval:${id}`;
      const approvalStored = await chrome.storage.session.get(approvalKey);
      const request = approvalStored[approvalKey] as ApprovalRecord | undefined;
      if (!request) throw new Error("The wallet approval request has expired.");

      if (request.method === "contractless_connect") {
        request.params = {
          ...(request.params as Record<string, unknown>),
          walletSessionId: unlocked.sessionId
        };
        await chrome.storage.session.set({ [approvalKey]: request });
      }
      await chrome.storage.session.set({
        [`approval-result:${id}`]: { approved: message.approved === true }
      });
      await chrome.storage.session.remove(approvalKey);
      const remaining = await removeApprovalFromQueue(id);
      return { resolved: true, remaining: remaining.length };
    }
    default:
      throw new Error("Unsupported wallet request.");
  }
}

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  handle(message, sender)
    .then((result) => sendResponse({ ok: true, result }))
    .catch((error: unknown) =>
      sendResponse({ ok: false, error: error instanceof Error ? error.message : String(error) })
    );
  return true;
});

chrome.alarms.onAlarm.addListener((alarm) => {
  if (alarm.name === LOCK_ALARM) void lock();
});
