import type { CryptoProvider } from './crypto.js';

/**
 * Shade event bus.
 *
 * Emits structural events for observability — NEVER plaintext, private keys,
 * nonces, or other secret material. Identity references are SHA-256 truncated
 * to 8 bytes (16 hex chars) for display only.
 *
 * Optional: pass a ShadeEventEmitter to ShadeSessionManager to enable.
 * If not passed, all emits are no-ops with zero overhead.
 */

// ─── Event payload types ──────────────────────────────────────

export interface ShadeEventBase {
  /** Monotonic sequence number assigned at emit time */
  seq: number;
  /** Wall-clock timestamp in milliseconds */
  timestamp: number;
}

/** Map of event names to their payload shape (without seq/timestamp) */
export interface ShadeEventMap {
  'identity.initialized': { fingerprint: string; registrationId: number };
  'identity.rotated': { newFingerprint: string };
  'session.created': { address: string; remoteIdentityKeyHash: string };
  'session.removed': { address: string };
  /** V4.8.3 — emitted when `aliasSession` moves a peer's per-peer state. */
  'session.aliased': { oldLabel: string; newLabel: string };
  'message.encrypted': { address: string; counter: number; ciphertextSize: number };
  'message.decrypted': { address: string; counter: number; plaintextSize: number };
  'ratchet.dh_step': { address: string };
  'prekey.generated': { count: number; totalAfter: number };
  'prekey.consumed': { keyId: number };
  'signed_prekey.rotated': { oldKeyId: number; newKeyId: number };
  'trust.pinned': { address: string; identityKeyHash: string };
  'trust.changed': { address: string; oldKeyHash: string; newKeyHash: string };
  /** V4.11 — a streaming sub-ratchet handshake completed. */
  'stream.opened': { address: string; role: 'initiator' | 'responder' };
  /** V4.11 — a streaming sub-ratchet was torn down and zeroized. */
  'stream.closed': { address: string };
}

export type ShadeEventName = keyof ShadeEventMap;

export type ShadeEvent = {
  [K in ShadeEventName]: ShadeEventBase & { name: K; data: ShadeEventMap[K] };
}[ShadeEventName];

export type ShadeEventListener = (event: ShadeEvent) => void;

// ─── EventEmitter implementation ─────────────────────────────

/**
 * Minimal typed event emitter for Shade observability.
 *
 * Supports subscribe (`on`), unsubscribe (`off`), and replay buffer
 * for late subscribers.
 */
export class ShadeEventEmitter {
  private listeners = new Set<ShadeEventListener>();
  private nextSeq = 1;
  private buffer: ShadeEvent[] = [];
  private readonly maxBuffer: number;

  constructor(options: { bufferSize?: number } = {}) {
    this.maxBuffer = options.bufferSize ?? 1000;
  }

  /** Subscribe to all events. Returns an unsubscribe function. */
  on(listener: ShadeEventListener): () => void {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  }

  off(listener: ShadeEventListener): void {
    this.listeners.delete(listener);
  }

  /** Emit a typed event. Adds seq + timestamp automatically. */
  emit<K extends ShadeEventName>(name: K, data: ShadeEventMap[K]): void {
    const event = {
      seq: this.nextSeq++,
      timestamp: Date.now(),
      name,
      data,
    } as ShadeEvent;

    // Add to ring buffer
    this.buffer.push(event);
    if (this.buffer.length > this.maxBuffer) {
      this.buffer.shift();
    }

    // Notify listeners (catching throws so one bad listener doesn't break others)
    for (const listener of this.listeners) {
      try {
        listener(event);
      } catch (err) {
        console.error('[Shade] Event listener threw:', err);
      }
    }
  }

  /** Get all buffered events with seq > since (for SSE replay/reconnect) */
  getBufferedSince(since: number): ShadeEvent[] {
    return this.buffer.filter((e) => e.seq > since);
  }

  /** Get the most recent N events */
  getRecent(n: number): ShadeEvent[] {
    return this.buffer.slice(-n);
  }

  /** Current sequence number (next event will use this + 1) */
  get currentSeq(): number {
    return this.nextSeq - 1;
  }
}

// ─── Hash helper for safe display ────────────────────────────

/**
 * Compute a short, display-safe hash of a public key.
 * Uses HKDF-SHA256 (since CryptoProvider has it) to produce 8 bytes,
 * then formats as 16 hex characters.
 *
 * NEVER use this for security decisions — it's lossy and only for UI display.
 */
export async function shortHash(crypto: CryptoProvider, key: Uint8Array): Promise<string> {
  const salt = new Uint8Array(32);
  const info = new TextEncoder().encode('ShadeShortHash');
  const hash = await crypto.hkdf(key, salt, info, 8);
  return Array.from(hash, (b) => b.toString(16).padStart(2, '0')).join('');
}
