<script setup>
import sharedLang from '~/assets/langs/shared.js'

const orderCompare = useOrderCompare()
const orderCompareParam = useOrderCompareParam()

const language = useCookie('language')
const lang = computed(() => language.value || 'en-US')
const t = computed(() => sharedLang(lang.value))

// ---------- data ----------
const candidates = ref([])            // last 10 completed orders of the partner
const selectedBaselineId = ref(null)
const currentLines = ref([])
const baselineLines = ref([])
const loading = ref(false)            // initial load (candidates + current lines)
const loadingBaseline = ref(false)    // baseline switch only
const loadError = ref(false)
const loadedForOrderId = ref(null)
const loadedGrandTotal = ref(null)
const linesCache = new Map()          // orderId -> raw line records (deliberately non-reactive)

const selectedCandidate = computed(() =>
  candidates.value.find(c => c.id === selectedBaselineId.value) || null)

const fetchLines = async (orderId) => {
  if (linesCache.has(orderId)) return linesCache.get(orderId)
  const res = await $fetch(`/api/orders/${orderId}/order-line`, {
    headers: useRequestHeaders(['cookie'])
  })
  const records = res?.records || []
  linesCache.set(orderId, records)
  return records
}

const load = async () => {
  const p = orderCompareParam.value
  if (!p?.orderId || !p?.partnerId) return
  loading.value = true
  loadError.value = false
  try {
    const [cand, curLines] = await Promise.all([
      $fetch(`/api/orders/${p.orderId}/compare-candidates`, {
        headers: useRequestHeaders(['cookie']),
        query: { partnerId: p.partnerId }
      }),
      fetchLines(p.orderId)
    ])
    candidates.value = cand?.records || []
    currentLines.value = curLines
    if (candidates.value.length) {
      const firstId = candidates.value[0].id
      if (selectedBaselineId.value === firstId) {
        baselineLines.value = await fetchLines(firstId)
      } else {
        selectedBaselineId.value = firstId // watcher loads the lines
      }
    } else {
      selectedBaselineId.value = null
      baselineLines.value = []
    }
    loadedForOrderId.value = p.orderId
    loadedGrandTotal.value = p.grandTotal
  } catch (err) {
    console.error('Order compare load failed', err)
    loadError.value = true
  } finally {
    loading.value = false
  }
}

watch(orderCompare, async (open) => {
  if (!open) return
  const p = orderCompareParam.value
  if (loadedForOrderId.value !== p?.orderId || loadedGrandTotal.value !== p?.grandTotal) {
    // new order or the order was edited since last open — refetch everything
    linesCache.clear()
    candidates.value = []
    await load()
  }
  nextTick(() => startAnimations())
})

watch(selectedBaselineId, async (id, old) => {
  if (!id || id === old) return
  loadingBaseline.value = true
  try {
    baselineLines.value = await fetchLines(id)
  } catch (err) {
    console.error('Order compare baseline load failed', err)
  } finally {
    loadingBaseline.value = false
    nextTick(() => startAnimations())
  }
})

const close = () => { orderCompare.value = false }
const onKey = (e) => { if (e.key === 'Escape' && orderCompare.value) close() }
onMounted(() => window.addEventListener('keydown', onKey))
onUnmounted(() => window.removeEventListener('keydown', onKey))

// ---------- threshold (per-position alert %, user-adjustable, persisted) ----------
const THRESHOLD_KEY = 'logship_order_compare_threshold'
const thresholdPct = ref(10)
onMounted(() => {
  const stored = Number(localStorage.getItem(THRESHOLD_KEY))
  if (stored >= 1 && stored <= 50) thresholdPct.value = stored
})
watch(thresholdPct, (v) => {
  try { localStorage.setItem(THRESHOLD_KEY, String(v)) } catch (e) { /* private mode */ }
})

// ---------- comparison ----------
const normKey = (line) => {
  const pid = line?.M_Product_ID?.id
  if (pid) return 'p:' + pid
  const cid = line?.C_Charge_ID?.id // charge lines (shipping, discount, ...)
  if (cid) return 'c:' + cid
  const d = (line?.Description || '').toString().trim().toLowerCase().replace(/\s+/g, ' ')
  return d ? 'd:' + d : 'l:' + line?.id // last resort: never merge blind lines
}

