/**
 * Pure version of the Service Worker push handler for unit testing.
 *
 * The actual ServiceWorker (ui/public/sw.js) runs in a separate JS context
 * where importing TS modules is awkward. We mirror the dedup-via-ACK
 * algorithm here so the logic can be exercised with mocks. The two
 * implementations must stay behaviorally identical — see
 * `docs/superpowers/specs/2026-05-28-browser-notifications-v2-webpush.md`
 * (## Service Worker 変更 / ## V1 との dedup 戦略).
 */

export interface PushPayload {
  title?: string;
  body?: string;
  tag: string;
  icon?: string;
  data?: { taskId?: number } & Record<string, unknown>;
}

export interface ClientLike {
  postMessage(msg: unknown): void;
}

export interface NotificationServices {
  /**
   * Resolve to true when a visible tab ACKs the tag; false on timeout.
   * The handler does not implement message routing — call sites do that
   * by adding message listeners that resolve this promise.
   */
  waitForAck(clients: readonly ClientLike[], payload: PushPayload): Promise<boolean>;
  /** Wrapper over `registration.showNotification`. */
  showNotification(title: string, options: NotificationOptions): Promise<void>;
}

/**
 * Decide whether to show an OS notification given the current clients and
 * the broadcast/ACK protocol.
 *
 * Returns true if `showNotification` was called, false if it was suppressed
 * because a visible tab ACKed.
 */
export async function handlePushEvent(
  payload: PushPayload,
  clients: readonly ClientLike[],
  services: NotificationServices,
): Promise<boolean> {
  const title = payload.title ?? 'MAESTRO';
  const options: NotificationOptions = {
    body: payload.body ?? '',
    tag: payload.tag,
    icon: payload.icon ?? '/ui/icon-192.png',
    badge: '/ui/icon-192.png',
    data: payload.data ?? {},
  };

  if (clients.length === 0) {
    await services.showNotification(title, options);
    return true;
  }

  const handled = await services.waitForAck(clients, payload);
  if (handled) return false;
  await services.showNotification(title, options);
  return true;
}
