/**
 * RFC 6962 §2.1 hash construction for an append-only Merkle log.
 *
 *   leaf_hash(d)        = SHA-256(0x00 || d)
 *   node_hash(left, r)  = SHA-256(0x01 || left || r)
 *
 * The 0x00 / 0x01 prefixes are critical — they make leaf-hashes and
 * node-hashes distinct, which prevents second-preimage attacks where
 * an attacker re-interprets a leaf as an internal node.
 */

import { sha256Sync } from './sha256.js';

const DOMAIN_LEAF = 0x00;
const DOMAIN_NODE = 0x01;

/** Bundle commitment domain prefix. Must be stable across versions. */
export const DOMAIN_BUNDLE = 0x01;

/** STH canonical-bytes domain prefix. */
export const DOMAIN_STH = 0x02;

/** Operation byte values inside a leaf. */
export const OP_REGISTER = 0x01;
export const OP_REPLENISH = 0x02;
export const OP_DELETE = 0x03;

export function leafHash(data: Uint8Array): Uint8Array {
  const buf = new Uint8Array(1 + data.length);
  buf[0] = DOMAIN_LEAF;
  buf.set(data, 1);
  return sha256Sync(buf);
}

export function nodeHash(left: Uint8Array, right: Uint8Array): Uint8Array {
  const buf = new Uint8Array(1 + left.length + right.length);
  buf[0] = DOMAIN_NODE;
  buf.set(left, 1);
  buf.set(right, 1 + left.length);
  return sha256Sync(buf);
}

/** RFC 6962 MTH for an empty list: SHA-256 of the empty string. */
export function emptyRootHash(): Uint8Array {
  return sha256Sync(new Uint8Array(0));
}

/**
 * Encode a leaf describing an `address → bundle_hash` event.
 *
 * Layout:
 *   uint64_be timestamp_ms
 *   byte      operation
 *   uint16_be addr_len
 *   bytes     address (utf-8)
 *   uint16_be hash_len
 *   bytes     bundle_hash (32 bytes for register/replenish, may be 0 for delete)
 */
export function encodeLeafData(
  timestampMs: number,
  operation: number,
  address: string,
  bundleHash: Uint8Array,
): Uint8Array {
  const addrBytes = new TextEncoder().encode(address);
  if (addrBytes.length > 0xffff) {
    throw new Error('address too long for KT leaf encoding');
  }
  if (bundleHash.length > 0xffff) {
    throw new Error('bundleHash too long for KT leaf encoding');
  }
  const len = 8 + 1 + 2 + addrBytes.length + 2 + bundleHash.length;
  const out = new Uint8Array(len);
  const view = new DataView(out.buffer);
  let off = 0;
  // uint64 BE — split into two uint32 halves
  view.setUint32(off, Math.floor(timestampMs / 0x100000000));
  view.setUint32(off + 4, timestampMs >>> 0);
  off += 8;
  out[off++] = operation;
  view.setUint16(off, addrBytes.length);
  off += 2;
  out.set(addrBytes, off);
  off += addrBytes.length;
  view.setUint16(off, bundleHash.length);
  off += 2;
  out.set(bundleHash, off);
  return out;
}

/**
 * Compute the canonical bundle commitment hash.
 *
 *   bundle_hash = SHA-256(
 *     0x01 ||
 *     identitySigningKey (32) ||
 *     identityDHKey (32) ||
 *     uint32_be signedPreKey.keyId ||
 *     signedPreKey.publicKey (32) ||
 *     signedPreKey.signature (64)
 *   )
 *
 * One-time prekeys are NOT included — they are ephemeral and including
 * them would force a log mutation per OTP rotation.
 */
export function computeBundleHash(input: {
  identitySigningKey: Uint8Array;
  identityDHKey: Uint8Array;
  signedPreKey: { keyId: number; publicKey: Uint8Array; signature: Uint8Array };
}): Uint8Array {
  if (input.identitySigningKey.length !== 32) {
    throw new Error('identitySigningKey must be 32 bytes');
  }
  if (input.identityDHKey.length !== 32) {
    throw new Error('identityDHKey must be 32 bytes');
  }
  if (input.signedPreKey.publicKey.length !== 32) {
    throw new Error('signedPreKey.publicKey must be 32 bytes');
  }
  if (input.signedPreKey.signature.length !== 64) {
    throw new Error('signedPreKey.signature must be 64 bytes');
  }

  const buf = new Uint8Array(1 + 32 + 32 + 4 + 32 + 64);
  let off = 0;
  buf[off++] = DOMAIN_BUNDLE;
  buf.set(input.identitySigningKey, off);
  off += 32;
  buf.set(input.identityDHKey, off);
  off += 32;
  new DataView(buf.buffer).setUint32(off, input.signedPreKey.keyId >>> 0);
  off += 4;
  buf.set(input.signedPreKey.publicKey, off);
  off += 32;
  buf.set(input.signedPreKey.signature, off);
  return sha256Sync(buf);
}