// Aggregate multiple lines of the same product (the fulfillment generator emits
// per-entry 'request' lines sharing a product but with different descriptions).
const aggregate = (lines) => {
  const map = new Map()
  for (const l of (lines || [])) {
    const key = normKey(l)
    const agg = map.get(key) || {
      key,
      name: l?.M_Product_ID?.Name || l?.M_Product_ID?.identifier || l?.C_Charge_ID?.identifier || l?.Description || '—',
      sku: l?.M_Product_ID?.Value || l?.M_Product_ID?.SKU || '',
      qty: 0, amount: 0, lineCount: 0
    }
    agg.qty += Number(l?.QtyOrdered ?? l?.QtyEntered ?? 0)
    agg.amount += Number(l?.LineNetAmt ?? 0)
    agg.lineCount++
    map.set(key, agg)
  }
  return map
}

const severityOf = (pct, amtDelta) => {
  if (pct === null) return Math.abs(amtDelta) < 0.005 ? 'ok' : 'alert'
  const a = Math.abs(pct)
  const T = thresholdPct.value
  return a > T ? 'alert' : a > T / 2 ? 'warn' : 'ok'
}

const comparison = computed(() => {
  const prevMap = aggregate(baselineLines.value)
  const curMap = aggregate(currentLines.value)
  const matched = [], added = [], missing = []

  for (const [key, cur] of curMap) {
    const prev = prevMap.get(key)
    if (prev) {
      const amtDelta = cur.amount - prev.amount
      const qtyDelta = cur.qty - prev.qty
      const pct = prev.amount !== 0 ? (amtDelta / Math.abs(prev.amount)) * 100 : null
      matched.push({
        key, name: cur.name, sku: cur.sku, prev, cur, amtDelta, qtyDelta, pct,
        changed: Math.abs(amtDelta) > 0.005 || Math.abs(qtyDelta) > 0.0001,
        severity: severityOf(pct, amtDelta)
      })
    } else {
      added.push({ key, name: cur.name, sku: cur.sku, prev: null, cur,
        amtDelta: cur.amount, qtyDelta: cur.qty, pct: null, severity: 'alert' })
    }
  }
  for (const [key, prev] of prevMap) {
    if (!curMap.has(key)) {
      missing.push({ key, name: prev.name, sku: prev.sku, prev, cur: null,
        amtDelta: -prev.amount, qtyDelta: -prev.qty, pct: null, severity: 'alert' })
    }
  }

  const changed = matched.filter(r => r.changed)
    .sort((a, b) => Math.abs(b.amtDelta) - Math.abs(a.amtDelta))
  const unchanged = matched.filter(r => !r.changed)
    .sort((a, b) => b.cur.amount - a.cur.amount)
  const maxAbsDelta = Math.max(0.01,
    ...[...changed, ...added, ...missing].map(r => Math.abs(r.amtDelta)))

  const prevTotal = Number(selectedCandidate.value?.grandTotal ?? 0)
  const curTotal = Number(orderCompareParam.value?.grandTotal ?? 0)
  const totalDelta = curTotal - prevTotal
  const totalPct = prevTotal !== 0 ? (totalDelta / Math.abs(prevTotal)) * 100 : null

  return { changed, unchanged, added, missing, maxAbsDelta,
    prevTotal, curTotal, totalDelta, totalPct,
    totalSeverity: severityOf(totalPct, totalDelta) }
})

const overThresholdCount = computed(() =>
  comparison.value.changed.filter(r => r.severity === 'alert').length)

// Flattened row list for the aligned grid, with group header rows
const rows = computed(() => {
  const c = comparison.value
  const out = []
  const push = (label, arr, group) => {
    if (!arr.length) return
    out.push({ type: 'group', key: 'g:' + group, label, count: arr.length, group })
    arr.forEach(r => out.push({ type: 'row', group, ...r }))
  }
  push(t.value['order_compare_changed'] || 'Geändert', c.changed, 'changed')
  push(t.value['order_compare_added'] || 'Nur im aktuellen Auftrag', c.added, 'added')
  push(t.value['order_compare_missing'] || 'Fehlt (nur im Vorauftrag)', c.missing, 'missing')
  push(t.value['order_compare_unchanged'] || 'Unverändert', c.unchanged, 'unchanged')
  return out
})

