/**
 * SPD — Push Notification Manager
 * Handles SW registration, VAPID subscription, in-app audio & toast notifications
 */
class PushManager {
  constructor() {
    this.reg          = null;   // ServiceWorkerRegistration
    this.subscription = null;   // PushSubscription
    this._enabled     = false;
    this._ready       = false;
  }

  // ── Public API ──────────────────────────────────────────────────────────────

  /** Call once on DOMContentLoaded */
  async init() {
    if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
      console.warn('[Push] Not supported in this browser');
      return;
    }

    try {
      this.reg = await navigator.serviceWorker.register('/sw.js', {
        scope:          '/',
        updateViaCache: 'none',
      });

      await navigator.serviceWorker.ready;

      // Listen for push events relayed by the SW (page is open)
      navigator.serviceWorker.addEventListener('message', e => {
        if (e.data?.type === 'PUSH_RECEIVED') {
          this._onPushReceived(e.data.payload);
        }
      });

      this.subscription = await this.reg.pushManager.getSubscription();
      this._enabled     = !!this.subscription;
      this._ready       = true;
      this._syncBellUI();

    } catch (err) {
      console.error('[Push] Init failed:', err);
    }
  }

  /** Toggle subscription on/off */
  async toggle() {
    if (this._enabled) {
      await this.unsubscribe();
    } else {
      await this.subscribe();
    }
  }

  isEnabled() { return this._enabled; }

  async subscribe() {
    if (!this.reg) {
      await this.init();
      if (!this.reg) {
        this._toast('❌ Service worker not available', 'error');
        return;
      }
    }

    try {
      const permission = await Notification.requestPermission();
      if (permission !== 'granted') {
        this._toast('🔕 Notification permission denied — allow it in browser settings', 'warning');
        return;
      }

      // Fetch VAPID public key
      const res  = await fetch('/push/key');
      const json = await res.json();

      if (!json.key) {
        this._toast('❌ Notifications not configured — contact admin', 'error');
        return;
      }

      // Subscribe with browser push service
      this.subscription = await this.reg.pushManager.subscribe({
        userVisibleOnly:      true,
        applicationServerKey: this._b64ToArray(json.key),
      });

      // Save subscription to server
      const saveRes = await this._post('/push/subscribe', this.subscription.toJSON());
      if (!saveRes.ok) {
        throw new Error('Server rejected subscription (HTTP ' + saveRes.status + ')');
      }

      this._enabled = true;
      this._syncBellUI();
      this._toast('🔔 Notifications enabled!', 'success');
    } catch (err) {
      console.error('[Push] Subscribe failed:', err);
      this._toast('❌ ' + (err?.message || String(err)), 'error');
    }
  }

  async unsubscribe() {
    try {
      if (this.subscription) {
        await this._post('/push/unsubscribe', { endpoint: this.subscription.endpoint });
        await this.subscription.unsubscribe();
        this.subscription = null;
      }
      this._enabled = false;
      this._syncBellUI();
      this._toast('🔕 Notifications disabled', 'info');
    } catch (err) {
      console.error('[Push] Unsubscribe failed:', err);
    }
  }

  // ── Internal ────────────────────────────────────────────────────────────────

  _onPushReceived(payload) {
    const { type, title, body, data } = payload;

    // 1. Play synthesized chime
    this._playChime(type);

    // 2. Animate the bell icon
    this._animateBell();

    // 3. Show in-app slide-in toast
    this._showCard(title, body, data?.url, type);

    // 4. Dispatch custom event (other JS can listen)
    window.dispatchEvent(new CustomEvent('pushReceived', { detail: payload }));
  }

  // ── Audio (Web Audio API) ──────────────────────────────────────────────────

  _playChime(type) {
    try {
      const Ctx = window.AudioContext || window.webkitAudioContext;
      if (!Ctx) return;

      const ctx = new Ctx();

      if (type === 'deposit') {
        // Rising major triad: C5 → E5 → G5  (bright "cha-ching")
        this._tone(ctx, 523.25, 0.00, 0.18, 0.28);
        this._tone(ctx, 659.25, 0.13, 0.18, 0.28);
        this._tone(ctx, 783.99, 0.26, 0.38, 0.32);
      } else if (type === 'expense') {
        // Descending: E5 → C5 (outgoing money)
        this._tone(ctx, 659.25, 0.00, 0.16, 0.28);
        this._tone(ctx, 523.25, 0.22, 0.32, 0.30);
      } else {
        // Single A5 ping
        this._tone(ctx, 880, 0, 0.25, 0.25);
      }

      setTimeout(() => ctx.close().catch(() => {}), 1200);
    } catch { /* audio blocked — silent fail */ }
  }

  _tone(ctx, freq, delay, duration, vol = 0.28) {
    const osc  = ctx.createOscillator();
    const gain = ctx.createGain();
    osc.connect(gain);
    gain.connect(ctx.destination);
    osc.type            = 'sine';
    osc.frequency.value = freq;
    const t = ctx.currentTime + delay;
    gain.gain.setValueAtTime(0, t);
    gain.gain.linearRampToValueAtTime(vol, t + 0.02);
    gain.gain.exponentialRampToValueAtTime(0.001, t + duration);
    osc.start(t);
    osc.stop(t + duration + 0.05);
  }

  // ── Bell UI ─────────────────────────────────────────────────────────────────

  _syncBellUI() {
    const icon = document.getElementById('push-status-icon');
    const text = document.getElementById('push-status-text');
    const btn  = document.getElementById('push-notification-toggle');
    if (icon) icon.className = this._enabled ? 'fas fa-bell' : 'fas fa-bell-slash';
    if (text) text.textContent = this._enabled ? 'On' : 'Off';
    if (btn)  btn.title = this._enabled
      ? 'Notifications ON — click to disable'
      : 'Notifications OFF — click to enable';
  }

  _animateBell() {
    const icon = document.getElementById('push-status-icon');
    if (!icon) return;
    icon.style.transition = 'color .15s, transform .15s';
    icon.style.color      = '#10b981';
    icon.style.transform  = 'scale(1.5) rotate(-20deg)';
    setTimeout(() => { icon.style.transform = 'scale(1.2) rotate(20deg)'; }, 150);
    setTimeout(() => { icon.style.transform = ''; icon.style.color = ''; }, 320);
  }

  // ── In-app slide-in card ───────────────────────────────────────────────────

  _showCard(title, body, url, type) {
    let container = document.getElementById('spd-push-cards');
    if (!container) {
      container = document.createElement('div');
      container.id = 'spd-push-cards';
      Object.assign(container.style, {
        position:      'fixed',
        top:           '72px',
        right:         '16px',
        zIndex:        '99999',
        display:       'flex',
        flexDirection: 'column',
        gap:           '10px',
        pointerEvents: 'none',
        maxWidth:      '360px',
        width:         'calc(100vw - 32px)',
      });
      document.body.appendChild(container);
    }

    const accent = type === 'expense' ? '#ef4444' : type === 'deposit' ? '#10b981' : '#3b82f6';
    const icon   = type === 'expense' ? '💸' : type === 'deposit' ? '💰' : '🔔';

    const card = document.createElement('div');
    Object.assign(card.style, {
      background:    '#1e293b',
      color:         '#f8fafc',
      borderRadius:  '14px',
      padding:       '14px 16px',
      boxShadow:     '0 8px 32px rgba(0,0,0,.45)',
      borderLeft:    `4px solid ${accent}`,
      pointerEvents: 'all',
      cursor:        url ? 'pointer' : 'default',
      display:       'flex',
      alignItems:    'flex-start',
      gap:           '12px',
      transform:     'translateX(calc(100% + 20px))',
      transition:    'transform .35s cubic-bezier(.22,.61,.36,1)',
      willChange:    'transform',
    });

    card.innerHTML = `
      <span style="font-size:1.5rem;line-height:1;flex-shrink:0">${icon}</span>
      <div style="flex:1;min-width:0">
        <div style="font-weight:700;font-size:.88rem;margin-bottom:3px;line-height:1.3">${this._esc(title)}</div>
        <div style="font-size:.8rem;opacity:.75;line-height:1.4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${this._esc(body)}</div>
      </div>
      <button style="background:none;border:none;color:rgba(255,255,255,.4);font-size:.95rem;cursor:pointer;padding:0;flex-shrink:0;line-height:1;margin-top:1px"
              onclick="event.stopPropagation();this.closest('.spd-card').remove()">✕</button>
    `;
    card.classList.add('spd-card');

    if (url) {
      card.addEventListener('click', e => {
        if (e.target.tagName !== 'BUTTON') window.location.href = url;
      });
    }

    container.appendChild(card);

    // Slide in
    requestAnimationFrame(() => {
      requestAnimationFrame(() => { card.style.transform = 'translateX(0)'; });
    });

    // Auto-dismiss after 6 s
    const dismiss = () => {
      card.style.transform = 'translateX(calc(100% + 20px))';
      setTimeout(() => card.remove(), 400);
    };
    const timer = setTimeout(dismiss, 6000);
    card.addEventListener('mouseenter', () => clearTimeout(timer));
  }

  // ── Status toast (subscribe/unsubscribe feedback) ──────────────────────────

  _toast(msg, type = 'info') {
    const colors = { success: '#10b981', warning: '#f59e0b', error: '#ef4444', info: '#3b82f6' };
    const t = document.createElement('div');
    Object.assign(t.style, {
      background:   colors[type] ?? colors.info,
      color:        '#fff',
      borderRadius: '8px',
      padding:      '10px 18px',
      position:     'fixed',
      bottom:       '24px',
      right:        '24px',
      zIndex:       '99998',
      fontSize:     '.875rem',
      fontWeight:   '500',
      boxShadow:    '0 4px 14px rgba(0,0,0,.3)',
      transform:    'translateY(60px)',
      transition:   'transform .3s ease',
    });
    t.textContent = msg;
    document.body.appendChild(t);
    requestAnimationFrame(() => { t.style.transform = 'translateY(0)'; });
    setTimeout(() => {
      t.style.transform = 'translateY(60px)';
      setTimeout(() => t.remove(), 350);
    }, 3500);
  }

  // ── Utilities ───────────────────────────────────────────────────────────────

  _b64ToArray(base64) {
    const padding = '='.repeat((4 - (base64.length % 4)) % 4);
    const b64     = (base64 + padding).replace(/-/g, '+').replace(/_/g, '/');
    const raw     = atob(b64);
    return Uint8Array.from([...raw].map(c => c.charCodeAt(0)));
  }

  _post(url, data) {
    return fetch(url, {
      method:  'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content ?? '',
        'Accept':       'application/json',
      },
      body: JSON.stringify(data),
    });
  }

  _esc(s) {
    return String(s ?? '')
      .replace(/&/g, '&amp;').replace(/</g, '&lt;')
      .replace(/>/g, '&gt;').replace(/"/g, '&quot;');
  }
}

// ── Bootstrap ──────────────────────────────────────────────────────────────────
window.pushManager = new PushManager();

if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', () => window.pushManager.init());
} else {
  window.pushManager.init();
}
