// bha-rewards-engine.jsx — Adherence-driven points & rewards engine for BHCA
// Owns the points ledger, pending pool, streak/adherence math, composite score,
// weekly lottery eligibility, compliant catalog, and payer-type redemption caps.
// Persists to localStorage and notifies subscribers so the Home card + Rewards
// screen stay in sync. Exposed as window.BHA_RW.

(function () {
  const KEY = 'bha_rewards_v3'
  const DAILY_SCHEDULED = 3
  const WEEKLY_BONUS = 50
  const MONTHLY_BONUS = 250
  const MISS_PENALTY = 50
  const WEEKLY_THRESHOLD = 0.80 // 80% of scheduled doses

  // ── Point economy ────────────────────────────────────────────
  const POINTS = {
    dose: 10, biometric: 10, appointment: 200, screening: 500,
    education: 50, streak7: 50, streak30: 250, referral: 300,
  }
  // 100 points = $1
  const dollars = (pts) => (pts / 100)

  // ── Compliant reward catalog ─────────────────────────────────
  // qualifying = access-to-care OR health-device → exempt from Medicaid/Medicare caps
  const CATALOG = [
    { id: 'bp-cuff',   name: 'Connected BP Cuff (Omron)',        category: 'Connected health device', pointCost: 1200, retailValue: 48, careNexus: 'Home blood-pressure monitoring for hypertension', qualifying: true },
    { id: 'glucose',   name: 'Connected Glucose Meter Kit',      category: 'Connected health device', pointCost: 1000, retailValue: 40, careNexus: 'Daily glucose tracking for diabetes',          qualifying: true },
    { id: 'rides',     name: 'Rideshare to Appointments (4 trips)', category: 'Rideshare / transit',  pointCost: 800,  retailValue: 60, careNexus: 'Transportation to keep medical appointments',  qualifying: true },
    { id: 'transit',   name: 'Transit Card — 10 trips',          category: 'Rideshare / transit',     pointCost: 500,  retailValue: 25, careNexus: 'Bus / rail trips to care visits',             qualifying: true },
    { id: 'copay',     name: 'Pharmacy Copay Support ($10)',     category: 'Pharmacy copay support',  pointCost: 1000, retailValue: 10, careNexus: 'Reduces out-of-pocket medication cost',       qualifying: true },
    { id: 'grocery',   name: 'Healthy Grocery Credit ($25)',     category: 'Healthy food / grocery',  pointCost: 2500, retailValue: 25, careNexus: 'Produce & healthy staples for DASH / diabetic diet', qualifying: false },
    { id: 'produce',   name: 'Fresh Produce Box — 1 month',      category: 'Healthy food / grocery',  pointCost: 3000, retailValue: 30, careNexus: 'Weekly fresh produce delivery',               qualifying: false },
    { id: 'fitness',   name: 'Fitness App — 3 months',           category: 'Fitness / wellness',      pointCost: 1500, retailValue: 30, careNexus: 'Guided exercise for cardiovascular health',    qualifying: false },
    { id: 'box',       name: 'Sankofa Wellness Box',             category: 'Sankofa recognition',     pointCost: 1800, retailValue: 20, careNexus: 'BHCA-branded self-care recognition',          qualifying: false },
    { id: 'bottle',    name: 'Sankofa Water Bottle',             category: 'Sankofa recognition',     pointCost: 400,  retailValue: 8,  careNexus: 'Hydration recognition item',                  qualifying: false },
  ]

  const CAPPED_PAYERS = ['medicaid', 'medicare', 'dual']
  const PER_ITEM_CAP = 15   // retail value must be UNDER $15
  const ANNUAL_CAP = 75     // aggregate non-qualifying retail must stay UNDER $75/yr

  const WEEKLY_PRIZE = { name: 'Connected BP Cuff', detail: 'Omron upper-arm monitor — ships to winners', emoji: '🩺' }

  // ── Seed / state ─────────────────────────────────────────────
  const todayDoses = () => ([
    { id: 'd-lisinopril', name: 'Lisinopril', dose: '10mg', time: '8:00 AM', status: 'pending' },
    { id: 'd-metformin',  name: 'Metformin',  dose: '500mg', time: '8:00 AM', status: 'pending' },
    { id: 'd-vitd',       name: 'Vitamin D3', dose: '2000 IU', time: '9:00 AM', status: 'pending' },
  ])

  function seedHistory() {
    // 29 prior days. Most recent 12 fully logged (gives a 12-day streak with today
    // still pending); older days at 2/3 so 30-day adherence lands ~78%.
    const days = []
    const now = Date.now()
    for (let i = 29; i >= 1; i--) {
      const full = i <= 12
      days.push({
        date: new Date(now - i * 86400000).toISOString().slice(0, 10),
        scheduled: DAILY_SCHEDULED,
        logged: full ? DAILY_SCHEDULED : 2,
      })
    }
    return days
  }

  function seedLedger() {
    const now = Date.now()
    return [
      { id: 'l1', ts: now - 6 * 86400000, type: 'earn', reason: 'Kept appointment — Dr. Osei', points: POINTS.appointment },
      { id: 'l2', ts: now - 5 * 86400000, type: 'earn', reason: 'Completed education module', points: POINTS.education },
      { id: 'l3', ts: now - 4 * 86400000, type: 'earn', reason: '7-day streak bonus', points: POINTS.streak7 },
      { id: 'l4', ts: now - 3 * 86400000, type: 'earn', reason: 'Logged biometric — blood pressure', points: POINTS.biometric },
      { id: 'l5', ts: now - 2 * 86400000, type: 'redeem', reason: 'Redeemed: Transit Card — 10 trips', points: -500 },
    ]
  }

  function seedRedemptions() {
    const now = Date.now()
    return [
      { id: 'r1', ts: now - 2 * 86400000, user: 'Marcus Johnson', item: 'Transit Card — 10 trips', value: 25, payerType: 'medicaid', capApplied: 'Exempt — access-to-care (rideshare/transit)' },
    ]
  }

  function seed() {
    return {
      payerType: 'medicaid',     // default to a capped payer to demonstrate compliance
      user: 'Marcus Johnson',
      balance: 1240,             // banked / available points
      ledger: seedLedger(),
      redemptions: seedRedemptions(),
      history: seedHistory(),
      today: { date: new Date().toISOString().slice(0, 10), doses: todayDoses() },
      pendingWeekly: WEEKLY_BONUS,
      pendingMonthly: MONTHLY_BONUS,
      eduCompleted: 2,
      screeningsDone: 3,
      apptsKept: 4,
      lastLoss: null,            // transient {points, ts}
    }
  }

  let state = load()
  const subs = new Set()

  function load() {
    try { const s = JSON.parse(localStorage.getItem(KEY)); if (s && s.today) return migrate(s) } catch (e) {}
    return seed()
  }
  function migrate(s) {
    // reset today's doses if the stored day is stale
    const today = new Date().toISOString().slice(0, 10)
    if (s.today.date !== today) s.today = { date: today, doses: todayDoses() }
    return s
  }
  function save() {
    try { localStorage.setItem(KEY, JSON.stringify(state)) } catch (e) {}
    subs.forEach(f => { try { f() } catch (e) {} })
  }

  // ── Derived metrics ──────────────────────────────────────────
  function adherence30() {
    const t = state.today
    const tLogged = t.doses.filter(d => d.status === 'logged').length
    let logged = tLogged, scheduled = DAILY_SCHEDULED
    // include the 29 most recent prior days
    state.history.slice(-29).forEach(d => { logged += d.logged; scheduled += d.scheduled })
    return scheduled ? Math.round((logged / scheduled) * 100) : 0
  }

  function weekAdherence() {
    const t = state.today
    const tLogged = t.doses.filter(d => d.status === 'logged').length
    let logged = tLogged, scheduled = DAILY_SCHEDULED
    state.history.slice(-6).forEach(d => { logged += d.logged; scheduled += d.scheduled })
    return scheduled ? logged / scheduled : 0
  }

  function streak() {
    const t = state.today
    const acted = t.doses.filter(d => d.status !== 'pending')
    const missedToday = t.doses.some(d => d.status === 'missed')
    const allLoggedToday = t.doses.every(d => d.status === 'logged')
    if (missedToday) return 0
    let count = allLoggedToday ? 1 : 0
    // walk history backward; full days extend the streak
    const hist = state.history.slice()
    for (let i = hist.length - 1; i >= 0; i--) {
      if (hist[i].logged >= hist[i].scheduled) count++
      else break
    }
    return count
  }

  function pendingTotal() { return state.pendingWeekly + state.pendingMonthly }

  function compositeScore() {
    const adh = adherence30()                                  // 0-100, weighted highest
    const streakScore = Math.min(100, (streak() / 30) * 100)
    const careScore = Math.min(100, ((state.screeningsDone + state.apptsKept) / 8) * 100)
    const eduScore = Math.min(100, (state.eduCompleted / 3) * 100)
    return Math.round(0.60 * adh + 0.10 * streakScore + 0.20 * careScore + 0.10 * eduScore)
  }

  function tierFor(score) {
    if (score >= 90) return { name: 'Champion', color: '#D4A017', emoji: '🏆' }
    if (score >= 75) return { name: 'Advocate', color: '#16A34A', emoji: '🌟' }
    if (score >= 60) return { name: 'Active',   color: '#2563EB', emoji: '💪' }
    return { name: 'Starter', color: '#6B6560', emoji: '🌱' }
  }

  function annualNonQualifying() {
    const yr = new Date().getFullYear()
    return state.redemptions
      .filter(r => new Date(r.ts).getFullYear() === yr && r.qualifying === false)
      .reduce((s, r) => s + (r.value || 0), 0)
  }

  function compute() {
    const score = compositeScore()
    const wk = weekAdherence()
    return {
      adherence: adherence30(),
      streak: streak(),
      pending: pendingTotal(),
      pendingWeekly: state.pendingWeekly,
      pendingMonthly: state.pendingMonthly,
      balance: state.balance,
      dollars: dollars(state.balance),
      score,
      tier: tierFor(score),
      rewardsCount: state.redemptions.length,
      weekAdherence: Math.round(wk * 100),
      drawEntered: wk >= WEEKLY_THRESHOLD,
      weeklyThreshold: Math.round(WEEKLY_THRESHOLD * 100),
      payerType: state.payerType,
      lastLoss: state.lastLoss,
    }
  }

  // ── Actions ──────────────────────────────────────────────────
  function logDose(doseId) {
    const d = state.today.doses.find(x => x.id === doseId)
    if (!d || d.status === 'logged') return
    d.status = 'logged'
    state.balance += POINTS.dose
    state.ledger.push({ id: 'l' + Date.now(), ts: Date.now(), type: 'earn', reason: `Logged medication — ${d.name}`, points: POINTS.dose })
    // full-day bonus credited once
    if (state.today.doses.every(x => x.status === 'logged') && !state.today.dayBonus) {
      state.today.dayBonus = true
    }
    save()
  }

  function missDose(doseId) {
    const d = state.today.doses.find(x => x.id === doseId)
    if (!d || d.status === 'missed') return
    d.status = 'missed'
    // deduct from pending pool only — never from banked/earned balance
    let penalty = MISS_PENALTY
    const fromWeekly = Math.min(state.pendingWeekly, penalty)
    state.pendingWeekly -= fromWeekly
    penalty -= fromWeekly
    let fromMonthly = 0
    if (penalty > 0) {
      fromMonthly = Math.min(state.pendingMonthly, penalty)
      state.pendingMonthly -= fromMonthly
      penalty -= fromMonthly
    }
    const lost = fromWeekly + fromMonthly
    d.pen = { w: fromWeekly, m: fromMonthly }   // remember split so undo can restore it
    state.ledger.push({ id: 'l' + Date.now(), ts: Date.now(), type: 'pending_loss', reason: `Missed dose — ${d.name}`, points: -lost })
    state.lastLoss = { points: lost, ts: Date.now() }
    save()
  }

  // Undo a logged/missed dose back to "pending" and reverse its points effect.
  function undoDose(doseId) {
    const d = state.today.doses.find(x => x.id === doseId)
    if (!d || d.status === 'pending') return
    if (d.status === 'logged') {
      state.balance = Math.max(0, state.balance - POINTS.dose)
      state.ledger.push({ id: 'l' + Date.now(), ts: Date.now(), type: 'adjust', reason: `Undo — logged ${d.name} in error`, points: -POINTS.dose })
      state.today.dayBonus = false
    } else if (d.status === 'missed') {
      const pen = d.pen || { w: MISS_PENALTY, m: 0 }
      state.pendingWeekly += pen.w
      state.pendingMonthly += pen.m
      const restored = pen.w + pen.m
      state.ledger.push({ id: 'l' + Date.now(), ts: Date.now(), type: 'adjust', reason: `Undo — missed ${d.name} in error`, points: restored })
      if (state.lastLoss) state.lastLoss = null
      delete d.pen
    }
    d.status = 'pending'
    save()
  }

  function clearLoss() { state.lastLoss = null; save() }

  function resetToday() {
    state.today.doses = todayDoses()
    state.today.dayBonus = false
    save()
  }

  function setPayer(type) { state.payerType = type; save() }

  // ── Redemption with payer-type cap enforcement ───────────────
  function checkRedeem(itemId) {
    const item = CATALOG.find(i => i.id === itemId)
    if (!item) return { ok: false, reason: 'Item not found' }
    if (item.pointCost > state.balance) {
      return { ok: false, reason: `Not enough points — need ${item.pointCost.toLocaleString()}, you have ${state.balance.toLocaleString()}.`, item }
    }
    const capped = CAPPED_PAYERS.includes(state.payerType)
    let capApplied = 'No cap (commercial / uninsured)'
    if (capped) {
      if (item.qualifying) {
        capApplied = 'Exempt — qualifying ' + (item.category.includes('device') ? 'health-device' : 'access-to-care') + ' reward'
      } else {
        if (item.retailValue >= PER_ITEM_CAP) {
          return { ok: false, item, capApplied, reason: `${state.payerType.toUpperCase()} compliance: non-health rewards must be under $${PER_ITEM_CAP}. "${item.name}" is $${item.retailValue}. Choose a connected device or access-to-care reward instead.` }
        }
        const agg = annualNonQualifying()
        if (agg + item.retailValue >= ANNUAL_CAP) {
          return { ok: false, item, capApplied, reason: `${state.payerType.toUpperCase()} annual cap: non-health rewards are limited to under $${ANNUAL_CAP}/yr ($${agg} already used this year).` }
        }
        capApplied = `Capped: <$${PER_ITEM_CAP}/item, <$${ANNUAL_CAP}/yr (used $${agg + item.retailValue} of $${ANNUAL_CAP})`
      }
    }
    return { ok: true, item, capApplied }
  }

  function redeem(itemId) {
    const chk = checkRedeem(itemId)
    if (!chk.ok) return chk
    const item = chk.item
    state.balance -= item.pointCost
    state.ledger.push({ id: 'l' + Date.now(), ts: Date.now(), type: 'redeem', reason: `Redeemed: ${item.name}`, points: -item.pointCost })
    state.redemptions.push({
      id: 'r' + Date.now(), ts: Date.now(), user: state.user,
      item: item.name, value: item.retailValue, payerType: state.payerType,
      qualifying: item.qualifying, capApplied: chk.capApplied,
    })
    save()
    return { ok: true, item, capApplied: chk.capApplied }
  }

  function subscribe(fn) { subs.add(fn); return () => subs.delete(fn) }

  window.BHA_RW = {
    POINTS, CATALOG, WEEKLY_PRIZE, CAPPED_PAYERS, PER_ITEM_CAP, ANNUAL_CAP, WEEKLY_THRESHOLD,
    get: () => state, compute, dollars,
    logDose, missDose, clearLoss, resetToday, setPayer, redeem, checkRedeem, subscribe, undoDose,
  }
})();