// ---------- formatting ----------
const currencyCode = computed(() => {
  const c = String(orderCompareParam.value?.currency || 'EUR').toUpperCase()
  return /^[A-Z]{3}$/.test(c) ? c : 'EUR'
})
const eur = (v) => new Intl.NumberFormat('de-DE', { style: 'currency', currency: currencyCode.value }).format(Number(v) || 0)
const num = (v) => Number(v || 0).toLocaleString('de-DE', { maximumFractionDigits: 2 })
const signedEur = (v) => (v > 0 ? '+' : '') + eur(v)
const pctFmt = (p) => p === null ? '—' : (p > 0 ? '+' : '') + p.toLocaleString('de-DE', { maximumFractionDigits: 1 }) + ' %'
const dateFmt = (d) => {
  const dt = new Date(d)
  return isNaN(dt) ? (d || '') : dt.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })
}
const pctLabel = (r) => {
  if (!r.prev) return t.value['order_compare_added_short'] || 'neu'
  if (!r.cur) return t.value['order_compare_missing_short'] || 'fehlend'
  return r.pct === null ? '—' : pctFmt(r.pct)
}

// ---------- gauge + count-up animation (rAF, honors prefers-reduced-motion) ----------
const gaugeVal = ref(0)          // 0..1 arc progress
const animTotalDelta = ref(0)    // count-up currency value
const animTotalPct = ref(0)      // count-up % value
let rafId = null

const startAnimations = () => {
  if (rafId) cancelAnimationFrame(rafId)
  const c = comparison.value
  const targetPct = c.totalPct === null ? 0 : c.totalPct
  // arc: |deviation| mapped onto 0..1, pegged at 50 % deviation
  const targetArc = Math.min(Math.abs(targetPct) / 50, 1)
  const reduced = window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches
  if (reduced) {
    gaugeVal.value = targetArc
    animTotalDelta.value = c.totalDelta
    animTotalPct.value = targetPct
    return
  }
  const t0 = performance.now(), dur = 900
  const ease = x => 1 - Math.pow(1 - x, 3) // easeOutCubic
  const step = (now) => {
    const k = ease(Math.min((now - t0) / dur, 1))
    gaugeVal.value = targetArc * k
    animTotalDelta.value = c.totalDelta * k
    animTotalPct.value = targetPct * k
    if (k < 1) rafId = requestAnimationFrame(step)
    else rafId = null
  }
  rafId = requestAnimationFrame(step)
}
onUnmounted(() => { if (rafId) cancelAnimationFrame(rafId) })

// diverging bar geometry (center column): positive Δ grows right, negative left
const barStyle = (r) => {
  const w = Math.min(Math.abs(r.amtDelta) / comparison.value.maxAbsDelta, 1) * 50
  return r.amtDelta >= 0
    ? { left: '50%', width: w + '%' }
    : { right: '50%', width: w + '%' }
}
</script>

