import { StreamDecryptionError } from './errors.js';
import { STREAM_NONCE_BYTES } from './nonce.js';

/** Authentication tag length for AES-256-GCM (always 16 bytes). */
export const AEAD_TAG_BYTES = 16;

/**
 * SubtleCrypto-style typed buffer source bridge. The DOM `BufferSource` alias
 * isn't available without `lib: ["DOM"]`, but SubtleCrypto runtime accepts
 * `ArrayBuffer` or `ArrayBufferView` interchangeably. Cast through `unknown`
 * to satisfy TS without dragging in DOM lib (matches the pattern in
 * `@shade/crypto-web/src/provider.ts:14`).
 */
function bs(u: Uint8Array): ArrayBuffer {
  return u as unknown as ArrayBuffer;
}

function resolveSubtle(subtle?: SubtleCrypto): SubtleCrypto {
  return subtle ?? globalThis.crypto.subtle;
}

/**
 * AES-256-GCM encrypt with a CALLER-SUPPLIED 12-byte nonce.
 *
 * Unlike `CryptoProvider.aesGcmEncrypt` (which generates a random nonce
 * internally), streams require deterministic nonces derived from
 * `(laneId, seq)`. Returns the ciphertext concatenated with the 16-byte
 * authentication tag (SubtleCrypto's standard layout).
 */
export async function aesGcmEncryptWithNonce(
  key: Uint8Array,
  nonce: Uint8Array,
  plaintext: Uint8Array,
  aad: Uint8Array,
  subtle?: SubtleCrypto,
): Promise<Uint8Array> {
  if (nonce.length !== STREAM_NONCE_BYTES) {
    throw new Error(`AES-GCM nonce must be ${STREAM_NONCE_BYTES} bytes`);
  }
  const s = resolveSubtle(subtle);
  const aesKey = await s.importKey('raw', bs(key), 'AES-GCM', false, ['encrypt']);
  const out = await s.encrypt(
    { name: 'AES-GCM', iv: bs(nonce), additionalData: bs(aad) },
    aesKey,
    bs(plaintext),
  );
  return new Uint8Array(out);
}

/**
 * AES-256-GCM decrypt with a CALLER-SUPPLIED nonce. Throws
 * `StreamDecryptionError` on authentication failure.
 */
export async function aesGcmDecryptWithNonce(
  key: Uint8Array,
  nonce: Uint8Array,
  ciphertext: Uint8Array,
  aad: Uint8Array,
  subtle?: SubtleCrypto,
): Promise<Uint8Array> {
  if (nonce.length !== STREAM_NONCE_BYTES) {
    throw new Error(`AES-GCM nonce must be ${STREAM_NONCE_BYTES} bytes`);
  }
  const s = resolveSubtle(subtle);
  const aesKey = await s.importKey('raw', bs(key), 'AES-GCM', false, ['decrypt']);
  try {
    const out = await s.decrypt(
      { name: 'AES-GCM', iv: bs(nonce), additionalData: bs(aad) },
      aesKey,
      bs(ciphertext),
    );
    return new Uint8Array(out);
  } catch {
    throw new StreamDecryptionError();
  }
}
