/**
 * Prekey server event emitter.
 *
 * Mirrors @shade/core's ShadeEventEmitter for the server side. Emits
 * structural facts only — no key material, no signatures, no plaintext.
 */

export interface PrekeyServerEventBase {
  seq: number;
  timestamp: number;
}

export interface PrekeyServerEventMap {
  'server.identity_registered': { address: string; identityKeyHash: string };
  'server.bundle_fetched': { address: string; hadOneTimePreKey: boolean };
  'server.prekeys_replenished': { address: string; count: number; totalAfter: number };
  'server.identity_deleted': { address: string };
  'server.rate_limited': { route: string; key: string };
}

export type PrekeyServerEventName = keyof PrekeyServerEventMap;

export type PrekeyServerEvent = {
  [K in PrekeyServerEventName]: PrekeyServerEventBase & { name: K; data: PrekeyServerEventMap[K] };
}[PrekeyServerEventName];

export type PrekeyServerEventListener = (event: PrekeyServerEvent) => void;

export class PrekeyServerEvents {
  private listeners = new Set<PrekeyServerEventListener>();
  private nextSeq = 1;
  private buffer: PrekeyServerEvent[] = [];
  private readonly maxBuffer: number;

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

  on(listener: PrekeyServerEventListener): () => void {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  }

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

  emit<K extends PrekeyServerEventName>(name: K, data: PrekeyServerEventMap[K]): void {
    const event = {
      seq: this.nextSeq++,
      timestamp: Date.now(),
      name,
      data,
    } as PrekeyServerEvent;

    this.buffer.push(event);
    if (this.buffer.length > this.maxBuffer) this.buffer.shift();

    for (const listener of this.listeners) {
      try {
        listener(event);
      } catch (err) {
        console.error('[Shade] Server event listener threw:', err);
      }
    }
  }

  getBufferedSince(since: number): PrekeyServerEvent[] {
    return this.buffer.filter((e) => e.seq > since);
  }

  getRecent(n: number): PrekeyServerEvent[] {
    return this.buffer.slice(-n);
  }

  get currentSeq(): number {
    return this.nextSeq - 1;
  }
}

/**
 * Compute a short display hash from a public key.
 * Identical algorithm to @shade/core/shortHash but inlined here to
 * avoid circular dependency on CryptoProvider.
 *
 * Uses SHA-256 via crypto.subtle directly.
 */
export async function shortHash(key: Uint8Array): Promise<string> {
  // Cast through ArrayBuffer (rather than DOM `BufferSource`) so this file
  // type-checks without DOM lib in the consumer's tsconfig.
  const buf = await globalThis.crypto.subtle.digest('SHA-256', key as unknown as ArrayBuffer);
  const arr = new Uint8Array(buf).slice(0, 8);
  return Array.from(arr, (b) => b.toString(16).padStart(2, '0')).join('');
}