<template>
  <div v-if="orderCompare" class="ocmp-overlay" @click.self="close">
    <div class="ocmp-container" role="dialog" aria-modal="true" @click.stop>

      <!-- Header -->
      <header class="ocmp-header">
        <div class="ocmp-header__title">
          <span class="ocmp-header__icon"><span class="ti ti-git-compare"></span></span>
          <div>
            <h3>{{ t['order_compare_title'] || 'Auftragsvergleich' }}</h3>
            <p class="ocmp-header__sub">{{ orderCompareParam.partnerName }}</p>
          </div>
        </div>
        <div v-if="candidates.length" class="ocmp-header__baseline">
          <label>{{ t['order_compare_baseline'] || 'Vergleichsauftrag' }}</label>
          <div class="ocmp-select-wrap">
            <select v-model="selectedBaselineId" class="ocmp-select">
              <option v-for="c in candidates" :key="c.id" :value="c.id">
                {{ c.documentNo }} · {{ dateFmt(c.dateOrdered) }} · {{ eur(c.grandTotal) }}
              </option>
            </select>
            <span v-if="loadingBaseline" class="ti ti-loader-2 ocmp-spin"></span>
          </div>
        </div>
        <button type="button" class="ocmp-close" :title="t['close'] || 'Schließen'" @click="close">
          <span class="ti ti-x"></span>
        </button>
      </header>

      <!-- Loading skeleton -->
      <div v-if="loading" class="ocmp-body ocmp-body--loading">
        <div class="ocmp-skeleton ocmp-skeleton--hero"></div>
        <div v-for="i in 6" :key="i" class="ocmp-skeleton ocmp-skeleton--row"></div>
      </div>

      <!-- Load error -->
      <div v-else-if="loadError" class="ocmp-body ocmp-empty">
        <span class="ti ti-alert-triangle"></span>
        <p>{{ t['order_compare_load_error'] || 'Vergleich konnte nicht geladen werden.' }}</p>
        <button type="button" class="button is-primary" @click="load">
          {{ t['order_compare_retry'] || 'Erneut versuchen' }}
        </button>
      </div>

      <!-- No completed previous order -->
      <div v-else-if="!candidates.length" class="ocmp-body ocmp-empty">
        <span class="ti ti-file-off"></span>
        <p>{{ t['order_compare_no_previous'] || 'Kein abgeschlossener Vorauftrag für diesen Partner gefunden.' }}</p>
      </div>

      <template v-else>
        <!-- Hero: gauge + stats + chips + threshold slider -->
        <section class="ocmp-hero">
          <div class="ocmp-gauge" :data-severity="comparison.totalSeverity" :style="{ '--arc': gaugeVal }">
            <div class="ocmp-gauge__ring"></div>
            <div class="ocmp-gauge__center">
              <strong>{{ comparison.totalPct === null ? '—' : pctFmt(animTotalPct) }}</strong>
              <span>{{ t['order_compare_deviation'] || 'Abweichung' }}</span>
            </div>
          </div>

          <div class="ocmp-hero__main">
            <div class="ocmp-stats">
              <div class="ocmp-stat">
                <span class="ocmp-stat__label">{{ t['order_compare_current'] || 'Aktueller Auftrag' }}</span>
                <span class="ocmp-stat__value">{{ eur(comparison.curTotal) }}</span>
                <span class="ocmp-stat__meta">{{ orderCompareParam.documentNo }} · {{ dateFmt(orderCompareParam.dateOrdered) }}</span>
              </div>
              <div class="ocmp-stat ocmp-stat--delta" :data-severity="comparison.totalSeverity">
                <span class="ocmp-stat__label">Δ {{ t['grand_total'] || 'Gesamtsumme' }}</span>
                <span class="ocmp-stat__value">{{ signedEur(animTotalDelta) }}</span>
                <span class="ocmp-stat__meta">{{ pctFmt(comparison.totalPct) }}</span>
              </div>
              <div class="ocmp-stat">
                <span class="ocmp-stat__label">{{ t['order_compare_previous'] || 'Vorauftrag' }}</span>
                <span class="ocmp-stat__value">{{ eur(comparison.prevTotal) }}</span>
                <span class="ocmp-stat__meta">{{ selectedCandidate?.documentNo }} · {{ dateFmt(selectedCandidate?.dateOrdered) }}</span>
              </div>
            </div>

            <div class="ocmp-hero__foot">
              <div class="ocmp-chips">
                <span class="ocmp-chip ocmp-chip--changed"><span class="ti ti-arrows-diff"></span> {{ comparison.changed.length }} {{ t['order_compare_changed'] || 'geändert' }}</span>
                <span class="ocmp-chip ocmp-chip--added"><span class="ti ti-plus"></span> {{ comparison.added.length }} {{ t['order_compare_added_short'] || 'neu' }}</span>
                <span class="ocmp-chip ocmp-chip--missing"><span class="ti ti-minus"></span> {{ comparison.missing.length }} {{ t['order_compare_missing_short'] || 'fehlend' }}</span>
                <span class="ocmp-chip ocmp-chip--unchanged"><span class="ti ti-equal"></span> {{ comparison.unchanged.length }} {{ t['order_compare_unchanged'] || 'unverändert' }}</span>
              </div>
              <div class="ocmp-threshold">
                <label>{{ t['order_compare_threshold'] || 'Schwellwert' }}</label>
                <input v-model.number="thresholdPct" type="range" min="1" max="50" step="1" />
                <span class="ocmp-threshold__value">{{ thresholdPct }} %</span>
                <span v-if="overThresholdCount" class="ocmp-threshold__count">
                  <span class="ti ti-alert-triangle"></span>
                  {{ overThresholdCount }} {{ t['order_compare_over_threshold'] || 'über Schwellwert' }}
                </span>
              </div>
            </div>
          </div>
        </section>

        <!-- Aligned side-by-side grid: current (left) | Δ | previous (right) -->
        <div class="ocmp-body" :class="{ 'ocmp-body--switching': loadingBaseline }">
          <div class="ocmp-grid">
            <div class="ocmp-grid__head ocmp-grid__head--left">
              {{ t['order_compare_current'] || 'Aktueller Auftrag' }} · {{ orderCompareParam.documentNo }}
            </div>
            <div class="ocmp-grid__head ocmp-grid__head--mid">Δ</div>
            <div class="ocmp-grid__head ocmp-grid__head--right">
              {{ t['order_compare_previous'] || 'Vorauftrag' }} · {{ selectedCandidate?.documentNo }}
            </div>

            <template v-for="(r, i) in rows" :key="r.key">
              <div v-if="r.type === 'group'" class="ocmp-group" :data-group="r.group">
                {{ r.label }} <span class="ocmp-group__count">{{ r.count }}</span>
              </div>
              <template v-else>
                <!-- left / current -->
                <div
                  class="ocmp-cell ocmp-cell--left"
                  :data-group="r.group"
                  :class="{ 'ocmp-cell--ghost': !r.cur }"
                  :style="{ '--i': Math.min(i, 24) }"
                >
                  <template v-if="r.cur">
                    <span class="ocmp-cell__name">{{ r.name }}</span>
                    <span v-if="r.sku" class="ocmp-cell__sku">{{ r.sku }}</span>
                    <span class="ocmp-cell__nums">{{ num(r.cur.qty) }} × · {{ eur(r.cur.amount) }}</span>
                  </template>
                  <span v-else class="ocmp-cell__ghostlabel">—</span>
                </div>
                <!-- center delta -->
                <div class="ocmp-cell ocmp-cell--mid" :data-severity="r.severity" :style="{ '--i': Math.min(i, 24) }">
                  <div class="ocmp-bar"><span class="ocmp-bar__fill" :style="barStyle(r)"></span></div>
                  <span class="ocmp-delta">{{ signedEur(r.amtDelta) }}</span>
                  <span class="ocmp-pct">{{ pctLabel(r) }}</span>
                  <span v-if="r.qtyDelta" class="ocmp-qtydelta">{{ r.qtyDelta > 0 ? '+' : '' }}{{ num(r.qtyDelta) }} Stk</span>
                </div>
                <!-- right / previous -->
                <div
                  class="ocmp-cell ocmp-cell--right"
                  :data-group="r.group"
                  :class="{ 'ocmp-cell--ghost': !r.prev }"
                  :style="{ '--i': Math.min(i, 24) }"
                >
                  <template v-if="r.prev">
                    <span class="ocmp-cell__name">{{ r.name }}</span>
                    <span v-if="r.sku" class="ocmp-cell__sku">{{ r.sku }}</span>
                    <span class="ocmp-cell__nums">{{ num(r.prev.qty) }} × · {{ eur(r.prev.amount) }}</span>
                  </template>
                  <span v-else class="ocmp-cell__ghostlabel">—</span>
                </div>
              </template>
            </template>
          </div>
        </div>

        <footer class="ocmp-footer">
          <div class="ocmp-legend">
            <span><i class="ocmp-dot" data-severity="ok"></i> ≤ {{ Math.round(thresholdPct / 2) }} %</span>
            <span><i class="ocmp-dot" data-severity="warn"></i> ≤ {{ thresholdPct }} %</span>
            <span><i class="ocmp-dot" data-severity="alert"></i> &gt; {{ thresholdPct }} % / {{ t['order_compare_added_short'] || 'neu' }} / {{ t['order_compare_missing_short'] || 'fehlend' }}</span>
          </div>
          <button type="button" class="button is-primary" @click="close">
            {{ t['close'] || 'Schließen' }}
          </button>
        </footer>
      </template>
    </div>
  </div>
