/* Visitor-graph pixel (Task 357).
 *
 * Single IIFE. Plain JS. No build step. ~3 KB minified.
 *
 * Loaded by hosted listing pages:
 *
 *   <script defer src="https://<brand>/v.js" data-site="<slug>"></script>
 *
 * Emits to POST /v/event:
 *
 *   pageview  — once per session per URL (idempotent within 5s for SPA nav)
 *   click     — DOM clicks on elements with data-track="<label>", or
 *               anchors flagged data-track-gallery / data-track-floorplan
 *   scroll    — roll-up: one event per milestone {25,50,75,100} crossed
 *   dwell     — on visibility change to hidden / pagehide, sendBeacon
 *
 * Identity: the server resolves identity from the mxy_v cookie. The pixel
 * sends visitorId from localStorage (mints one if absent) as a fallback so
 * cross-tab and same-browser-no-cookie cases still cluster.
 */
(function () {
  if (typeof window === 'undefined' || typeof document === 'undefined') return
  var script = document.currentScript
  if (!script) return
  var siteSlug = script.getAttribute('data-site') || ''
  var endpoint = new URL('/v/event', script.src).toString()

  var CONSENT_KEY = 'mxy_consent'
  var VISITOR_KEY = 'mxy_visitor_id'
  var SESSION_KEY = 'mxy_session_id'
  var SESSION_PV_KEY = 'mxy_session_pv'
  var LAST_PV_KEY = 'mxy_last_pv'
  var SCROLL_KEY = 'mxy_scroll_depth'
  var DWELL_KEY = 'mxy_dwell_start'

  // ─────────── PECR consent gate (Task 375) ───────────
  // Reads the mxy_consent cookie set by /consent.js (and persisted server-side
  // via POST /v/consent). When the visitor has not accepted, the pixel is a
  // total no-op: no storage writes, no /v/event POSTs, no event listeners
  // attached. A later flip yes ← rebooted via the mxy:consent-changed event.
  function readConsentCookie() {
    var raw = (typeof document !== 'undefined' && document.cookie) || ''
    var parts = raw.split(';')
    for (var i = 0; i < parts.length; i++) {
      var kv = parts[i].trim().split('=')
      if (kv[0] === CONSENT_KEY) return decodeURIComponent(kv.slice(1).join('='))
    }
    return null
  }
  function purgeAll() {
    var keys = [VISITOR_KEY, SESSION_KEY, SESSION_PV_KEY, LAST_PV_KEY, SCROLL_KEY, DWELL_KEY]
    for (var i = 0; i < keys.length; i++) {
      try { localStorage.removeItem(keys[i]) } catch (_e) {}
      try { sessionStorage.removeItem(keys[i]) } catch (_e) {}
    }
  }
  var started = false
  function start() {
    if (started) return
    started = true
    boot()
  }
  // React to consent changes from the banner: yes→start (emit deferred
  // pageview), yes→no→purge storage and detach listeners by setting a flag
  // that all emit-paths check before sending.
  var revoked = false
  window.addEventListener('mxy:consent-changed', function (ev) {
    var decision = (ev && ev.detail && ev.detail.decision) || readConsentCookie()
    if (decision === 'yes') {
      revoked = false
      start()
    } else if (decision === 'no') {
      revoked = true
      purgeAll()
    }
  })
  if (readConsentCookie() !== 'yes') {
    // Banner / server will fire mxy:consent-changed when the visitor accepts.
    return
  }
  start()

  function boot() {

  function randomId() {
    if (window.crypto && window.crypto.getRandomValues) {
      var buf = new Uint8Array(16)
      window.crypto.getRandomValues(buf)
      return Array.prototype.map.call(buf, function (b) {
        return ('0' + b.toString(16)).slice(-2)
      }).join('')
    }
    return String(Date.now()) + '_' + Math.random().toString(36).slice(2, 10)
  }

  function getVisitorId() {
    try {
      var v = localStorage.getItem(VISITOR_KEY)
      if (v) return v
      var fresh = randomId()
      localStorage.setItem(VISITOR_KEY, fresh)
      return fresh
    } catch (_e) {
      return 'noltc-' + randomId()
    }
  }

  function getSessionId() {
    try {
      var s = sessionStorage.getItem(SESSION_KEY)
      if (s) return s
      var fresh = randomId()
      sessionStorage.setItem(SESSION_KEY, fresh)
      return fresh
    } catch (_e) {
      return 'nots-' + randomId()
    }
  }

  var visitorId = getVisitorId()
  var sessionId = getSessionId()

  function send(payload) {
    if (revoked) return
    payload.site = siteSlug
    payload.vid = visitorId
    payload.sid = sessionId
    payload.url = location.href
    payload.path = location.pathname
    payload.title = (document.title || '').slice(0, 256)
    payload.ts = Date.now()
    try {
      var body = JSON.stringify(payload)
      if (navigator.sendBeacon) {
        var blob = new Blob([body], { type: 'application/json' })
        if (navigator.sendBeacon(endpoint, blob)) return
      }
      fetch(endpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: body,
        credentials: 'include',
        keepalive: true,
      }).catch(function () { /* fire-and-forget */ })
    } catch (_e) {
      /* silently drop — pixel must never break the page */
    }
  }

  // ─────────── pageview (idempotent within 5s for SPA nav) ───────────
  function emitPageview() {
    if (revoked) return
    try {
      var last = sessionStorage.getItem(LAST_PV_KEY)
      if (last) {
        var parsed = JSON.parse(last)
        if (parsed && parsed.url === location.href && Date.now() - parsed.ts < 5000) {
          return
        }
      }
      sessionStorage.setItem(LAST_PV_KEY, JSON.stringify({ url: location.href, ts: Date.now() }))
      sessionStorage.setItem(SESSION_PV_KEY, randomId()) // PageView id for milestone roll-up
      sessionStorage.setItem(SCROLL_KEY, '0')
    } catch (_e) { /* localStorage / sessionStorage disabled */ }

    var pvId = ''
    try { pvId = sessionStorage.getItem(SESSION_PV_KEY) || '' } catch (_e) {}
    send({
      type: 'pageview',
      pvId: pvId,
      referrer: document.referrer || '',
    })
  }

  // ─────────── click tracking ───────────
  document.addEventListener('click', function (ev) {
    var el = ev.target && (ev.target.closest ? ev.target.closest('[data-track]') : null)
    if (!el) return
    var label = el.getAttribute('data-track') || 'unknown'
    var pvId = ''
    try { pvId = sessionStorage.getItem(SESSION_PV_KEY) || '' } catch (_e) {}
    send({
      type: 'click',
      label: label.slice(0, 64),
      pvId: pvId,
      href: el.getAttribute('href') || '',
    })
  }, true)

  // ─────────── scroll milestones (roll-up) ───────────
  var scrollScheduled = false
  function onScroll() {
    if (scrollScheduled) return
    scrollScheduled = true
    requestAnimationFrame(function () {
      scrollScheduled = false
      var doc = document.documentElement
      var max = Math.max(doc.scrollHeight, document.body.scrollHeight) - window.innerHeight
      if (max <= 0) return
      var pct = Math.floor(((window.scrollY || doc.scrollTop) / max) * 100)
      var threshold = pct >= 100 ? 100 : pct >= 75 ? 75 : pct >= 50 ? 50 : pct >= 25 ? 25 : 0
      if (threshold === 0) return
      try {
        var prev = parseInt(sessionStorage.getItem(SCROLL_KEY) || '0', 10) || 0
        if (threshold <= prev) return
        sessionStorage.setItem(SCROLL_KEY, String(threshold))
      } catch (_e) {}
      var pvId = ''
      try { pvId = sessionStorage.getItem(SESSION_PV_KEY) || '' } catch (_e) {}
      send({ type: 'scroll', depth: threshold, pvId: pvId })
    })
  }
  window.addEventListener('scroll', onScroll, { passive: true })

  // ─────────── dwell on unload / visibility change ───────────
  try { sessionStorage.setItem(DWELL_KEY, String(Date.now())) } catch (_e) {}
  function emitDwell() {
    var start = 0
    try { start = parseInt(sessionStorage.getItem(DWELL_KEY) || '0', 10) || 0 } catch (_e) {}
    if (!start) return
    var ms = Date.now() - start
    if (ms < 250) return // ignore drive-bys
    var pvId = ''
    try { pvId = sessionStorage.getItem(SESSION_PV_KEY) || '' } catch (_e) {}
    send({ type: 'dwell', ms: ms, pvId: pvId })
  }
  document.addEventListener('visibilitychange', function () {
    if (document.visibilityState === 'hidden') emitDwell()
  })
  window.addEventListener('pagehide', emitDwell)

  // SPA navigation hooks — many listing sites are client-routed.
  var origPushState = history.pushState
  history.pushState = function () {
    var r = origPushState.apply(this, arguments)
    setTimeout(emitPageview, 0)
    return r
  }
  window.addEventListener('popstate', emitPageview)

  // Boot.
  emitPageview()
  } // end boot()
})()
