/* PECR consent banner (Task 375).
 *
 * Loads BEFORE /v.js on any page that wires in the visitor pixel:
 *
 *   <link rel="stylesheet" href="/consent.css">
 *   <script defer src="/consent.js"></script>
 *   <script defer src="/v.js" data-site="<slug>"></script>
 *
 * Responsibility:
 *   1. Reads mxy_consent cookie. If absent → render banner.
 *   2. On accept → POST /v/consent {decision:"yes", v?:<token>}, server sets
 *      mxy_consent (12mo) + (if v= present in URL and HMAC-valid) mxy_v.
 *   3. On reject → POST /v/consent {decision:"no"}, banner hides, never re-shows
 *      until manageCookies() is called or cookie expires.
 *   4. After accept with ?consent=needed&v=… in URL, calls history.replaceState
 *      to strip both params so the token doesn't sit in browser history.
 *   5. Dispatches `mxy:consent-changed` so v.js can react without reloading.
 *   6. Exposes window.mxyManageCookies() to re-open the banner from a footer link.
 *   7. On yes→no transition, purges every visitor-graph storage key the pixel
 *      could have written.
 *
 * No build step. Plain JS IIFE. ~2.5 KB.
 */
(function () {
  if (typeof window === 'undefined' || typeof document === 'undefined') return

  var COOKIE_NAME = 'mxy_consent'
  var CONSENT_ENDPOINT = '/v/consent'
  var BRAND_CONFIG_ENDPOINT = '/v/brand-config'
  // Storage keys the pixel may write. Mirror v.js literals.
  var PIXEL_STORAGE_KEYS = [
    'mxy_visitor_id', // localStorage
    'mxy_session_id', // sessionStorage
    'mxy_session_pv',
    'mxy_last_pv',
    'mxy_scroll_depth',
    'mxy_dwell_start',
  ]

  var DEFAULT_COPY = {
    title: 'Before you browse',
    body: 'We use a small first-party script to count page views, scrolls and clicks on this listing. Nothing is shared with third parties. You can change your mind at any time.',
    accept: 'Accept',
    reject: 'Reject',
    manage: 'Manage cookies',
    policyLinkText: 'Read the cookie policy',
    policyHref: '/privacy.html#cookies',
  }
  var DEFAULT_PALETTE = {
    bg: '#FFFFFF',
    fg: '#1F1F1F',
    accent: '#1F1F1F',
    accentFg: '#FFFFFF',
    border: '#E5E5E2',
  }

  function readCookie(name) {
    var raw = document.cookie || ''
    var parts = raw.split(';')
    for (var i = 0; i < parts.length; i++) {
      var kv = parts[i].trim().split('=')
      if (kv[0] === name) return decodeURIComponent(kv.slice(1).join('='))
    }
    return null
  }

  function readUrlToken() {
    try {
      var u = new URL(window.location.href)
      if (u.searchParams.get('consent') !== 'needed') return null
      var v = u.searchParams.get('v')
      return v && v.length > 0 && v.length <= 2048 ? v : null
    } catch (_e) {
      return null
    }
  }

  function stripConsentQuery() {
    try {
      var u = new URL(window.location.href)
      if (!u.searchParams.has('consent') && !u.searchParams.has('v')) return
      u.searchParams.delete('consent')
      u.searchParams.delete('v')
      var next = u.pathname + (u.searchParams.toString() ? '?' + u.searchParams.toString() : '') + u.hash
      history.replaceState(null, '', next)
    } catch (_e) { /* never break the page */ }
  }

  function purgePixelStorage() {
    for (var i = 0; i < PIXEL_STORAGE_KEYS.length; i++) {
      var k = PIXEL_STORAGE_KEYS[i]
      try { localStorage.removeItem(k) } catch (_e) {}
      try { sessionStorage.removeItem(k) } catch (_e) {}
    }
  }

  function fireChange(decision) {
    try {
      window.dispatchEvent(new CustomEvent('mxy:consent-changed', { detail: { decision: decision } }))
    } catch (_e) {
      // IE/older Safari fallback — synthesise a plain Event.
      try {
        var ev = document.createEvent('Event')
        ev.initEvent('mxy:consent-changed', true, true)
        ev.detail = { decision: decision }
        window.dispatchEvent(ev)
      } catch (_e2) {}
    }
  }

  function postConsent(decision, token) {
    var body = JSON.stringify(token ? { decision: decision, v: token } : { decision: decision })
    return fetch(CONSENT_ENDPOINT, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: body,
      credentials: 'include',
      keepalive: true,
    }).catch(function () { /* server records the decision; UI proceeds regardless */ })
  }

  function fetchBrandConfig() {
    return fetch(BRAND_CONFIG_ENDPOINT, { credentials: 'omit' })
      .then(function (r) { return r.ok ? r.json() : null })
      .catch(function () { return null })
  }

  function applyPalette(palette) {
    var root = document.documentElement
    for (var k in palette) {
      if (Object.prototype.hasOwnProperty.call(palette, k)) {
        root.style.setProperty('--mxy-consent-' + k, palette[k])
      }
    }
  }

  var bannerEl = null

  function buildBanner(copy) {
    var root = document.createElement('div')
    root.id = 'mxy-consent'
    root.setAttribute('role', 'dialog')
    root.setAttribute('aria-live', 'polite')
    root.setAttribute('aria-label', copy.title)

    var inner = document.createElement('div')
    inner.className = 'mxy-consent__inner'

    var text = document.createElement('div')
    text.className = 'mxy-consent__text'
    var h = document.createElement('strong')
    h.className = 'mxy-consent__title'
    h.textContent = copy.title
    var p = document.createElement('p')
    p.className = 'mxy-consent__body'
    p.textContent = copy.body
    var link = document.createElement('a')
    link.className = 'mxy-consent__link'
    link.href = copy.policyHref
    link.textContent = copy.policyLinkText
    link.target = '_blank'
    link.rel = 'noopener'
    text.appendChild(h)
    text.appendChild(p)
    text.appendChild(link)

    var actions = document.createElement('div')
    actions.className = 'mxy-consent__actions'
    var reject = document.createElement('button')
    reject.className = 'mxy-consent__btn mxy-consent__btn--reject'
    reject.type = 'button'
    reject.textContent = copy.reject
    var accept = document.createElement('button')
    accept.className = 'mxy-consent__btn mxy-consent__btn--accept'
    accept.type = 'button'
    accept.textContent = copy.accept
    // Order: reject first then accept; CSS equal-weights them. ICO requires
    // the reject path to be no harder than the accept path.
    actions.appendChild(reject)
    actions.appendChild(accept)

    inner.appendChild(text)
    inner.appendChild(actions)
    root.appendChild(inner)

    accept.addEventListener('click', function () { onDecide('yes') })
    reject.addEventListener('click', function () { onDecide('no') })

    return root
  }

  function hideBanner() {
    if (bannerEl && bannerEl.parentNode) {
      bannerEl.parentNode.removeChild(bannerEl)
    }
    bannerEl = null
  }

  function onDecide(decision) {
    var token = decision === 'yes' ? readUrlToken() : null
    // Optimistic local cookie write so v.js sees consent immediately. The
    // server POST below will refresh the lifetime to the full 12 months;
    // this JS write is ITP-clamped to 7 days on Safari but covers the gap
    // until the server response lands.
    document.cookie = COOKIE_NAME + '=' + decision + '; Max-Age=' + (60 * 60 * 24 * 365) + '; Path=/; SameSite=Lax; Secure'
    if (decision === 'no') purgePixelStorage()
    if (decision === 'yes') stripConsentQuery()
    hideBanner()

    var p = postConsent(decision, token)
    if (decision === 'yes' && token) {
      // Wait for the server to set HttpOnly mxy_v before letting v.js fire
      // the first pageview. Otherwise the first event is attributed to
      // AnonVisitor and the cross-session merge runs one event later.
      p.then(function () { fireChange(decision) })
    } else {
      // No token to bind → no reason to wait; emit immediately.
      fireChange(decision)
    }
  }

  function maybeShowBanner() {
    var current = readCookie(COOKIE_NAME)
    if (current === 'yes' || current === 'no') return
    fetchBrandConfig().then(function (cfg) {
      var consent = (cfg && cfg.consent) || {}
      var copy = {}
      for (var k in DEFAULT_COPY) copy[k] = (consent.copy && consent.copy[k]) || DEFAULT_COPY[k]
      var palette = {}
      for (var p in DEFAULT_PALETTE) palette[p] = (consent.palette && consent.palette[p]) || DEFAULT_PALETTE[p]
      applyPalette(palette)
      bannerEl = buildBanner(copy)
      document.body.appendChild(bannerEl)
    })
  }

  // Withdrawal entry point — footer links call this to re-open the banner
  // when the visitor wants to change their mind. Purge happens in onDecide
  // when the visitor actually flips to reject, not here.
  window.mxyManageCookies = function () {
    if (bannerEl) hideBanner()
    fetchBrandConfig().then(function (cfg) {
      var consent = (cfg && cfg.consent) || {}
      var copy = {}
      for (var k in DEFAULT_COPY) copy[k] = (consent.copy && consent.copy[k]) || DEFAULT_COPY[k]
      var palette = {}
      for (var p in DEFAULT_PALETTE) palette[p] = (consent.palette && consent.palette[p]) || DEFAULT_PALETTE[p]
      applyPalette(palette)
      bannerEl = buildBanner(copy)
      document.body.appendChild(bannerEl)
    })
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', maybeShowBanner)
  } else {
    maybeShowBanner()
  }
})()