</template>

<style scoped>
/* ---------- overlay + container ---------- */
.ocmp-overlay {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(15, 18, 28, 0.5);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.ocmp-container {
  width: 100%;
  max-width: min(1400px, 95vw);
  max-height: calc(100vh - 40px);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--theme-bg-primary, #fff);
  color: var(--theme-text-primary, #1b1d1d);
  border: 1px solid var(--theme-border, #e5e7eb);
  border-radius: 16px;
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.35);
  animation: ocmpPop 180ms cubic-bezier(0.2, 0.9, 0.3, 1.2);
  /* severity + accent tokens */
  --ocmp-ok: #16a34a;
  --ocmp-ok-soft: rgba(22, 163, 74, 0.14);
  --ocmp-warn: #d97706;
  --ocmp-warn-soft: rgba(217, 119, 6, 0.16);
  --ocmp-alert: #dc2626;
  --ocmp-alert-soft: rgba(220, 38, 38, 0.14);
  --ocmp-add: #2563eb;
  --ocmp-add-soft: rgba(37, 99, 235, 0.12);
}
@keyframes ocmpPop {
  from { transform: translateY(14px) scale(0.97); opacity: 0; }
}

/* ---------- header ---------- */
.ocmp-header {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  padding: 1rem 1.5rem;
  border-bottom: 1px solid var(--theme-border, #e5e7eb);
}
.ocmp-header__title {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  min-width: 0;
  flex: 1;
}
.ocmp-header__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  flex: none;
  border-radius: 12px;
  font-size: 1.3rem;
  color: #fff;
  background: linear-gradient(135deg, #6366f1, #8b5cf6);
  box-shadow: 0 6px 18px rgba(99, 102, 241, 0.35);
}
.ocmp-header__title h3 {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1.2;
}
.ocmp-header__sub {
  margin: 0;
  font-size: 0.82rem;
  color: var(--theme-text-secondary, #6b7280);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ocmp-header__baseline {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ocmp-header__baseline label {
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--theme-text-secondary, #6b7280);
}
.ocmp-select-wrap {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.ocmp-select {
  max-width: 340px;
  padding: 0.4rem 0.6rem;
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
  border: 1px solid var(--theme-border, #d1d5db);
  border-radius: 8px;
  background: var(--theme-bg-secondary, #f8f8f8);
  color: var(--theme-text-primary, #1b1d1d);
  cursor: pointer;
}
.ocmp-spin {
  animation: ocmpSpin 0.8s linear infinite;
  color: var(--theme-text-secondary, #6b7280);
}
@keyframes ocmpSpin { to { transform: rotate(360deg); } }
.ocmp-close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex: none;
  border: none;
  border-radius: 10px;
  background: transparent;
  color: var(--theme-text-secondary, #6b7280);
  font-size: 1.15rem;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.ocmp-close:hover {
  background: var(--theme-bg-tertiary, #f0f0f0);
  color: var(--theme-text-primary, #1b1d1d);
}

/* ---------- hero ---------- */
.ocmp-hero {
  display: flex;
  gap: 1.5rem;
  align-items: center;
  padding: 1.1rem 1.5rem;
  background:
    linear-gradient(120deg, var(--theme-bg-secondary, #f8f8f8), transparent),
    radial-gradient(1200px 200px at 20% 0%, rgba(115, 101, 255, 0.1), transparent);
  border-bottom: 1px solid var(--theme-border, #e5e7eb);
}
.ocmp-hero__main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
}

/* conic ring gauge — arc driven by --arc (0..1) from the rAF loop */
.ocmp-gauge {
  position: relative;
  width: 118px;
  height: 118px;
  flex: none;
}
.ocmp-gauge__ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: conic-gradient(
    from -120deg,
    var(--gcolor, #9ca3af) calc(var(--arc, 0) * 240deg),
    var(--theme-bg-tertiary, #ececec) calc(var(--arc, 0) * 240deg) 240deg,
    transparent 240deg
  );
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 11px), #000 calc(100% - 10px));
          mask: radial-gradient(farthest-side, transparent calc(100% - 11px), #000 calc(100% - 10px));
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.12));
}
.ocmp-gauge[data-severity="ok"] { --gcolor: var(--ocmp-ok); }
.ocmp-gauge[data-severity="warn"] { --gcolor: var(--ocmp-warn); }
.ocmp-gauge[data-severity="alert"] { --gcolor: var(--ocmp-alert); }
.ocmp-gauge__center {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.ocmp-gauge__center strong {
  font-size: 1.15rem;
  line-height: 1.1;
}
.ocmp-gauge__center span {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--theme-text-secondary, #6b7280);
}

/* stat cards */
.ocmp-stats {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 0.75rem;
}
.ocmp-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 0.6rem 0.9rem;
  border-radius: 12px;
  background: var(--theme-bg-primary, #fff);
  border: 1px solid var(--theme-border, #e5e7eb);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
  font-variant-numeric: tabular-nums;
  min-width: 0;
}
.ocmp-stat__label {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--theme-text-secondary, #6b7280);
}
.ocmp-stat__value {
  font-size: 1.25rem;
  font-weight: 800;
  line-height: 1.2;
}
.ocmp-stat__meta {
  font-size: 0.75rem;
  color: var(--theme-text-secondary, #6b7280);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ocmp-stat--delta[data-severity="ok"] { border-color: var(--ocmp-ok); background: var(--ocmp-ok-soft); }
.ocmp-stat--delta[data-severity="ok"] .ocmp-stat__value { color: var(--ocmp-ok); }
.ocmp-stat--delta[data-severity="warn"] { border-color: var(--ocmp-warn); background: var(--ocmp-warn-soft); }
.ocmp-stat--delta[data-severity="warn"] .ocmp-stat__value { color: var(--ocmp-warn); }
.ocmp-stat--delta[data-severity="alert"] { border-color: var(--ocmp-alert); background: var(--ocmp-alert-soft); }
.ocmp-stat--delta[data-severity="alert"] .ocmp-stat__value { color: var(--ocmp-alert); }

.ocmp-hero__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.ocmp-chips {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.ocmp-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.22rem 0.65rem;
  border-radius: 999px;
  font-size: 0.76rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.ocmp-chip--changed { background: var(--ocmp-warn-soft); color: var(--ocmp-warn); }
.ocmp-chip--added { background: var(--ocmp-add-soft); color: var(--ocmp-add); }
.ocmp-chip--missing { background: var(--ocmp-alert-soft); color: var(--ocmp-alert); }
.ocmp-chip--unchanged { background: var(--ocmp-ok-soft); color: var(--ocmp-ok); }

/* threshold slider */
.ocmp-threshold {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.78rem;
}
.ocmp-threshold label {
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 0.7rem;
  color: var(--theme-text-secondary, #6b7280);
}
.ocmp-threshold input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 150px;
  height: 6px;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--ocmp-ok), var(--ocmp-warn) 45%, var(--ocmp-alert));
  outline: none;
  cursor: pointer;
}
.ocmp-threshold input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 17px;
  height: 17px;
  border-radius: 50%;
  background: var(--theme-bg-primary, #fff);
  border: 2px solid var(--theme-text-primary, #374151);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
  cursor: grab;
}
.ocmp-threshold input[type="range"]::-moz-range-thumb {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: var(--theme-bg-primary, #fff);
  border: 2px solid var(--theme-text-primary, #374151);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
  cursor: grab;
}
.ocmp-threshold__value {
  min-width: 38px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.ocmp-threshold__count {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.22rem 0.65rem;
  border-radius: 999px;
  font-weight: 700;
  background: var(--ocmp-alert-soft);
  color: var(--ocmp-alert);
  font-variant-numeric: tabular-nums;
}

/* ---------- body / grid ---------- */
.ocmp-body {
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  flex: 1;
  padding: 0 1.25rem 1.25rem;
}
.ocmp-body--switching {
  opacity: 0.45;
  transition: opacity 0.15s;
  pointer-events: none;
}
.ocmp-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 230px minmax(0, 1fr);
  gap: 6px 10px;
}
.ocmp-grid__head {
  position: sticky;
  top: 0;
  z-index: 2;
  padding: 0.6rem 0.75rem;
  background: var(--theme-bg-primary, #fff);
  font-weight: 700;
  font-size: 0.82rem;
  border-bottom: 2px solid var(--theme-border, #e5e7eb);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ocmp-grid__head--mid { text-align: center; }
.ocmp-grid__head--right { text-align: right; }
.ocmp-group {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.6rem;
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--theme-text-secondary, #6b7280);
}
.ocmp-group__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 999px;
  font-size: 0.72rem;
  background: var(--theme-bg-tertiary, #efefef);
}
.ocmp-group[data-group="changed"] { color: var(--ocmp-warn); }
.ocmp-group[data-group="added"] { color: var(--ocmp-add); }
.ocmp-group[data-group="missing"] { color: var(--ocmp-alert); }

/* row cells with staggered entry */
.ocmp-cell {
  display: flex;
  flex-direction: column;
  gap: 1px;
  border-radius: 10px;
  padding: 0.5rem 0.75rem;
  background: var(--theme-bg-secondary, #f8f8f8);
  border: 1px solid var(--theme-border, #e5e7eb);
  min-width: 0;
  animation: ocmpRow 0.35s both;
  animation-delay: calc(var(--i, 0) * 22ms);
}
@keyframes ocmpRow {
  from { opacity: 0; transform: translateY(6px); }
}
.ocmp-cell__name {
  font-weight: 600;
  font-size: 0.86rem;
  line-height: 1.25;
  overflow-wrap: anywhere;
}
.ocmp-cell__sku {
  font-size: 0.72rem;
  color: var(--theme-text-secondary, #6b7280);
  font-variant-numeric: tabular-nums;
}
.ocmp-cell__nums {
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
}
.ocmp-cell--right { text-align: right; }
.ocmp-cell--ghost {
  background: transparent;
  border-style: dashed;
  opacity: 0.45;
  display: flex;
  align-items: center;
  justify-content: center;
}
.ocmp-cell--left[data-group="added"] { box-shadow: inset 3px 0 0 var(--ocmp-add); }
.ocmp-cell--right[data-group="missing"] { box-shadow: inset -3px 0 0 var(--ocmp-alert); }

/* center delta cell */
.ocmp-cell--mid {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 2px 8px;
  background: transparent;
  border: none;
  font-variant-numeric: tabular-nums;
}
.ocmp-delta {
  font-size: 0.82rem;
  font-weight: 700;
}
.ocmp-pct {
  font-size: 0.74rem;
  font-weight: 800;
  padding: 1px 7px;
  border-radius: 999px;
  justify-self: end;
}
.ocmp-qtydelta {
  grid-column: 1 / -1;
  font-size: 0.72rem;
  color: var(--theme-text-secondary, #6b7280);
}
.ocmp-bar {
  grid-column: 1 / -1;
  position: relative;
  height: 8px;
  border-radius: 4px;
  background: var(--theme-bg-tertiary, #f0f0f0);
  overflow: hidden;
}
.ocmp-bar::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--theme-border, #d1d5db);
}
.ocmp-bar__fill {
  position: absolute;
  top: 0;
  bottom: 0;
  border-radius: 4px;
  animation: ocmpBar 0.5s 0.15s both cubic-bezier(0.2, 0.8, 0.3, 1);
  transform-origin: center;
}
@keyframes ocmpBar {
  from { transform: scaleX(0); }
}
.ocmp-cell--mid[data-severity="ok"] .ocmp-bar__fill { background: var(--ocmp-ok); }
.ocmp-cell--mid[data-severity="ok"] .ocmp-delta { color: var(--ocmp-ok); }
.ocmp-cell--mid[data-severity="ok"] .ocmp-pct { background: var(--ocmp-ok-soft); color: var(--ocmp-ok); }
.ocmp-cell--mid[data-severity="warn"] .ocmp-bar__fill { background: var(--ocmp-warn); }
.ocmp-cell--mid[data-severity="warn"] .ocmp-delta { color: var(--ocmp-warn); }
.ocmp-cell--mid[data-severity="warn"] .ocmp-pct { background: var(--ocmp-warn-soft); color: var(--ocmp-warn); }
.ocmp-cell--mid[data-severity="alert"] .ocmp-bar__fill { background: var(--ocmp-alert); }
.ocmp-cell--mid[data-severity="alert"] .ocmp-delta { color: var(--ocmp-alert); }
.ocmp-cell--mid[data-severity="alert"] .ocmp-pct { background: var(--ocmp-alert-soft); color: var(--ocmp-alert); }

/* ---------- footer ---------- */
.ocmp-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.85rem 1.5rem;
  border-top: 1px solid var(--theme-border, #e5e7eb);
  background: var(--theme-bg-secondary, #fafafa);
}
.ocmp-legend {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
  font-size: 0.76rem;
  color: var(--theme-text-secondary, #6b7280);
  font-variant-numeric: tabular-nums;
}
.ocmp-legend span {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}
.ocmp-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
}
.ocmp-dot[data-severity="ok"] { background: var(--ocmp-ok); }
.ocmp-dot[data-severity="warn"] { background: var(--ocmp-warn); }
.ocmp-dot[data-severity="alert"] { background: var(--ocmp-alert); }

/* ---------- loading / empty ---------- */
.ocmp-body--loading {
  padding-top: 1.25rem;
}
.ocmp-skeleton {
  border-radius: 10px;
  margin-bottom: 0.6rem;
  background: linear-gradient(
    90deg,
    var(--theme-bg-secondary, #f3f3f3) 25%,
    var(--theme-bg-tertiary, #e9e9e9) 37%,
    var(--theme-bg-secondary, #f3f3f3) 63%
  );
  background-size: 400% 100%;
  animation: ocmpShimmer 1.2s infinite linear;
}
.ocmp-skeleton--hero { height: 110px; }
.ocmp-skeleton--row { height: 52px; }
@keyframes ocmpShimmer {
  from { background-position: 100% 0; }
  to { background-position: 0 0; }
}
.ocmp-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  padding: 3rem 1.5rem;
  text-align: center;
  color: var(--theme-text-secondary, #6b7280);
}
.ocmp-empty > .ti {
  font-size: 2.4rem;
  opacity: 0.55;
}
.ocmp-empty p {
  margin: 0;
  max-width: 420px;
}

/* ---------- responsive + motion ---------- */
@media (max-width: 900px) {
  .ocmp-hero { flex-direction: column; align-items: stretch; }
  .ocmp-gauge { margin: 0 auto; }
  .ocmp-stats { grid-template-columns: 1fr; }
  .ocmp-grid { grid-template-columns: minmax(0, 1fr) 140px minmax(0, 1fr); }
  .ocmp-header { flex-wrap: wrap; }
}
@media (prefers-reduced-motion: reduce) {
  .ocmp-container,
  .ocmp-cell,
  .ocmp-bar__fill,
  .ocmp-skeleton,
  .ocmp-spin {
    animation: none !important;
  }
}
</style>
