// ──────────────────────────────────────────────────────────────────────────
// Kurzer, dezenter Benachrichtigungston via WebAudio (keine Asset-Datei nötig).
//
// Browser blockieren Audio, solange der Nutzer nicht mit der Seite interagiert
// hat ("Autoplay Policy"). Deshalb:
//   1. `installAudioUnlock()` weckt den AudioContext bei der ersten Interaktion.
//   2. `playPing()` wartet auf `resume()`, bevor Töne geplant werden — sonst
//      liegen die Startzeiten in der Vergangenheit und man hört nichts.
// ──────────────────────────────────────────────────────────────────────────

const PREF_KEY = "avaria:sound";

let ctx: AudioContext | null = null;
let unlockInstalled = false;

function createCtx(): AudioContext | null {
  if (typeof window === "undefined") return null;
  const AC =
    window.AudioContext ??
    (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;
  if (!AC) return null;
  try {
    ctx = ctx ?? new AC();
    return ctx;
  } catch {
    return null;
  }
}

// Kleine Stores, damit Komponenten die Einstellungen per
// `useSyncExternalStore` lesen können (statt setState im Effekt).
const listeners = new Set<() => void>();
function emit() {
  for (const l of listeners) l();
}
function subscribe(cb: () => void): () => void {
  listeners.add(cb);
  return () => listeners.delete(cb);
}

export const soundStore = {
  subscribe,
  /** Ton-Einstellung des Nutzers (Standard: an). */
  get: (): boolean => {
    if (typeof window === "undefined") return true;
    try {
      return localStorage.getItem(PREF_KEY) !== "off";
    } catch {
      return true;
    }
  },
  getServerSnapshot: (): boolean => true,
};

export const isSoundEnabled = soundStore.get;

export function setSoundEnabled(on: boolean): void {
  try {
    localStorage.setItem(PREF_KEY, on ? "on" : "off");
  } catch {
    /* ignorieren */
  }
  if (on) void createCtx()?.resume();
  emit();
}

/**
 * Registriert einmalig Listener, die den AudioContext bei der ersten
 * Nutzerinteraktion freischalten. Danach ist der Ton jederzeit abspielbar.
 */
export function installAudioUnlock(): () => void {
  if (typeof window === "undefined" || unlockInstalled) return () => {};
  unlockInstalled = true;

  const unlock = () => {
    const c = createCtx();
    if (!c) return;
    void c.resume().catch(() => {});
    if (c.state === "running") remove();
  };
  const events: (keyof WindowEventMap)[] = ["pointerdown", "keydown", "touchstart"];
  const remove = () => {
    for (const ev of events) window.removeEventListener(ev, unlock);
  };
  for (const ev of events) window.addEventListener(ev, unlock, { passive: true });

  return () => {
    unlockInstalled = false;
    remove();
  };
}

let lastPingAt = 0;
const PING_THROTTLE_MS = 1500;

/** Spielt den Benachrichtigungston (zwei kurze, ansteigende Töne). */
export async function playPing(): Promise<void> {
  if (!isSoundEnabled()) return;
  // Chat-Fenster und Glocke können dieselbe Nachricht melden — nur einmal tönen.
  if (Date.now() - lastPingAt < PING_THROTTLE_MS) return;
  lastPingAt = Date.now();

  const c = createCtx();
  if (!c) return;

  try {
    // Erst aufwecken, dann planen — sonst liegen die Startzeiten in der
    // Vergangenheit und der Ton bleibt stumm.
    if (c.state === "suspended") await c.resume();
    if (c.state !== "running") return;

    const now = c.currentTime;
    const notes: [number, number][] = [
      [784, 0], // G5
      [1046, 0.1], // C6
    ];
    for (const [freq, offset] of notes) {
      const osc = c.createOscillator();
      const gain = c.createGain();
      osc.type = "sine";
      osc.frequency.value = freq;
      osc.connect(gain);
      gain.connect(c.destination);
      const t = now + offset;
      gain.gain.setValueAtTime(0.0001, t);
      gain.gain.exponentialRampToValueAtTime(0.11, t + 0.02);
      gain.gain.exponentialRampToValueAtTime(0.0001, t + 0.2);
      osc.start(t);
      osc.stop(t + 0.22);
    }
  } catch {
    /* Audio nicht verfügbar — ignorieren. */
  }
}

// ── Desktop-Benachrichtigungen (Web Notification API) ─────────────────────

export type NotifyPermission = "unsupported" | NotificationPermission;

export function notifyPermission(): NotifyPermission {
  if (typeof window === "undefined" || !("Notification" in window)) return "unsupported";
  return Notification.permission;
}

export const notifyPermissionStore = {
  subscribe,
  get: notifyPermission,
  getServerSnapshot: (): NotifyPermission => "unsupported",
};

export async function requestNotifyPermission(): Promise<NotifyPermission> {
  if (notifyPermission() === "unsupported") return "unsupported";
  try {
    const result = await Notification.requestPermission();
    emit();
    return result;
  } catch {
    return Notification.permission;
  }
}

/** Zeigt eine Desktop-Benachrichtigung, sofern der Nutzer sie erlaubt hat. */
export function showDesktopNotification(
  title: string,
  options: { body?: string; link?: string | null; tag?: string } = {},
): void {
  if (notifyPermission() !== "granted") return;
  try {
    const n = new Notification(title, {
      body: options.body ?? undefined,
      tag: options.tag,
      icon: "/favicon.ico",
    });
    if (options.link) {
      n.onclick = () => {
        window.focus();
        window.location.href = options.link as string;
        n.close();
      };
    }
  } catch {
    /* z. B. iOS Safari ohne Support — ignorieren. */
  }
}
