import type { CryptoProvider } from '@shade/core';
import { ValidationError } from '@shade/core';
import { STREAM_ID_BYTES, STREAM_SECRET_BYTES } from './ids.js';

const TEXT = new TextEncoder();
const STREAM_KEY_INFO = TEXT.encode('shade-stream/v1\0master');
const LANE_KEY_INFO_PREFIX = TEXT.encode('shade-stream/v1\0lane\0');

const STREAM_KEY_BYTES = 32;
export const LANE_KEY_BYTES = 32;

/**
 * Derive the master streamKey from a streamSecret + streamId.
 *
 *   streamKey = HKDF(ikm=streamSecret, salt=streamId,
 *                    info="shade-stream/v1\0master", length=32)
 *
 * The streamKey is NEVER used to encrypt chunks directly — it is a root for
 * per-lane key derivation (see `deriveLaneKey`).
 */
export async function deriveStreamKey(
  crypto: CryptoProvider,
  streamSecret: Uint8Array,
  streamId: Uint8Array,
): Promise<Uint8Array> {
  if (streamSecret.length !== STREAM_SECRET_BYTES) {
    throw new ValidationError(
      `streamSecret must be ${STREAM_SECRET_BYTES} bytes`,
      'streamSecret',
    );
  }
  if (streamId.length !== STREAM_ID_BYTES) {
    throw new ValidationError(`streamId must be ${STREAM_ID_BYTES} bytes`, 'streamId');
  }
  return crypto.hkdf(streamSecret, streamId, STREAM_KEY_INFO, STREAM_KEY_BYTES);
}

/**
 * Derive a lane-specific AEAD key.
 *
 *   laneKey[i] = HKDF(ikm=streamKey, salt=streamId,
 *                     info="shade-stream/v1\0lane\0" || u32_be(laneId), length=32)
 *
 * Distinct laneIds produce independent keys; receiver derives the same key
 * given the same (streamSecret, streamId, laneId).
 */
export async function deriveLaneKey(
  crypto: CryptoProvider,
  streamKey: Uint8Array,
  streamId: Uint8Array,
  laneId: number,
): Promise<Uint8Array> {
  if (streamKey.length !== STREAM_KEY_BYTES) {
    throw new ValidationError(`streamKey must be ${STREAM_KEY_BYTES} bytes`, 'streamKey');
  }
  if (streamId.length !== STREAM_ID_BYTES) {
    throw new ValidationError(`streamId must be ${STREAM_ID_BYTES} bytes`, 'streamId');
  }
  if (!Number.isInteger(laneId) || laneId < 0 || laneId > 0xffff_ffff) {
    throw new ValidationError(`laneId must fit in u32: ${laneId}`, 'laneId');
  }
  const info = new Uint8Array(LANE_KEY_INFO_PREFIX.length + 4);
  info.set(LANE_KEY_INFO_PREFIX, 0);
  new DataView(info.buffer).setUint32(LANE_KEY_INFO_PREFIX.length, laneId, false);
  return crypto.hkdf(streamKey, streamId, info, LANE_KEY_BYTES);
}
