(function () {
  'use strict';

  function qs(s, el) { return (el || document).querySelector(s); }
  function qsa(s, el) { return Array.from((el || document).querySelectorAll(s)); }

  /* ==========================================================================
   * SOUND (Web Audio API)
   * --------------------------------------------------------------------------
   * We synthesize short UI tones instead of shipping audio files. That keeps
   * the app offline-friendly and avoids autoplay policy issues with <audio>.
   *
   * Flow:
   *  1. ensureAudio()  — creates / resumes a shared AudioContext after a
   *                     user gesture (required by browsers).
   *  2. tone()         — one oscillator + gain envelope (attack → decay).
   *  3. VeloraSound.*  — named patterns that schedule one or more tones.
   *
   * Frequencies are approximate musical notes (Hz):
   *   C5=523, E5=659, G5=784, A5=880, D6=1175
   * ========================================================================== */
  var AudioCtx = window.AudioContext || window.webkitAudioContext;
  var audioCtx = null;

  /**
   * Lazily create the AudioContext and resume it if the browser suspended it.
   * Must be called from (or after) a user gesture for autoplay policies.
   */
  function ensureAudio() {
    if (!AudioCtx) return null;
    if (!audioCtx) {
      try { audioCtx = new AudioCtx(); } catch (e) { return null; }
    }
    // Chrome starts contexts as "suspended" until a gesture
    if (audioCtx.state === 'suspended') {
      audioCtx.resume().catch(function () {});
    }
    return audioCtx;
  }

  /**
   * Play a single enveloped tone.
   * @param {number} freq  Frequency in Hz
   * @param {number} dur   Duration in seconds (decay length)
   * @param {string} type  Oscillator type: sine | triangle | square | sawtooth
   * @param {number} gain  Peak gain (keep low: ~0.04–0.1 to avoid harshness)
   * @param {number} when  Delay from now in seconds (for chords / arpeggios)
   */
  function tone(freq, dur, type, gain, when) {
    var ctx = ensureAudio();
    if (!ctx) return;
    var t0 = ctx.currentTime + (when || 0);
    var osc = ctx.createOscillator();
    var g = ctx.createGain();
    osc.type = type || 'sine';
    osc.frequency.setValueAtTime(freq, t0);
    // Exponential ramps need non-zero values — use 0.0001 as near-silence
    g.gain.setValueAtTime(0.0001, t0);
    g.gain.exponentialRampToValueAtTime(gain || 0.08, t0 + 0.02); // soft attack
    g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);        // decay
    osc.connect(g);
    g.connect(ctx.destination);
    osc.start(t0);
    osc.stop(t0 + dur + 0.02);
  }

  /**
   * Named UI sound patterns. Each is a short sequence of tone() calls.
   * Keep patterns under ~0.5s so they feel snappy on mobile.
   */
  var VeloraSound = {
    /** Soft two-note "yes" — used on like / swipe right */
    like: function () {
      tone(523.25, 0.12, 'sine', 0.06);       // C5
      tone(659.25, 0.18, 'sine', 0.05, 0.08); // E5 slightly later
    },
    /** Low short thud — used on pass / swipe left */
    pass: function () {
      tone(220, 0.1, 'triangle', 0.04); // A3
    },
    /** Rising three-note sparkle — super like */
    super: function () {
      tone(523.25, 0.1, 'sine', 0.05);
      tone(659.25, 0.1, 'sine', 0.05, 0.08);
      tone(783.99, 0.2, 'sine', 0.06, 0.16); // G5
    },
    /**
     * Celebration arpeggio — mutual match.
     * Four notes walk up the major triad + top; longer final note.
     */
    match: function () {
      tone(392, 0.12, 'sine', 0.07);           // G4
      tone(523.25, 0.12, 'sine', 0.07, 0.1);   // C5
      tone(659.25, 0.14, 'sine', 0.08, 0.2);   // E5
      tone(783.99, 0.28, 'sine', 0.09, 0.32);  // G5 sustain
    },
    /** Tiny chime when an incoming chat message arrives via poll */
    message: function () {
      tone(880, 0.08, 'sine', 0.04);            // A5
      tone(1174.66, 0.12, 'sine', 0.05, 0.07);  // D6
    }
  };
  window.VeloraSound = VeloraSound;

  // Unlock audio on first interaction so later programmatic plays work
  ['pointerdown', 'touchstart', 'click'].forEach(function (ev) {
    document.addEventListener(ev, function () { ensureAudio(); }, { once: true, passive: true });
  });

  /* ==========================================================================
   * HAPTIC FEEDBACK (Vibration API)
   * --------------------------------------------------------------------------
   * navigator.vibrate(pattern) — pattern is ms on/off alternating.
   * Silently no-ops on iOS Safari and unsupported browsers.
   * ========================================================================== */
  function canVibrate() {
    return typeof navigator !== 'undefined' && typeof navigator.vibrate === 'function';
  }

  /**
   * @param {number|number[]} pattern  Single duration or [vibrate, pause, vibrate, ...]
   */
  function haptic(pattern) {
    if (!canVibrate()) return;
    try { navigator.vibrate(pattern); } catch (e) { /* ignore */ }
  }

  var VeloraHaptic = {
    /** Light tick — like */
    like: function () { haptic(12); },
    /** Soft double — pass */
    pass: function () { haptic([8, 40, 8]); },
    /** Stronger pulse — super like */
    super: function () { haptic([15, 30, 15, 30, 25]); },
    /** Celebratory pattern — match (long, short, long) */
    match: function () { haptic([40, 60, 25, 50, 40, 60, 80]); },
    /** Subtle tick — incoming message */
    message: function () { haptic(10); },
    /** Selection / button press */
    tap: function () { haptic(8); }
  };
  window.VeloraHaptic = VeloraHaptic;

  /* ==========================================================================
   * PARTICLE BURST — match celebration
   * --------------------------------------------------------------------------
   * Lightweight DOM particles (no canvas dependency). Spawned over the match
   * toast and removed after their CSS animation ends.
   * ========================================================================== */
  var PARTICLE_COLORS = ['#ff4f81', '#a855f7', '#34d399', '#60a5fa', '#fbbf24', '#fb7185', '#e879f9'];
  var PARTICLE_SHAPES = ['●', '♥', '✦', '◆', '●'];

  function particleBurst(originEl, count) {
    count = count || 28;
    var layer = document.createElement('div');
    layer.className = 'particle-layer';
    layer.setAttribute('aria-hidden', 'true');
    document.body.appendChild(layer);

    var rect = originEl
      ? originEl.getBoundingClientRect()
      : { left: window.innerWidth / 2, top: window.innerHeight / 2, width: 0, height: 0 };
    var cx = rect.left + rect.width / 2;
    var cy = rect.top + rect.height / 2;

    for (var i = 0; i < count; i++) {
      var p = document.createElement('span');
      p.className = 'particle';
      p.textContent = PARTICLE_SHAPES[i % PARTICLE_SHAPES.length];
      var angle = (Math.PI * 2 * i) / count + (Math.random() * 0.4);
      var dist = 60 + Math.random() * 140;
      var tx = Math.cos(angle) * dist;
      var ty = Math.sin(angle) * dist - 20;
      var size = 10 + Math.random() * 14;
      var color = PARTICLE_COLORS[i % PARTICLE_COLORS.length];
      var dur = 0.7 + Math.random() * 0.55;
      p.style.cssText = [
        'left:' + cx + 'px',
        'top:' + cy + 'px',
        'font-size:' + size + 'px',
        'color:' + color,
        '--tx:' + tx + 'px',
        '--ty:' + ty + 'px',
        'animation-duration:' + dur + 's',
        'animation-delay:' + (Math.random() * 0.08) + 's'
      ].join(';');
      layer.appendChild(p);
    }

    setTimeout(function () {
      if (layer.parentNode) layer.parentNode.removeChild(layer);
    }, 1600);
  }

  function celebrateMatch(originEl) {
    VeloraSound.match();
    VeloraHaptic.match();
    particleBurst(originEl || qs('.match-toast-inner'), 32);
  }
  window.VeloraCelebrateMatch = celebrateMatch;

  if (qs('.chat-page')) document.body.classList.add('chat-focus');

  /* ---------- Chat poll + AJAX ---------- */
  var chat = qs('.chat[data-partner]');
  if (chat) {
    var box = qs('#messages', chat);
    var form = qs('#chat-form', chat);
    var input = qs('#chat-body', chat);
    var partner = chat.getAttribute('data-partner');
    var pollUrl = chat.getAttribute('data-poll');
    var lastId = 0;
    qsa('.msg[data-id]', box).forEach(function (el) {
      var id = parseInt(el.getAttribute('data-id'), 10);
      if (id > lastId) lastId = id;
    });
    function scrollBottom() { if (box) box.scrollTop = box.scrollHeight; }
    scrollBottom();
    function escapeHtml(t) {
      var d = document.createElement('div'); d.textContent = t; return d.innerHTML;
    }
    function formatTime(iso) {
      try {
        return new Date((iso || '').replace(' ', 'T')).toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });
      } catch (e) { return ''; }
    }
    function appendMsg(m) {
      if (qs('.msg[data-id="' + m.id + '"]', box)) return;
      var empty = qs('#chat-empty', box);
      if (empty) empty.remove();
      var div = document.createElement('div');
      div.className = 'msg' + (m.mine ? ' mine' : '');
      div.setAttribute('data-id', m.id);
      div.innerHTML = escapeHtml(m.body).replace(/\n/g, '<br>') +
        '<small>' + escapeHtml(formatTime(m.created_at)) + (m.mine && m.is_read ? ' · Read' : '') + '</small>';
      box.appendChild(div);
      if (m.id > lastId) lastId = m.id;
      if (!m.mine) {
        VeloraSound.message();
        VeloraHaptic.message();
      }
      scrollBottom();
    }
    if (form) {
      form.addEventListener('submit', function (e) {
        e.preventDefault();
        var body = (input.value || '').trim();
        if (!body) return;
        var fd = new FormData(form);
        fd.set('ajax', '1');
        input.disabled = true;
        VeloraHaptic.tap();
        fetch(form.action, {
          method: 'POST', body: fd, credentials: 'same-origin',
          headers: { 'X-Requested-With': 'XMLHttpRequest' }
        }).then(function (r) { return r.json(); }).then(function (data) {
          input.disabled = false;
          if (data && data.ok && data.message) {
            appendMsg(data.message);
            input.value = '';
            input.focus();
          } else if (data && data.error) alert(data.error);
        }).catch(function () { input.disabled = false; form.submit(); });
      });
    }
    function poll() {
      if (document.hidden) return;
      var url = pollUrl + (pollUrl.indexOf('?') >= 0 ? '&' : '?') + 'user=' + encodeURIComponent(partner) + '&after=' + lastId;
      fetch(url, { credentials: 'same-origin', headers: { Accept: 'application/json' } })
        .then(function (r) { return r.json(); })
        .then(function (data) {
          if (!data || !data.ok) return;
          if (data.messages) data.messages.forEach(appendMsg);
          var presence = qs('#presence-label');
          var typingEl = qs('#typing-label');
          if (presence) {
            if (data.online) {
              presence.innerHTML = '<span class="online-dot"></span> Active now';
            }
          }
          if (typingEl) {
            typingEl.hidden = !data.typing;
          }
        }).catch(function () {});
    }
    setInterval(poll, 2000);

    // Typing indicator: notify peer while composing
    var typingUrl = chat.getAttribute('data-typing');
    var typingTimer = null;
    var lastTypingSent = 0;
    function sendTyping() {
      if (!typingUrl || !partner) return;
      var now = Date.now();
      if (now - lastTypingSent < 1500) return;
      lastTypingSent = now;
      var fd = new FormData();
      fd.append('peer_id', partner);
      fd.append('_csrf', chat.getAttribute('data-csrf') || '');
      fetch(typingUrl, { method: 'POST', body: fd, credentials: 'same-origin' }).catch(function () {});
    }
    if (input) {
      input.addEventListener('input', function () {
        sendTyping();
      });
    }
  }

  /* ---------- Discovery swipe ----------
   * Pointer Events (mouse + touch unified)
   * - Horizontal lock after 12px so photo taps / vertical scroll don't fight
   * - Threshold ~28% of width (min 90px)
   * - Ignore starts on photo-hit / buttons / links
   * - Keyboard: ArrowLeft = pass, ArrowRight = like, ArrowUp = super (if available)
   */
  var card = qs('#swipe-card');
  var wrap = qs('.discover-wrap');
  if (card && wrap) {
    var startX = 0, startY = 0, dx = 0, dy = 0;
    var dragging = false, locked = false, axis = null; // axis: 'x' | 'y' | null
    var threshold = Math.max(90, Math.min(140, window.innerWidth * 0.28));
    var pointerId = null;

    function setTransform(x, y, rot) {
      card.style.transform = 'translate3d(' + x + 'px,' + y + 'px,0) rotate(' + rot + 'deg)';
    }
    function clearStamps() {
      card.classList.remove('show-like', 'show-nope', 'show-super');
    }
    function updateStamps() {
      clearStamps();
      if (dx > 36) card.classList.add('show-like');
      else if (dx < -36) card.classList.add('show-nope');
      else if (dy < -50 && qs('#form-super')) card.classList.add('show-super');
    }

    function finish(action) {
      if (locked) return;
      locked = true;
      card.classList.remove('is-dragging');
      card.style.transition = 'transform .32s cubic-bezier(.2,.8,.2,1), opacity .32s ease';
      if (action === 'like') {
        if (window.VeloraSound) VeloraSound.like();
        if (window.VeloraHaptic) VeloraHaptic.like();
        card.classList.add('fly-right', 'show-like');
        setTransform(window.innerWidth * 1.2, dy, 18);
        setTimeout(function () { submitAction('like'); }, 300);
      } else if (action === 'pass') {
        if (window.VeloraSound) VeloraSound.pass();
        if (window.VeloraHaptic) VeloraHaptic.pass();
        card.classList.add('fly-left', 'show-nope');
        setTransform(-window.innerWidth * 1.2, dy, -18);
        setTimeout(function () { submitAction('pass'); }, 300);
      } else if (action === 'superlike') {
        if (window.VeloraSound) VeloraSound.super();
        if (window.VeloraHaptic) VeloraHaptic.super();
        card.classList.add('fly-up', 'show-super');
        setTransform(0, -window.innerHeight * 0.8, 0);
        setTimeout(function () { submitAction('superlike'); }, 340);
      } else {
        locked = false;
        setTransform(0, 0, 0);
        clearStamps();
        setTimeout(function () { card.style.transition = ''; }, 320);
      }
    }

    function submitAction(action) {
      var form = qs('#form-' + (action === 'superlike' ? 'super' : action));
      if (form) form.submit();
      else {
        locked = false;
        card.classList.remove('fly-up', 'show-super', 'fly-left', 'fly-right');
        setTransform(0, 0, 0);
        card.style.transition = '';
      }
    }

    function shouldIgnoreTarget(el) {
      if (!el || !el.closest) return false;
      return !!(el.closest('.photo-hit') || el.closest('button') || el.closest('a') || el.closest('form') || el.closest('.discover-actions'));
    }

    function onPointerDown(e) {
      if (locked || e.button === 2) return;
      if (shouldIgnoreTarget(e.target)) return;
      pointerId = e.pointerId;
      try { card.setPointerCapture(e.pointerId); } catch (err) {}
      startX = e.clientX; startY = e.clientY;
      dx = dy = 0; axis = null; dragging = true;
      card.classList.add('is-dragging');
      card.style.transition = 'none';
      if (window.VeloraHaptic) VeloraHaptic.tap();
    }
    function onPointerMove(e) {
      if (!dragging || locked) return;
      if (pointerId !== null && e.pointerId !== pointerId) return;
      dx = e.clientX - startX;
      dy = e.clientY - startY;
      if (!axis) {
        if (Math.abs(dx) > 12 || Math.abs(dy) > 12) {
          axis = Math.abs(dx) >= Math.abs(dy) ? 'x' : 'y';
        } else {
          return;
        }
      }
      if (axis === 'y' && !qs('#form-super')) {
        // Allow page scroll when vertical and no super-like
        return;
      }
      var useX = axis === 'x' ? dx : 0;
      var useY = axis === 'y' ? dy : dy * 0.25;
      var rot = useX * 0.07;
      setTransform(useX, useY, rot);
      updateStamps();
      if (axis === 'x' && e.cancelable) e.preventDefault();
    }
    function onPointerUp(e) {
      if (!dragging || locked) return;
      if (pointerId !== null && e.pointerId !== pointerId) return;
      dragging = false;
      pointerId = null;
      card.classList.remove('is-dragging');
      if (axis === 'x') {
        if (dx > threshold) finish('like');
        else if (dx < -threshold) finish('pass');
        else finish(null);
      } else if (axis === 'y' && dy < -threshold * 0.85 && qs('#form-super')) {
        finish('superlike');
      } else {
        finish(null);
      }
      axis = null;
    }

    card.addEventListener('pointerdown', onPointerDown);
    card.addEventListener('pointermove', onPointerMove);
    card.addEventListener('pointerup', onPointerUp);
    card.addEventListener('pointercancel', onPointerUp);
    card.style.touchAction = 'pan-y';

    // Buttons still animate before submit
    [['#form-like', 'like'], ['#form-pass', 'pass'], ['#form-super', 'superlike']].forEach(function (pair) {
      var form = qs(pair[0]);
      if (!form) return;
      form.addEventListener('submit', function (e) {
        e.preventDefault();
        if (locked) return;
        finish(pair[1]);
      });
    });

    document.addEventListener('keydown', function (e) {
      if (locked || !document.body.contains(card)) return;
      var tag = (e.target && e.target.tagName) || '';
      if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return;
      if (e.key === 'ArrowRight') { e.preventDefault(); finish('like'); }
      else if (e.key === 'ArrowLeft') { e.preventDefault(); finish('pass'); }
      else if (e.key === 'ArrowUp' && qs('#form-super')) { e.preventDefault(); finish('superlike'); }
    });
  }

  /* Card photo browser (tap left/right edges) */
  var cardPhotos = document.getElementById('card-photos');
  if (cardPhotos) {
    var photos = qsa('.card-photo', cardPhotos);
    var bars = qsa('.photo-bar', cardPhotos);
    var pi = 0;
    function showPhoto(i) {
      if (!photos.length) return;
      pi = (i + photos.length) % photos.length;
      photos.forEach(function (img, n) { img.classList.toggle('is-active', n === pi); });
      bars.forEach(function (b, n) { b.classList.toggle('is-active', n === pi); });
    }
    var prevBtn = qs('#photo-prev');
    var nextBtn = qs('#photo-next');
    if (prevBtn) prevBtn.addEventListener('click', function (e) { e.stopPropagation(); showPhoto(pi - 1); });
    if (nextBtn) nextBtn.addEventListener('click', function (e) { e.stopPropagation(); showPhoto(pi + 1); });
  }

  /* Match toast + particle burst */
  var toast = qs('#match-toast');
  if (toast) {
    var successAlert = qs('.alert.success');
    if (successAlert && /match/i.test(successAlert.textContent || '')) {
      toast.hidden = false;
      // Sound + haptic + particles after paint so layout is ready
      requestAnimationFrame(function () {
        celebrateMatch(qs('.match-toast-inner'));
      });
      if (successAlert.parentNode) successAlert.style.display = 'none';
    }
    var dismiss = qs('#match-toast-dismiss');
    if (dismiss) dismiss.addEventListener('click', function () {
      toast.hidden = true;
      VeloraHaptic.tap();
    });
  }


  /* Photo carousel on full profile */
  var carousel = qs('#photo-carousel');
  if (carousel) {
    var slides = qsa('.carousel-slide', carousel);
    var dots = qsa('.carousel-dots .dot', carousel);
    var idx = 0;
    function show(i) {
      if (!slides.length) return;
      idx = (i + slides.length) % slides.length;
      slides.forEach(function (s, n) { s.classList.toggle('is-active', n === idx); });
      dots.forEach(function (d, n) { d.classList.toggle('is-active', n === idx); });
    }
    var prev = qs('.carousel-nav.prev', carousel);
    var next = qs('.carousel-nav.next', carousel);
    if (prev) prev.addEventListener('click', function () { show(idx - 1); });
    if (next) next.addEventListener('click', function () { show(idx + 1); });
    dots.forEach(function (d) {
      d.addEventListener('click', function () { show(parseInt(d.getAttribute('data-i'), 10)); });
    });
    // Tap left/right already via nav zones
  }

})();
