/**
 * KDS — Kitchen Display System
 * Fullscreen standalone page. No AppLayout wrapper.
 * Designed for tablets/touchscreens running Fully Kiosk or similar.
 */
import { useState, useEffect, useRef, useMemo, useCallback, Fragment } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import {
  Timer, Flame, CheckCheck,
  Undo2, Redo2, BellRing, Volume2, VolumeX, Settings, X,
  Check, Minus, Plus, ArrowRight, Info, User, CookingPot,
  Utensils, ShoppingBag, Bike, ZoomIn, ZoomOut, Clock, Printer, Ban,
} from 'lucide-react'
import client from '../api/client'
import useAuthStore from '../store/authStore'

// ─────────────────────────────────────────────────────────────────────────────
// Unit formatting helpers
// ─────────────────────────────────────────────────────────────────────────────

const UNIT_LABELS = { kg: 'kg', liter: 'L', gram: 'g', ml: 'mL' }

function fmtItemQty(quantity, unitType) {
  const label = UNIT_LABELS[unitType]
  if (!label) return String(quantity)
  if (unitType === 'kg' || unitType === 'liter') {
    return `${Number(quantity).toFixed(1)}${label}`
  }
  return `${quantity}${label}`
}

// ─────────────────────────────────────────────────────────────────────────────
// Constants & design tokens
// ─────────────────────────────────────────────────────────────────────────────

const COL_W_DEFAULT = 272   // base card width, overridden by settings.colW
const COL_GAP       = 14
const CARD_GAP_Y    = 14
const BOARD_PAD     = 14
const BLOCK_GAP     = 4    // vertical gap between item blocks — keep compact
const BODY_PAD_T    = 8    // body top padding
const BODY_PAD_B    = 10   // body bottom padding (a bit extra so last item clears footer)
const BODY_PAD_H    = 10
const MAX_UNITS     = 3

// These are BASE chrome heights at fontScale=1. Scale them by fontScale
// when computing cardChrome() so layout is accurate at any zoom level.
const CARD_HEAD_H_BASE = 46
const CARD_SUB_H_BASE  = 28
const CARD_FOOT_H_BASE = 32
// Note block: spacer (6) + icon line (≈20) + bottom pad (8) + border (1)
const CARD_NOTE_H_BASE = 35

const THR_DEFAULT   = { green: 5, amber: 10, flash: 15 }
const STORAGE_KEY   = 'xenia_kds_settings_v2'

const FONT_SCALE_MIN  = 0.6
const FONT_SCALE_MAX  = 2.0
const FONT_SCALE_STEP = 0.1

const COLORS = {
  bg:           '#1d232b',
  rail:         '#161b21',
  railLine:     '#2b333d',
  topbar:       '#20272f',
  tab:          '#2b333d',
  tabInk:       '#aab4c0',
  tabActiveBg:  '#f3f6f9',
  tabActiveInk: '#1d232b',
  card:         '#ffffff',
  cardSub:      '#2c333c',
  cardSubInk:   '#c5cdd7',
  cardFoot:     '#eef1f5',
  cardFootInk:  '#41474f',
  ink:          '#1f242b',
  ink2:         '#5b626c',
  qtyBg:        '#f0f2f5',
  qtyInk:       '#2b3038',
  line:         '#e8ebef',
  doneInk:      '#aeb4bc',
  green:        '#34b56a',
  greenD:       '#1e8048',   // much darker subheader green
  amber:        '#f0a52a',
  amberD:       '#b87310',   // much darker subheader amber
  red:          '#e8513f',
  redD:         '#a02d1e',   // much darker subheader red
  future:       '#6dd08a',   // side-rail badge colour
  futureD:      '#3ea85e',
  futureHead:   '#4a5568',   // grey card header for future orders
  futureSubBg:  '#374151',   // grey subheader
  inactive:     '#3d4757',   // done card header (user-configurable)
  inactiveD:    '#2a3140',   // done card subheader (auto-derived darker)
  accent:       '#2f7ff0',
  railW:        104,
  topbarH:      54,
  // Category glow colors — used when multiple filters are active
  glowFuture:   '#6dd08a',
  glowPending:  '#f0a52a',
  glowPreparing:'#2f7ff0',
  glowDone:     '#e7ecf2',
}

// ─────────────────────────────────────────────────────────────────────────────
// Chime
// ─────────────────────────────────────────────────────────────────────────────

let _audio = null
function chime() {
  try {
    _audio = _audio || new (window.AudioContext || window.webkitAudioContext)()
    if (_audio.state === 'suspended') _audio.resume()
    const t = _audio.currentTime
    ;[880, 1320].forEach((f, i) => {
      const o = _audio.createOscillator(), g = _audio.createGain()
      o.type = 'sine'; o.frequency.value = f
      o.connect(g); g.connect(_audio.destination)
      const s = t + i * 0.13
      g.gain.setValueAtTime(0.0001, s)
      g.gain.exponentialRampToValueAtTime(0.28, s + 0.02)
      g.gain.exponentialRampToValueAtTime(0.0001, s + 0.26)
      o.start(s); o.stop(s + 0.28)
    })
  } catch { /* ignore */ }
}

// ─────────────────────────────────────────────────────────────────────────────
// Time helpers
// Fix #1: backend returns UTC ISO strings without 'Z' suffix from SQLite,
// so new Date() may treat them as local. We append 'Z' to force UTC parse.
// ─────────────────────────────────────────────────────────────────────────────

function parseUTC(isoStr) {
  if (!isoStr) return null
  // If already has tz info (Z or +xx:xx), use as-is; otherwise treat as UTC
  return new Date(/[Z+]/.test(isoStr) ? isoStr : isoStr + 'Z')
}

function minutesSince(isoStr, now) {
  const d = parseUTC(isoStr)
  if (!d) return 0
  return Math.floor((now - d.getTime()) / 60_000)
}

function fmtDuration(absM) {
  if (absM >= 60) return `${Math.floor(absM / 60)}h ${absM % 60}m`
  return `${absM}m`
}

function fmtAgo(absM) {
  if (absM >= 60) return `${Math.floor(absM / 60)}h ${absM % 60}m ago`
  return `${absM}m ago`
}

function ageState(order, now, thr) {
  // Done orders: show elapsed time since completion (closed_at or fallback to opened_at)
  if (order.kds_status === 'done') {
    const ref = order.closed_at || order.opened_at
    const m = Math.max(0, minutesSince(ref, now))
    return { label: fmtAgo(m), age: 'done', flash: false }
  }
  const m = minutesSince(order.opened_at, now)
  if (m < 0) {
    // future: show countdown (time remaining until scheduled)
    return { label: fmtDuration(Math.abs(m)), age: 'future', flash: false }
  }
  const label = m === 0 ? 'new' : fmtDuration(m)
  if (m >= thr.flash) return { label, age: 'red', flash: true }
  if (m >= thr.amber) return { label, age: 'red', flash: false }
  if (m >= thr.green) return { label, age: 'amber', flash: false }
  return { label, age: 'green', flash: false }
}

// ─────────────────────────────────────────────────────────────────────────────
// Layout engine
//
// THE RULE: no vertical scrolling. Every card must fit within maxH (the
// available board height). Cards widen into extra columns until they fit.
// If even MAX_UNITS columns aren't enough, the last column overflows — that
// is the only acceptable compromise and only happens with extremely tall items.
//
// Cards are absolutely positioned. Their CSS height is set explicitly from
// the layout so the board inner div has a known height (no auto-height surprises).
// ─────────────────────────────────────────────────────────────────────────────

function colHeight(col, blocks, gap) {
  if (!col.length) return 0
  let h = 0
  for (const idx of col) h += blocks[idx]
  return h + (col.length - 1) * gap
}

// Distribute items across columns, targeting bodyH per column.
function distribute(blocks, gap, bodyH) {
  if (!blocks.length) return [[]]
  const cols = []
  let cur = [], curH = 0
  for (let i = 0; i < blocks.length; i++) {
    const h = blocks[i]
    const tentative = cur.length ? curH + gap + h : h
    if (cur.length && tentative > bodyH) {
      cols.push(cur); cur = [i]; curH = h
    } else {
      cur.push(i); curH = tentative
    }
  }
  if (cur.length) cols.push(cur)
  return cols
}

// Size a card so it fits within maxH. Increases column count until it fits.
// KEY FIX: each attempt divides availBodyH by the number of columns being tried,
// so more columns = shorter per-column target = more splits = shorter card.
function sizeCard({ blocks, blockGap, chrome, maxH, base, maxUnits = MAX_UNITS }) {
  const availBodyH = Math.max(40, maxH - chrome)

  for (let units = 1; units <= maxUnits; units++) {
    // Divide the body height equally among `units` columns.
    // This is the per-column height budget that drives splitting.
    const perColH = availBodyH / units

    const cols = distribute(blocks, blockGap, perColH)

    // Fold any overflow columns into the last slot
    let finalCols = cols
    if (cols.length > units) {
      const head = cols.slice(0, units - 1)
      const tailIdx = []
      for (let i = units - 1; i < cols.length; i++) tailIdx.push(...cols[i])
      finalCols = [...head, tailIdx]
    }

    const actualUnits = finalCols.length
    let maxColH = 0
    for (const c of finalCols) maxColH = Math.max(maxColH, colHeight(c, blocks, blockGap))
    const cardH = chrome + maxColH
    const cardW = actualUnits * base + (actualUnits - 1) * COL_GAP

    if (cardH <= maxH + 0.5) {
      return { units: actualUnits, cols: finalCols, cardW, cardH }
    }
  }

  // Absolute fallback: maxUnits wide, cap height to maxH (only for truly extreme content)
  const perColH = availBodyH / maxUnits
  const cols = distribute(blocks, blockGap, perColH)
  let finalCols = cols
  if (cols.length > maxUnits) {
    const head = cols.slice(0, maxUnits - 1)
    const tailIdx = []
    for (let i = maxUnits - 1; i < cols.length; i++) tailIdx.push(...cols[i])
    finalCols = [...head, tailIdx]
  }
  let maxColH = 0
  for (const c of finalCols) maxColH = Math.max(maxColH, colHeight(c, blocks, blockGap))
  return {
    units: maxUnits,
    cols: finalCols,
    cardW: maxUnits * base + (maxUnits - 1) * COL_GAP,
    cardH: maxH,   // hard cap — content clipped only as absolute last resort
  }
}

// Skyline packing. Cards stack in a column until the next card would exceed maxH,
// then start a new column to the right. Oldest orders are leftmost.
function packBoard(cards, colW, maxH) {
  const tracks = []   // tracks[i] = current bottom y of track column i
  const positions = {}
  const trackW = colW + COL_GAP

  const topOf = (s, u) => {
    let y = 0
    for (let k = 0; k < u; k++) y = Math.max(y, tracks[s + k] || 0)
    return y
  }

  for (const card of cards) {
    const u = card.units

    // Find the leftmost existing group of u tracks where the card fits vertically
    let placed = null
    for (let s = 0; s + u <= tracks.length; s++) {
      const y = topOf(s, u)
      if (y + card.cardH <= maxH + 0.5) {
        placed = { s, y }
        break
      }
    }
    // No room in existing tracks — open new track column(s)
    if (!placed) placed = { s: tracks.length, y: 0 }

    const { s, y } = placed
    const bottom = y + card.cardH + CARD_GAP_Y
    for (let k = 0; k < u; k++) tracks[s + k] = bottom
    positions[card.id] = { x: s * trackW, y }
  }

  let totalW = 0
  for (const id in positions) {
    const card = cards.find(c => c.id === id)
    if (card) totalW = Math.max(totalW, positions[id].x + card.cardW)
  }
  return { positions, totalW }
}

// ─────────────────────────────────────────────────────────────────────────────
// Item block
// ─────────────────────────────────────────────────────────────────────────────

const MOD_ICON_SZ = 13

// Chrome = fixed header rows + body padding. Scales with fontScale so the
// layout engine stays accurate when the user zooms from the topbar buttons.
function cardChrome(fs, hasWaiter, hasNote) {
  const head = Math.round(CARD_HEAD_H_BASE * fs)
  const sub  = Math.round(CARD_SUB_H_BASE  * fs)
  const foot = hasWaiter ? Math.round(CARD_FOOT_H_BASE * fs) : 0
  const note = hasNote   ? Math.round(CARD_NOTE_H_BASE * fs) : 0
  return head + sub + foot + note + BODY_PAD_T + BODY_PAD_B
}

// Collapse repeated modifier names into "Nx Name" entries
function dedupeModifiers(arr) {
  const counts = new Map()
  for (const name of arr) counts.set(name, (counts.get(name) || 0) + 1)
  return [...counts.entries()].map(([name, n]) => n > 1 ? `${n}x ${name}` : name)
}

// Modifier row. Icon always aligns with the FIRST line of text, even if text wraps.
// alignItems:'flex-start' + matching height on icon achieves this.
function ModRow({ Icon, color, text, done, fs }) {
  const col      = done ? COLORS.doneInk : color
  const fontSize = Math.round(12 * fs)
  const lineH    = Math.round(fontSize * 1.4)  // the actual rendered line-height in px
  const iconSz   = Math.round(MOD_ICON_SZ * fs)
  return (
    <div style={{
      display: 'flex', alignItems: 'flex-start', gap: 4,
      color: col, fontSize, fontWeight: 600,
      textDecoration: done ? 'line-through' : 'none',
    }}>
      {/* Icon container is exactly one line tall, centring the icon within that line */}
      <span style={{
        flexShrink: 0, width: iconSz, height: lineH,
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
      }}>
        <Icon style={{ width: iconSz, height: iconSz, color: col }} />
      </span>
      <span style={{ lineHeight: `${lineH}px` }}>{text}</span>
    </div>
  )
}

function ItemBlock({ item, kdsStatus, focused, onBump, onItemDecline, fontScale, fontWeight, combineModifiers }) {
  const done      = kdsStatus === 'done' || kdsStatus === 'served'
  const preparing = kdsStatus === 'preparing'
  const fs        = fontScale ?? 1
  const fw        = fontWeight ?? 600

  const removed = dedupeModifiers(item.removed ?? [])
  const extras  = dedupeModifiers(item.extras  ?? [])
  const prefs   = dedupeModifiers(item.prefs   ?? [])
  const hasMods = removed.length || extras.length || prefs.length || item.notes

  const nameFontSize = Math.round(15 * fs)
  const nameLineH    = Math.round(nameFontSize * 1.4)  // rendered line height of one name line
  // Badge is exactly one name-line tall so it always aligns with the first line of text,
  // even when the product name wraps. alignItems:'flex-start' keeps both at the top.
  const badgeH = nameLineH

  const itemHoldRef = useRef(null)
  const handleItemPointerDown = focused && onItemDecline ? (e) => {
    e.stopPropagation()
    itemHoldRef.current = setTimeout(() => {
      onItemDecline(item.id, item.product_name)
    }, 500)
  } : undefined
  const cancelItemHold = () => { if (itemHoldRef.current) { clearTimeout(itemHoldRef.current); itemHoldRef.current = null } }

  return (
    <div
      onClick={focused ? (e) => { e.stopPropagation(); cancelItemHold(); onBump(e) } : undefined}
      onPointerDown={handleItemPointerDown}
      onPointerUp={cancelItemHold}
      onPointerLeave={cancelItemHold}
      onPointerCancel={cancelItemHold}
      style={{
        borderRadius: 7, margin: '0 -5px', padding: '2px 5px',
        cursor: focused ? 'pointer' : 'default',
        transition: 'background .1s',
        opacity: done ? 0.55 : 1,
        touchAction: 'none',
      }}
      onMouseEnter={focused ? (e) => { e.currentTarget.style.background = 'rgba(47,127,240,.07)' } : undefined}
      onMouseLeave={focused ? (e) => { e.currentTarget.style.background = '' } : undefined}
    >
      {/* badge + name: flex-start so badge stays at first line when name wraps */}
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 7 }}>
        <span style={{
          flexShrink: 0,
          minWidth: badgeH, height: badgeH,
          paddingLeft: UNIT_LABELS[item.unit_type] ? Math.round(4 * fs) : 0,
          paddingRight: UNIT_LABELS[item.unit_type] ? Math.round(4 * fs) : 0,
          display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
          background: done ? '#f1f2f4' : COLORS.qtyBg,
          color: done ? COLORS.doneInk : COLORS.qtyInk,
          borderRadius: Math.round(5 * fs),
          fontFamily: 'ui-monospace, monospace',
          fontWeight: 700, fontSize: Math.round(12 * fs),
          lineHeight: 1,
        }}>
          {fmtItemQty(item.quantity, item.unit_type)}
        </span>

        <span style={{
          color: done ? COLORS.doneInk : COLORS.ink,
          fontSize: nameFontSize, fontWeight: fw,
          lineHeight: `${nameLineH}px`,
          textDecoration: done ? 'line-through' : 'none',
          textDecorationThickness: 2,
          flex: '1 1 auto', minWidth: 0,
        }}>
          {item.product_name}
        </span>

        {focused && (
          <span style={{
            flexShrink: 0, width: 8, height: 8, borderRadius: 4, display: 'inline-block',
            marginTop: Math.round((badgeH - 8) / 2),
            background: done ? COLORS.green : preparing ? COLORS.accent : COLORS.qtyBg,
            border: `1px solid ${done ? COLORS.greenD : preparing ? COLORS.accent : '#ccc'}`,
          }} />
        )}
      </div>

      {hasMods && (
        <div style={{ marginTop: 3, paddingLeft: badgeH + 7, display: 'flex', flexDirection: 'column', gap: 3 }}>
          {combineModifiers ? (
            <>
              {removed.length > 0 && <ModRow Icon={Minus}     color={COLORS.redD}   text={removed.join(' · ')} done={done} fs={fs} />}
              {extras.length  > 0 && <ModRow Icon={Plus}      color={COLORS.greenD} text={extras.join(' · ')}  done={done} fs={fs} />}
              {prefs.length   > 0 && <ModRow Icon={ArrowRight} color={COLORS.amberD} text={prefs.join(' · ')}   done={done} fs={fs} />}
            </>
          ) : (
            <>
              {removed.map((r, i) => <ModRow key={'r'+i} Icon={Minus}      color={COLORS.redD}   text={r} done={done} fs={fs} />)}
              {extras.map((e, i)  => <ModRow key={'e'+i} Icon={Plus}       color={COLORS.greenD} text={e} done={done} fs={fs} />)}
              {prefs.map((p, i)   => <ModRow key={'p'+i} Icon={ArrowRight}  color={COLORS.amberD} text={p} done={done} fs={fs} />)}
            </>
          )}
          {item.notes && <ModRow Icon={Info} color='#3b82f6' text={item.notes} done={done} fs={fs} />}
        </div>
      )}
    </div>
  )
}

// ─────────────────────────────────────────────────────────────────────────────
// Channel badge
// ─────────────────────────────────────────────────────────────────────────────

const CHANNEL_META = {
  here:     { label: 'Εδώ',      Icon: Utensils },
  takeaway: { label: 'Takeaway', Icon: ShoppingBag },
  delivery: { label: 'Delivery', Icon: Bike },
}

function ChannelBadge({ orderType, tableName, fs = 1 }) {
  const meta = CHANNEL_META[orderType] || CHANNEL_META.here
  const Icon = meta.Icon
  const text = orderType === 'here' && tableName ? tableName : meta.label
  const iconSz = Math.round(17 * fs)
  return (
    <span style={{
      display: 'flex', alignItems: 'center', gap: 6,
      fontSize: Math.round(15 * fs), fontWeight: 700, whiteSpace: 'nowrap',
      color: 'rgba(255,255,255,0.60)', flexShrink: 0,
    }}>
      <Icon style={{ width: iconSz, height: iconSz, opacity: 0.65 }} />
      {text}
    </span>
  )
}

// ─────────────────────────────────────────────────────────────────────────────
// Order Card
// Fix #5: focus action bar is icon-only — all 4 buttons fit in one row,
// staying inside the header strip so items below remain tappable.
// ─────────────────────────────────────────────────────────────────────────────

const FLASH_KEYFRAMES = `
@keyframes kds-redflash {
  0%,100% { background: #e8513f; }
  50%      { background: #0a0c0f; }
}
.kds-flash {
  animation: kds-redflash 1.4s ease-in-out infinite;
}
@keyframes kds-cardin {
  from { transform: translateY(10px) scale(.97); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
`

// Action overlay shown when a card is focused.
// Dark-grey container covers header+subheader; 3 padded rounded buttons fill it equally.
// Footer overlay (foot height): Call Waiter button — only renders if there's a footer.
function ActionBar({ order, onSetOrderStatus, onFocus, onPrint, fs = 1, hasWaiter }) {
  const isDone      = order.kds_status === 'done'
  const isPreparing = order.kds_status === 'preparing'
  const iconSz  = Math.round(22 * fs)
  const barH    = Math.round((CARD_HEAD_H_BASE + CARD_SUB_H_BASE) * fs)
  const footH   = Math.round(CARD_FOOT_H_BASE * fs)
  const pad     = Math.round(6 * fs)
  const btnR    = Math.round(9 * fs)

  const Btn = ({ bg, title, onClick, children }) => (
    <button
      title={title}
      onClick={onClick}
      style={{
        flex: 1,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        background: bg, color: '#fff',
        border: 'none', cursor: 'pointer', fontFamily: 'inherit',
        borderRadius: btnR,
        transition: 'filter .1s, transform .08s',
      }}
      onMouseEnter={(e) => { e.currentTarget.style.filter = 'brightness(1.18)' }}
      onMouseLeave={(e) => { e.currentTarget.style.filter = '' }}
      onMouseDown={(e) => { e.currentTarget.style.transform = 'scale(.95)' }}
      onMouseUp={(e) => { e.currentTarget.style.transform = '' }}
    >
      {children}
    </button>
  )

  return (
    <>
      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          position: 'absolute', left: 0, right: 0, top: 0,
          height: barH, zIndex: 4,
          background: '#0a0e13',
          borderRadius: `10px 10px 0 0`,
          display: 'flex', alignItems: 'stretch',
          padding: pad, gap: pad,
        }}
      >
        {/* Prepare */}
        <Btn
          title={isPreparing ? 'Επαναφορά' : 'Σε εκτέλεση'}
          bg={isPreparing ? '#3d4757' : COLORS.accent}
          onClick={() => onSetOrderStatus(order.id, isPreparing ? 'pending' : 'preparing')}
        >
          {isPreparing ? <Undo2 style={{ width: iconSz, height: iconSz }} /> : <Flame style={{ width: iconSz, height: iconSz }} />}
        </Btn>

        {/* Complete */}
        <Btn
          title={isDone ? 'Επαναφορά' : 'Ολοκλήρωση'}
          bg={isDone ? '#3d4757' : COLORS.greenD}
          onClick={() => onSetOrderStatus(order.id, isDone ? 'pending' : 'done')}
        >
          {isDone ? <Undo2 style={{ width: iconSz, height: iconSz }} /> : <Check style={{ width: iconSz, height: iconSz }} />}
        </Btn>

        {/* Print */}
        <Btn
          title="Εκτύπωση"
          bg="rgba(255,255,255,.12)"
          onClick={(e) => { e.stopPropagation(); onPrint(order.id) }}
        >
          <Printer style={{ width: iconSz, height: iconSz }} />
        </Btn>
      </div>

      {/* Call Waiter footer overlay */}
      {hasWaiter && (
        <button
          title="Κάλεσε σερβιτόρο"
          onClick={(e) => { e.stopPropagation(); onSetOrderStatus(order.id, '__call') }}
          style={{
            position: 'absolute', left: 0, right: 0, bottom: 0,
            height: footH, zIndex: 4,
            display: 'flex', alignItems: 'center', justifyContent: 'center', gap: Math.round(7 * fs),
            background: COLORS.amberD, color: '#fff',
            border: 'none', cursor: 'pointer', fontFamily: 'inherit',
            fontSize: Math.round(13 * fs), fontWeight: 700,
            transition: 'filter .1s',
          }}
          onMouseEnter={(e) => { e.currentTarget.style.filter = 'brightness(1.15)' }}
          onMouseLeave={(e) => { e.currentTarget.style.filter = '' }}
        >
          <BellRing style={{ width: Math.round(14 * fs), height: Math.round(14 * fs) }} />
          Κάλεσε σερβιτόρο
        </button>
      )}
    </>
  )
}

// ─────────────────────────────────────────────────────────────────────────────
// Quick Action Bar — shown on long-press of unselected card
// ─────────────────────────────────────────────────────────────────────────────

function QuickActionBar({ order, onSetOrderStatus, onDecline, onDismiss, fs = 1 }) {
  const isDone      = order.kds_status === 'done'
  const isPreparing = order.kds_status === 'preparing'
  const iconSz = Math.round(20 * fs)
  const barH   = Math.round((CARD_HEAD_H_BASE + CARD_SUB_H_BASE) * fs)
  const pad    = Math.round(6 * fs)
  const btnR   = Math.round(9 * fs)

  const Btn = ({ bg, label, Icon, onClick }) => (
    <button
      onClick={(e) => { e.stopPropagation(); onClick() }}
      style={{
        flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
        gap: Math.round(3 * fs), background: bg, color: '#fff',
        border: 'none', cursor: 'pointer', fontFamily: 'inherit',
        borderRadius: btnR, fontSize: Math.round(10 * fs), fontWeight: 700, letterSpacing: '.06em', textTransform: 'uppercase',
        transition: 'filter .1s, transform .08s',
      }}
      onMouseEnter={(e) => { e.currentTarget.style.filter = 'brightness(1.18)' }}
      onMouseLeave={(e) => { e.currentTarget.style.filter = '' }}
      onMouseDown={(e) => { e.currentTarget.style.transform = 'scale(.95)' }}
      onMouseUp={(e) => { e.currentTarget.style.transform = '' }}
    >
      <Icon style={{ width: iconSz, height: iconSz }} />
      {label}
    </button>
  )

  return (
    <div
      onClick={(e) => e.stopPropagation()}
      style={{
        position: 'absolute', left: 0, right: 0, top: 0,
        height: barH, zIndex: 5,
        background: '#0a0e13',
        borderRadius: `10px 10px 0 0`,
        display: 'flex', alignItems: 'stretch',
        padding: pad, gap: pad,
      }}
    >
      <Btn
        bg={isPreparing ? '#3d4757' : COLORS.accent}
        label={isPreparing ? 'Undo' : 'Prep'}
        Icon={isPreparing ? Undo2 : Flame}
        onClick={() => { onSetOrderStatus(order.id, isPreparing ? 'pending' : 'preparing'); onDismiss() }}
      />
      <Btn
        bg={isDone ? '#3d4757' : COLORS.greenD}
        label={isDone ? 'Undo' : 'Ready'}
        Icon={isDone ? Undo2 : Check}
        onClick={() => { onSetOrderStatus(order.id, isDone ? 'pending' : 'done'); onDismiss() }}
      />
      <Btn
        bg={COLORS.redD}
        label="Decline"
        Icon={Ban}
        onClick={() => { onDecline(order.id, null); onDismiss() }}
      />
      <Btn
        bg="rgba(255,255,255,.10)"
        label="Κλείσιμο"
        Icon={X}
        onClick={onDismiss}
      />
    </div>
  )
}

// ─────────────────────────────────────────────────────────────────────────────
// Decline Modal
// ─────────────────────────────────────────────────────────────────────────────

const DECLINE_REASONS = [
  { value: 'out_of_stock', label: 'Εξαντλήθηκε' },
  { value: 'too_many_orders', label: 'Υπερβολικά πολλές παραγγελίες' },
  { value: 'other', label: 'Άλλος λόγος' },
]

function DeclineModal({ orderId, itemId, itemName, onConfirm, onClose }) {
  const [reason, setReason] = useState(null)
  const [note, setNote]     = useState('')
  const isItem = itemId != null

  const confirm = () => {
    if (!reason) return
    const finalNote = reason === 'other' ? (note.trim() || null) : DECLINE_REASONS.find(r => r.value === reason)?.label
    onConfirm(orderId, itemId, finalNote)
    onClose()
  }

  return (
    <div
      onClick={onClose}
      style={{ position: 'absolute', inset: 0, zIndex: 300, background: 'rgba(10,13,17,.8)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
    >
      <div
        onClick={e => e.stopPropagation()}
        style={{ width: 380, background: '#1a2029', border: '1px solid #2c3744', borderRadius: 18, boxShadow: '0 32px 80px rgba(0,0,0,.6)', color: '#e7ecf2', fontFamily: 'inherit', overflow: 'hidden' }}
      >
        <div style={{ padding: '18px 20px 14px', borderBottom: '1px solid #252e38' }}>
          <div style={{ fontSize: 17, fontWeight: 700, color: '#e7ecf2' }}>
            {isItem ? 'Απόρριψη αντικειμένου' : 'Απόρριψη παραγγελίας'}
          </div>
          {isItem && itemName && (
            <div style={{ fontSize: 13, color: '#6b7a8d', marginTop: 4 }}>{itemName}</div>
          )}
        </div>

        <div style={{ padding: '14px 20px', display: 'flex', flexDirection: 'column', gap: 8 }}>
          {DECLINE_REASONS.map(r => (
            <button
              key={r.value}
              onClick={() => setReason(r.value)}
              style={{
                width: '100%', padding: '12px 14px', borderRadius: 10, textAlign: 'left',
                background: reason === r.value ? 'rgba(232,81,63,.15)' : '#1e2832',
                border: `2px solid ${reason === r.value ? COLORS.red : '#2c3744'}`,
                color: reason === r.value ? '#f5a7a0' : '#9ca8b6',
                fontSize: 14, fontWeight: reason === r.value ? 600 : 400,
                cursor: 'pointer', fontFamily: 'inherit',
                transition: 'background .1s, border-color .1s, color .1s',
              }}
            >
              {r.label}
            </button>
          ))}

          {reason === 'other' && (
            <input
              value={note}
              onChange={e => setNote(e.target.value)}
              placeholder="Προσθέστε σημείωση (προαιρετικό)…"
              autoFocus
              style={{
                width: '100%', height: 44, borderRadius: 10, padding: '0 12px',
                background: '#232d38', border: '1px solid #3d4f5e', color: '#e7ecf2',
                fontSize: 14, fontFamily: 'inherit', outline: 'none',
                boxSizing: 'border-box',
              }}
            />
          )}
        </div>

        <div style={{ display: 'flex', gap: 10, padding: '12px 20px 18px' }}>
          <button onClick={onClose} style={{ flex: 1, height: 48, borderRadius: 11, background: '#252e38', color: '#aab8c8', fontSize: 15, fontWeight: 600, border: 'none', cursor: 'pointer', fontFamily: 'inherit' }}
            onMouseEnter={e => { e.currentTarget.style.background = '#2e3a47' }}
            onMouseLeave={e => { e.currentTarget.style.background = '#252e38' }}>
            Ακύρωση
          </button>
          <button
            onClick={confirm}
            disabled={!reason}
            style={{ flex: 2, height: 48, borderRadius: 11, background: reason ? COLORS.redD : '#2c3744', color: reason ? '#fff' : '#5e6772', fontSize: 15, fontWeight: 700, border: 'none', cursor: reason ? 'pointer' : 'default', fontFamily: 'inherit', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8, transition: 'background .12s' }}
            onMouseEnter={e => { if (reason) e.currentTarget.style.background = '#c02e1e' }}
            onMouseLeave={e => { if (reason) e.currentTarget.style.background = COLORS.redD }}
          >
            <Ban style={{ width: 16, height: 16 }} />
            {isItem ? 'Απόρριψη αντικειμένου' : 'Απόρριψη παραγγελίας'}
          </button>
        </div>
      </div>
    </div>
  )
}

function IconBtn({ bg, onClick, title, children }) {
  return (
    <button
      title={title}
      onClick={onClick}
      style={{
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        border: 'none', cursor: 'pointer', borderRadius: 8,
        width: 44, height: 44, flexShrink: 0,
        background: bg, color: '#fff',
        transition: 'filter .1s, transform .08s',
        fontFamily: 'inherit',
      }}
      onMouseEnter={(e) => { e.currentTarget.style.filter = 'brightness(1.12)' }}
      onMouseLeave={(e) => { e.currentTarget.style.filter = '' }}
      onMouseDown={(e) => { e.currentTarget.style.transform = 'scale(.95)' }}
      onMouseUp={(e) => { e.currentTarget.style.transform = '' }}
    >
      {children}
    </button>
  )
}

function OrderCard({ order, ageInfo, layout, focused, dimmed, isNew, settings, activeFilters,
                     onFocus, onSetOrderStatus, onBumpItem, onPrint, onDecline, onItemDecline, courses, groupByCourse, batchMode }) {
  const { cols } = layout
  const isFuture = ageInfo.age === 'future'
  const isDone   = ageInfo.age === 'done'
  const fc = settings.flashColors ?? {}
  const headBg = isDone    ? (fc.inactive ?? COLORS.inactive)
    : isFuture ? COLORS.futureHead
    : ageInfo.age === 'green'  ? (fc.green  ?? COLORS.green)
    : ageInfo.age === 'amber'  ? (fc.amber  ?? COLORS.amber) : (fc.red ?? COLORS.red)
  const subBg  = isDone    ? COLORS.inactiveD
    : isFuture ? COLORS.futureSubBg
    : ageInfo.age === 'green'  ? COLORS.greenD
    : ageInfo.age === 'amber'  ? COLORS.amberD : COLORS.redD

  const multiFilter = (activeFilters?.size ?? 1) > 1
  const glowColor = multiFilter
    ? (isDone    ? COLORS.glowDone
      : isFuture ? COLORS.glowFuture
      : order.kds_status === 'preparing' ? COLORS.glowPreparing
      : COLORS.glowPending)
    : null
  const itemCount = order.items.reduce((s, it) => s + it.quantity, 0)
  const waiterLabel = order.waiters?.length ? order.waiters[0] : null
  const fs = settings.fontScale ?? 1

  // Press+hold on unselected card — show QuickActionBar
  const [quickAction, setQuickAction] = useState(false)
  const holdTimerRef = useRef(null)

  const handlePointerDown = (e) => {
    if (focused) return  // already selected — let normal click/item-hold logic run
    holdTimerRef.current = setTimeout(() => {
      setQuickAction(true)
    }, 500)
  }
  const cancelHold = () => { if (holdTimerRef.current) { clearTimeout(holdTimerRef.current); holdTimerRef.current = null } }

  // Auto-deselect after 5s idle when focused
  const idleTimerRef = useRef(null)
  const resetIdle = useCallback(() => {
    if (!focused) return
    if (idleTimerRef.current) clearTimeout(idleTimerRef.current)
    idleTimerRef.current = setTimeout(() => onFocus(null), 5000)
  }, [focused, onFocus])

  useEffect(() => {
    if (focused) {
      resetIdle()
    } else {
      if (idleTimerRef.current) { clearTimeout(idleTimerRef.current); idleTimerRef.current = null }
    }
    return () => { if (idleTimerRef.current) clearTimeout(idleTimerRef.current) }
  }, [focused, resetIdle])

  return (
    <div
      onClick={(e) => { e.stopPropagation(); cancelHold(); if (!quickAction) { onFocus(order.id); resetIdle() } }}
      onPointerDown={handlePointerDown}
      onPointerUp={cancelHold}
      onPointerLeave={cancelHold}
      onPointerCancel={cancelHold}
      style={{
        position: 'absolute',
        left: layout.x, top: layout.y, width: layout.w, height: layout.h,
        background: headBg, borderRadius: 10, overflow: 'hidden',
        boxShadow: focused
          ? `0 10px 24px rgba(0,0,0,.4), 0 0 0 4px rgba(47,127,240,.25)${glowColor ? `, 0 0 0 2px ${glowColor}40` : ''}`
          : `0 1px 2px rgba(0,0,0,.25), 0 6px 16px rgba(0,0,0,.22)${glowColor ? `, 0 0 12px 2px ${glowColor}55, 0 0 0 2px ${glowColor}44` : ''}`,
        outline: focused ? `2px solid ${COLORS.accent}` : '2px solid transparent',
        outlineOffset: 2,
        display: 'flex', flexDirection: 'column',
        transform: focused ? 'scale(1.012)' : 'none',
        transition: 'box-shadow .15s, transform .15s, outline-color .15s',
        opacity: dimmed ? 0.42 : 1,
        filter: dimmed ? 'saturate(.7)' : 'none',
        zIndex: focused ? 60 : 1,
        animation: isNew ? 'kds-cardin .42s cubic-bezier(.16,.8,.3,1) both' : 'none',
        cursor: 'pointer',
        userSelect: 'none', WebkitUserSelect: 'none',
        touchAction: 'none',
      }}
    >
      {/* header — height scales with fs */}
      <div
        className={ageInfo.flash && settings.flashLate ? 'kds-flash' : undefined}
        style={{
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          padding: `0 11px`, color: '#fff',
          height: Math.round(CARD_HEAD_H_BASE * fs), flexShrink: 0,
          background: headBg,
          gap: 8,
        }}
      >
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
          <span style={{
            background: 'rgba(255,255,255,0.92)', color: '#0a0c0f',
            borderRadius: 7, padding: `0 ${Math.round(9 * fs)}px`,
            height: Math.round(30 * fs),
            fontSize: Math.round(17 * fs), fontWeight: 800,
            fontFamily: '"Inter", "SF Pro Display", system-ui, -apple-system, sans-serif',
            fontVariantNumeric: 'tabular-nums',
            letterSpacing: '-0.02em',
            flexShrink: 0,
            display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
          }}>
            {ageInfo.label}
          </span>
          <span style={{ fontSize: 12 * fs, fontWeight: 600, opacity: 0.9, whiteSpace: 'nowrap', lineHeight: 1 }}>
            {itemCount} item{itemCount !== 1 ? 's' : ''}
          </span>
        </div>
        {/* Topbar click deselects focused card */}
        <div
          onClick={(e) => { if (focused) { e.stopPropagation(); onFocus(null) } }}
          style={{ cursor: focused ? 'default' : 'inherit' }}
        >
          <ChannelBadge orderType={order.order_type} tableName={order.table_name} fs={fs} />
        </div>
      </div>

      {/* subheader — height scales with fs */}
      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        background: subBg, color: 'rgba(255,255,255,0.88)',
        padding: '0 11px', height: Math.round(CARD_SUB_H_BASE * fs), flexShrink: 0,
        fontSize: 12.5 * fs, fontWeight: 600,
      }}>
        <span style={{ fontFamily: 'ui-monospace, monospace', letterSpacing: '.02em', opacity: 0.85, lineHeight: 1 }}>
          #{order.id}
        </span>
        <span
          onClick={(e) => { if (focused) { e.stopPropagation(); onFocus(null) } }}
          style={{ fontSize: 11 * fs, fontWeight: 700, letterSpacing: '.08em', textTransform: 'uppercase', lineHeight: 1 }}>
          {order.kds_status === 'done' ? '✓ Done'
            : order.kds_status === 'preparing' ? '▶ Prep'
            : 'Pending'}
        </span>
      </div>

      {/* body */}
      <div style={{
        background: isFuture ? '#f0f2f5'
          : order.kds_status === 'done' ? '#f4f7f4'
          : order.kds_status === 'preparing' ? '#f3f8ff'
          : '#ffffff',
        paddingTop: BODY_PAD_T, paddingBottom: BODY_PAD_B,
        paddingLeft: BODY_PAD_H, paddingRight: BODY_PAD_H,
        display: 'flex', gap: 12,
        flex: '1 1 auto', overflow: 'hidden',
      }}>
        {/* grouped batch mode: single column with batch dividers */}
        {batchMode === 'grouped' && order._batches?.length > 1 ? (
          <div style={{ display: 'flex', flexDirection: 'column', gap: BLOCK_GAP, flex: '1 1 0', minWidth: 0 }}>
            {order._batches.map((batch, bi) => {
              const batchItems = order.items.filter(it => (it._batchIndex ?? 0) === bi)
              if (!batchItems.length) return null
              const batchMins = Math.max(0, Math.floor((Date.now() - parseUTC(batch.opened_at).getTime()) / 60000))
              return (
                <Fragment key={bi}>
                  <div style={{
                    display: 'flex', alignItems: 'center', gap: 6,
                    fontSize: 10 * fs, fontWeight: 700, textTransform: 'uppercase',
                    letterSpacing: '.1em', color: '#7b8694',
                    padding: bi === 0 ? '0 0 2px' : '4px 0 2px',
                  }}>
                    <div style={{ flex: 1, height: 1, background: '#e0e5eb' }} />
                    <span>Batch #{bi + 1}</span>
                    <span style={{
                      background: '#f0f2f5', color: '#5b6370',
                      borderRadius: 5, padding: '1px 6px',
                      fontFamily: 'ui-monospace, monospace',
                    }}>{batchMins}m</span>
                    <div style={{ flex: 1, height: 1, background: '#e0e5eb' }} />
                  </div>
                  {batchItems.map(item => (
                    <ItemBlock
                      key={item.id}
                      item={item}
                      kdsStatus={item.kds_status}
                      focused={focused}
                      fontScale={fs}
                      fontWeight={settings.fontWeight}
                      combineModifiers={settings.combineModifiers}
                      onBump={(e) => { e && e.stopPropagation(); resetIdle(); onBumpItem(order.id, item.id) }}
                      onItemDecline={focused ? (itemId, itemName) => { resetIdle(); onItemDecline(order.id, itemId, itemName) } : null}
                    />
                  ))}
                </Fragment>
              )
            })}
          </div>
        ) : cols.map((col, ci) => {
          const colItems = col.map(idx => order.items[idx]).filter(Boolean)

          if (groupByCourse && courses?.length > 0) {
            // Group items by course_id
            const grouped = {}
            colItems.forEach(item => {
              const key = item.course_id != null ? String(item.course_id) : '__none__'
              if (!grouped[key]) grouped[key] = []
              grouped[key].push(item)
            })

            // Build ordered groups: courses in order, then null group last
            const orderedGroups = []
            courses.forEach(course => {
              const key = String(course.id)
              if (grouped[key]?.length > 0) {
                orderedGroups.push({ course, items: grouped[key] })
              }
            })
            if (grouped['__none__']?.length > 0) {
              orderedGroups.push({ course: null, items: grouped['__none__'] })
            }

            return (
              <div key={ci} style={{ display: 'flex', flexDirection: 'column', gap: BLOCK_GAP, flex: '1 1 0', minWidth: 0 }}>
                {orderedGroups.map((group, gi) => (
                  <div key={gi}>
                    <div style={{
                      fontSize: 10, fontWeight: 700, textTransform: 'uppercase',
                      letterSpacing: '0.08em',
                      color: group.course ? group.course.color : '#6b7a8d',
                      padding: '4px 10px 2px',
                    }}>
                      {group.course ? group.course.name : 'No course'}
                    </div>
                    {group.items.map(item => (
                      <ItemBlock
                        key={item.id}
                        item={item}
                        kdsStatus={item.kds_status}
                        focused={focused}
                        fontScale={fs}
                        fontWeight={settings.fontWeight}
                        combineModifiers={settings.combineModifiers}
                        onBump={(e) => { e && e.stopPropagation(); resetIdle(); onBumpItem(order.id, item.id) }}
                        onItemDecline={focused ? (itemId, itemName) => { resetIdle(); onItemDecline(order.id, itemId, itemName) } : null}
                      />
                    ))}
                  </div>
                ))}
              </div>
            )
          }

          return (
            <div key={ci} style={{ display: 'flex', flexDirection: 'column', gap: BLOCK_GAP, flex: '1 1 0', minWidth: 0 }}>
              {colItems.map((item) => (
                <ItemBlock
                  key={item.id}
                  item={item}
                  kdsStatus={item.kds_status}
                  focused={focused}
                  fontScale={fs}
                  fontWeight={settings.fontWeight}
                  combineModifiers={settings.combineModifiers}
                  onBump={(e) => { e && e.stopPropagation(); resetIdle(); onBumpItem(order.id, item.id) }}
                  onItemDecline={focused ? (itemId, itemName) => { resetIdle(); onItemDecline(order.id, itemId, itemName) } : null}
                />
              ))}
            </div>
          )
        })}
      </div>

      {/* Order-level note (amber, italic, blank separator line above) */}
      {order.notes && (
        <div style={{
          background: '#fffbeb', borderTop: `1px solid ${COLORS.line}`,
          flexShrink: 0,
        }}>
          <div style={{ height: Math.round(6 * fs) }} />
          <div style={{
            padding: `0 ${Math.round(BODY_PAD_H * fs)}px ${Math.round(8 * fs)}px`,
            fontSize: Math.round(12.5 * fs), fontWeight: 600,
            color: '#b45309', fontStyle: 'italic', lineHeight: 1.45,
            display: 'flex', alignItems: 'flex-start', gap: Math.round(5 * fs),
          }}>
            <Info style={{ width: Math.round(13 * fs), height: Math.round(13 * fs), flexShrink: 0, marginTop: Math.round(2 * fs), color: '#d97706' }} />
            <span>{order.notes}</span>
          </div>
        </div>
      )}

      {/* footer — waiter */}
      {waiterLabel && (
        <div style={{
          background: COLORS.cardFoot, color: COLORS.cardFootInk,
          display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 7,
          height: Math.round(CARD_FOOT_H_BASE * fs), flexShrink: 0,
          fontSize: 13 * fs, fontWeight: 700,
          borderTop: `1px solid ${COLORS.line}`,
        }}>
          <User style={{ width: 13, height: 13, opacity: 0.7 }} />
          {waiterLabel}
        </div>
      )}

      {/* Quick action overlay (long-press on unselected) */}
      {quickAction && !focused && (
        <QuickActionBar
          order={order}
          onSetOrderStatus={onSetOrderStatus}
          onDecline={onDecline}
          onDismiss={() => setQuickAction(false)}
          fs={fs}
        />
      )}

      {/* Focus action overlay */}
      {focused && (
        <ActionBar
          order={order}
          onSetOrderStatus={onSetOrderStatus}
          onFocus={onFocus}
          onPrint={onPrint}
          fs={fs}
          hasWaiter={!!waiterLabel}
        />
      )}
    </div>
  )
}

// ─────────────────────────────────────────────────────────────────────────────
// Item Summary Panel — grouped item counts across all visible orders
// Only pending/preparing items; groups only when product + all prefs are identical.
// ─────────────────────────────────────────────────────────────────────────────

function buildItemGroups(orders) {
  const map = new Map()
  for (const order of orders) {
    for (const item of order.items) {
      if (['done', 'served', 'cancelled', 'declined'].includes(item.kds_status)) continue
      if (item.status === 'cancelled') continue

      // Build a canonical key: name + sorted prefs/extras/removes + notes
      const removes = [...(item.removed ?? [])].sort().join('|')
      const extras  = [...(item.extras  ?? [])].sort().join('|')
      const prefs   = [...(item.prefs   ?? [])].sort().join('|')
      const notes   = (item.notes ?? '').trim()
      const key     = `${item.product_name}\x00${removes}\x00${extras}\x00${prefs}\x00${notes}`

      if (!map.has(key)) {
        map.set(key, {
          key,
          product_name: item.product_name,
          removed: item.removed ?? [],
          extras:  item.extras  ?? [],
          prefs:   item.prefs   ?? [],
          notes,
          count: 0,
        })
      }
      map.get(key).count += item.quantity ?? 1
    }
  }
  return [...map.values()].sort((a, b) => b.count - a.count)
}

function ItemSummaryPanel({ orders, mode, open, onClose, fontScale, fontWeight }) {
  const groups = useMemo(() => buildItemGroups(orders), [orders])
  const fs = fontScale ?? 1
  const fw = fontWeight ?? 600

  // Text sizes matching card item style exactly
  const nameFontSize  = Math.round(15 * fs)
  const nameLineH     = Math.round(nameFontSize * 1.4)
  const badgeH        = nameLineH
  const modFontSize   = Math.round(12 * fs)
  const modIconSz     = Math.round(MOD_ICON_SZ * fs)
  const headerFontSz  = Math.round(13 * fs)
  const subFontSz     = Math.round(11 * fs)

  const isModal = mode === 'floating' || mode === 'topbar_button'

  const panelStyle = {
    background: '#161b21',
    border: '1px solid #2b3540',
    borderRadius: isModal ? 14 : 0,
    color: '#e7ecf2',
    fontFamily: 'inherit',
    display: 'flex',
    flexDirection: 'column',
    overflow: 'hidden',
  }

  const header = (
    <div style={{ padding: `${Math.round(10 * fs)}px ${Math.round(14 * fs)}px`, borderBottom: '1px solid #252e38', flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
      <div>
        <div style={{ fontSize: headerFontSz, fontWeight: 700, color: '#e7ecf2', letterSpacing: '.02em' }}>Σύνοψη αντικειμένων</div>
        <div style={{ fontSize: subFontSz, color: '#6b7a8d', marginTop: 1 }}>Pending + Preparing · {groups.length} είδη</div>
      </div>
      {isModal && (
        <button
          onClick={onClose}
          style={{ width: Math.round(28 * fs), height: Math.round(28 * fs), borderRadius: 8, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'none', border: 'none', cursor: 'pointer', color: '#6b7a8d', flexShrink: 0 }}
          onMouseEnter={(e) => { e.currentTarget.style.background = '#252e38' }}
          onMouseLeave={(e) => { e.currentTarget.style.background = 'none' }}
        >
          <X style={{ width: Math.round(13 * fs), height: Math.round(13 * fs) }} />
        </button>
      )}
    </div>
  )

  const listContent = (
    <div style={{ overflowY: 'auto', flex: '1 1 auto', padding: `${Math.round(6 * fs)}px 0` }}>
      {groups.length === 0 ? (
        <div style={{ textAlign: 'center', padding: `${Math.round(24 * fs)}px 0`, color: '#4a5568', fontSize: nameFontSize }}>Κανένα αντικείμενο</div>
      ) : groups.map((g) => {
        const hasMods = g.removed.length || g.extras.length || g.prefs.length || g.notes
        return (
          <div key={g.key} style={{ padding: `${Math.round(4 * fs)}px ${Math.round(14 * fs)}px` }}>
            {/* qty badge + name — mirrors ItemBlock layout, light colors for dark bg */}
            <div style={{ display: 'flex', alignItems: 'flex-start', gap: 7 }}>
              <span style={{
                flexShrink: 0,
                minWidth: badgeH, height: badgeH,
                display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                background: '#2b3540', color: '#f1f5f9',
                borderRadius: Math.round(5 * fs),
                fontFamily: 'ui-monospace, monospace',
                fontWeight: 700, fontSize: Math.round(12 * fs),
                lineHeight: 1,
              }}>
                {g.count}
              </span>
              <span style={{
                color: '#e7ecf2',
                fontSize: nameFontSize, fontWeight: fw,
                lineHeight: `${nameLineH}px`,
                flex: '1 1 auto', minWidth: 0,
              }}>
                {g.product_name}
              </span>
            </div>
            {/* modifiers — light color variants for dark bg */}
            {hasMods && (
              <div style={{ marginTop: 3, paddingLeft: badgeH + 7, display: 'flex', flexDirection: 'column', gap: 3 }}>
                {g.removed.length > 0 && (
                  <div style={{ display: 'flex', alignItems: 'flex-start', gap: 4, color: '#fca5a5', fontSize: modFontSize, fontWeight: 600 }}>
                    <span style={{ flexShrink: 0, width: modIconSz, height: Math.round(modFontSize * 1.4), display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>
                      <Minus style={{ width: modIconSz, height: modIconSz }} />
                    </span>
                    <span>{g.removed.join(' · ')}</span>
                  </div>
                )}
                {g.extras.length > 0 && (
                  <div style={{ display: 'flex', alignItems: 'flex-start', gap: 4, color: '#86efac', fontSize: modFontSize, fontWeight: 600 }}>
                    <span style={{ flexShrink: 0, width: modIconSz, height: Math.round(modFontSize * 1.4), display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>
                      <Plus style={{ width: modIconSz, height: modIconSz }} />
                    </span>
                    <span>{g.extras.join(' · ')}</span>
                  </div>
                )}
                {g.prefs.length > 0 && (
                  <div style={{ display: 'flex', alignItems: 'flex-start', gap: 4, color: '#fcd34d', fontSize: modFontSize, fontWeight: 600 }}>
                    <span style={{ flexShrink: 0, width: modIconSz, height: Math.round(modFontSize * 1.4), display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>
                      <ArrowRight style={{ width: modIconSz, height: modIconSz }} />
                    </span>
                    <span>{g.prefs.join(' · ')}</span>
                  </div>
                )}
                {g.notes && (
                  <div style={{ display: 'flex', alignItems: 'flex-start', gap: 4, color: '#93c5fd', fontSize: modFontSize, fontWeight: 600 }}>
                    <span style={{ flexShrink: 0, width: modIconSz, height: Math.round(modFontSize * 1.4), display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>
                      <Info style={{ width: modIconSz, height: modIconSz }} />
                    </span>
                    <span>{g.notes}</span>
                  </div>
                )}
              </div>
            )}
          </div>
        )
      })}
    </div>
  )

  // ── Sidebar variant: fills its container column
  if (mode === 'right_sidebar' || mode === 'left_sidebar') {
    return (
      <div style={{
        ...panelStyle, height: '100%',
        borderLeft:  mode === 'right_sidebar' ? '1px solid #2b3540' : 'none',
        borderRight: mode === 'left_sidebar'  ? '1px solid #2b3540' : 'none',
      }}>
        {header}
        {listContent}
      </div>
    )
  }

  // ── Floating: absolutely positioned over board viewport, right side, vertically centred
  if (mode === 'floating') {
    return (
      <div style={{
        position: 'absolute', right: 18, zIndex: 50,
        top: '50%', transform: 'translateY(-50%)',
        width: `${Math.round(20 * fs)}vw`, minWidth: 200, maxHeight: '90%',
        pointerEvents: 'auto',
        ...panelStyle,
        boxShadow: '0 8px 40px rgba(0,0,0,.7), 0 0 0 1px rgba(255,255,255,.06)',
      }}>
        {header}
        {listContent}
      </div>
    )
  }

  // ── Topbar button modal: centred overlay, closes on backdrop click
  if (mode === 'topbar_button' && open) {
    return (
      <div
        onClick={onClose}
        style={{ position: 'absolute', inset: 0, zIndex: 150, background: 'rgba(10,13,17,.6)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
      >
        <div
          onClick={(e) => e.stopPropagation()}
          style={{ ...panelStyle, width: `${Math.round(20 * fs)}vw`, minWidth: 220, maxHeight: '80vh', boxShadow: '0 24px 80px rgba(0,0,0,.7)' }}
        >
          {header}
          {listContent}
        </div>
      </div>
    )
  }

  return null
}

// ─────────────────────────────────────────────────────────────────────────────
// Measurement layer
// ─────────────────────────────────────────────────────────────────────────────

function MeasureLayer({ orders, colW, fontScale, fontWeight, combineModifiers, onResult }) {
  const itemRefs = useRef({})
  const prevRef  = useRef({})

  useEffect(() => {
    const measured = {}
    for (const o of orders) {
      const refs = itemRefs.current[o.id] || []
      measured[o.id] = { blocks: o.items.map((_, i) => (refs[i]?.offsetHeight ?? 36)) }
    }
    const prev = prevRef.current
    const keys = Object.keys(measured)
    if (keys.length !== Object.keys(prev).length) { prevRef.current = measured; onResult(measured); return }
    let changed = false
    for (const k of keys) {
      const a = prev[k], b = measured[k]
      if (!a || a.blocks.length !== b.blocks.length || a.blocks.some((h, i) => Math.abs(h - b.blocks[i]) > 0.5)) {
        changed = true; break
      }
    }
    if (changed) { prevRef.current = measured; onResult(measured) }
  })

  return (
    <div style={{
      position: 'absolute', left: -99999, top: 0,
      visibility: 'hidden', pointerEvents: 'none',
      width: colW - BODY_PAD_H * 2,
      fontSize: 14 * fontScale,
    }}>
      {orders.map((o) => {
        itemRefs.current[o.id] = itemRefs.current[o.id] || []
        return (
          <div key={o.id} style={{ padding: '9px 10px' }}>
            {o.items.map((item, i) => (
              <div
                key={item.id}
                ref={(el) => { itemRefs.current[o.id][i] = el }}
                style={{ marginBottom: i < o.items.length - 1 ? BLOCK_GAP : 0 }}
              >
                <ItemBlock item={item} kdsStatus={item.kds_status} focused={false} onBump={null} fontScale={fontScale} fontWeight={fontWeight} combineModifiers={combineModifiers} />
              </div>
            ))}
          </div>
        )
      })}
    </div>
  )
}

// ─────────────────────────────────────────────────────────────────────────────
// Side rail
// Fix #3: clock removed from rail, now lives in top bar.
// ─────────────────────────────────────────────────────────────────────────────

const STATUS_META = [
  { key: 'future',    label: 'Future',  Icon: Clock,      cls: 'green'  },
  { key: 'pending',   label: 'Pending', Icon: Timer,      cls: 'amber'  },
  { key: 'preparing', label: 'Prep',    Icon: Flame,      cls: 'accent' },
  { key: 'done',      label: 'Done',    Icon: CheckCheck, cls: 'done'   },
]

function RailBtn({ active, activeColor, onClick, disabled, fs, children }) {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      style={{
        position: 'relative', width: '100%',
        display: 'flex', flexDirection: 'column', alignItems: 'center',
        gap: Math.round(4 * fs), padding: `${Math.round(9 * fs)}px ${Math.round(4 * fs)}px`,
        borderRadius: Math.round(10 * fs),
        color: active ? activeColor : '#8b95a1',
        background: active ? 'rgba(255,255,255,0.06)' : 'none',
        border: active ? `1px solid rgba(255,255,255,0.08)` : '1px solid transparent',
        cursor: disabled ? 'default' : 'pointer',
        fontFamily: 'inherit',
        opacity: disabled ? 0.32 : 1,
        transition: 'background .12s, color .12s, opacity .15s',
      }}
      onMouseEnter={(e) => { if (!disabled) { e.currentTarget.style.filter = 'brightness(1.12)' } }}
      onMouseLeave={(e) => { e.currentTarget.style.filter = '' }}
    >
      {children}
    </button>
  )
}

function SideRail({ activeFilters, counts, onToggleFilter,
                    soundOn, onToggleSound, canUndo, canRedo,
                    onUndo, onRedo, onSettings, onCallFloor, fontScale }) {
  const fs = fontScale ?? 1
  const iconSzLg = Math.round(24 * fs)
  const iconSzSm = Math.round(20 * fs)
  const labelSz  = Math.round(10 * fs)
  const badgeSz  = Math.round(12 * fs)
  const badgeH   = Math.round(22 * fs)
  const railW    = Math.round(COLORS.railW * fs)

  return (
    <div style={{
      width: railW, flexShrink: 0,
      background: COLORS.rail,
      borderRight: `1px solid ${COLORS.railLine}`,
      display: 'flex', flexDirection: 'column',
      alignItems: 'stretch', padding: `${Math.round(10 * fs)}px 0`, gap: 2,
      overflowY: 'auto',
    }}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 2, padding: `2px ${Math.round(8 * fs)}px` }}>
        {STATUS_META.map(({ key, label, Icon, cls }) => {
          const active = activeFilters.has(key)
          const activeColor = cls === 'green'  ? COLORS.green
            : cls === 'amber'  ? COLORS.amber
            : cls === 'accent' ? COLORS.accent
            : '#cdd6e0'
          return (
            <RailBtn key={key} active={active} activeColor={activeColor} onClick={() => onToggleFilter(key)} fs={fs}>
              <Icon style={{ width: iconSzLg, height: iconSzLg }} />
              <span style={{ fontSize: labelSz, fontWeight: 600, letterSpacing: '.06em', textTransform: 'uppercase' }}>{label}</span>
              <span style={{
                minWidth: badgeH, height: badgeH, padding: `0 ${Math.round(6 * fs)}px`,
                display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                borderRadius: Math.round(11 * fs), fontSize: badgeSz, fontWeight: 700,
                background: active ? activeColor : '#2b333d',
                color: active ? '#0e1217' : '#cfd6df',
              }}>
                {counts[key] || 0}
              </span>
            </RailBtn>
          )
        })}

        <div style={{ height: 1, background: COLORS.railLine, margin: `${Math.round(6 * fs)}px ${Math.round(6 * fs)}px` }} />

        <RailBtn active={false} activeColor="#fff" onClick={onUndo} disabled={!canUndo} fs={fs}>
          <Undo2 style={{ width: iconSzSm, height: iconSzSm }} />
          <span style={{ fontSize: labelSz, fontWeight: 600, letterSpacing: '.06em', textTransform: 'uppercase' }}>Undo</span>
        </RailBtn>
        <RailBtn active={false} activeColor="#fff" onClick={onRedo} disabled={!canRedo} fs={fs}>
          <Redo2 style={{ width: iconSzSm, height: iconSzSm }} />
          <span style={{ fontSize: labelSz, fontWeight: 600, letterSpacing: '.06em', textTransform: 'uppercase' }}>Redo</span>
        </RailBtn>
      </div>

      <div style={{ flex: '1 1 auto' }} />

      <div style={{ display: 'flex', flexDirection: 'column', gap: 2, padding: `2px ${Math.round(8 * fs)}px` }}>
        {[
          { label: 'Call',     Icon: BellRing,                 fn: onCallFloor  },
          { label: soundOn ? 'Sound' : 'Muted', Icon: soundOn ? Volume2 : VolumeX, fn: onToggleSound },
          { label: 'Settings', Icon: Settings,                 fn: onSettings   },
        ].map(({ label, Icon, fn }) => (
          <RailBtn key={label} active={false} activeColor="#fff" onClick={fn} fs={fs}>
            <Icon style={{ width: iconSzSm, height: iconSzSm }} />
            <span style={{ fontSize: labelSz, fontWeight: 600, letterSpacing: '.06em', textTransform: 'uppercase' }}>{label}</span>
          </RailBtn>
        ))}
      </div>
    </div>
  )
}

// ─────────────────────────────────────────────────────────────────────────────
// Top bar — tabs + clock (Fix #3) + font zoom (Fix #4)
// ─────────────────────────────────────────────────────────────────────────────

const TABS = [
  { key: 'all',      label: 'Όλα'      },
  { key: 'here',     label: 'Εδώ'      },
  { key: 'takeaway', label: 'Takeaway' },
  { key: 'delivery', label: 'Delivery' },
]

function TopBar({ activeTab, counts, onTab, clock, fontScale, onFontScale, showSummaryBtn, summaryOpen, onToggleSummary }) {
  const CLOCK_FONT = 'Arial, "Helvetica Neue", sans-serif'
  const fs    = fontScale ?? 1
  const barH  = Math.round(COLORS.topbarH * fs)
  const tabH  = Math.round(36 * fs)
  const tabR  = Math.round(9 * fs)

  return (
    <div style={{
      height: barH, flexShrink: 0,
      background: COLORS.topbar,
      borderBottom: `1px solid ${COLORS.railLine}`,
      display: 'flex', alignItems: 'center',
      gap: 8, padding: '0 12px',
    }}>
      {/* tabs — shrink to their natural width, don't grow */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 6, flexShrink: 0, overflowX: 'auto' }}>
        {TABS.map(({ key, label }) => {
          const active = activeTab === key
          return (
            <button
              key={key}
              onClick={() => onTab(key)}
              style={{
                flexShrink: 0,
                display: 'flex', alignItems: 'center', gap: Math.round(7 * fs),
                height: tabH, padding: `0 ${Math.round(14 * fs)}px`, borderRadius: tabR,
                background: active ? COLORS.tabActiveBg : COLORS.tab,
                color: active ? COLORS.tabActiveInk : COLORS.tabInk,
                fontSize: Math.round(13 * fs), fontWeight: 600, whiteSpace: 'nowrap',
                border: 'none', cursor: 'pointer', fontFamily: 'inherit',
                transition: 'background .12s, color .12s',
              }}
            >
              {label}
              <span style={{
                minWidth: Math.round(20 * fs), height: Math.round(20 * fs), padding: `0 ${Math.round(5 * fs)}px`,
                display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                borderRadius: Math.round(6 * fs), fontSize: Math.round(11 * fs), fontWeight: 700,
                background: active ? 'rgba(0,0,0,.10)' : 'rgba(255,255,255,.08)',
                color: active ? '#41474f' : COLORS.tabInk,
              }}>
                {counts[key] ?? 0}
              </span>
            </button>
          )
        })}
      </div>

      {/* left spacer — pushes summary button to true center */}
      <div style={{ flex: '1 1 0' }} />

      {/* summary button — only when topbar_button mode, sits dead-center */}
      {showSummaryBtn && (
        <button
          onClick={onToggleSummary}
          style={{
            flexShrink: 0,
            display: 'flex', alignItems: 'center', gap: Math.round(6 * fs),
            height: tabH, padding: `0 ${Math.round(14 * fs)}px`,
            borderRadius: tabR,
            background: summaryOpen ? COLORS.tabActiveBg : COLORS.tab,
            color: summaryOpen ? COLORS.tabActiveInk : COLORS.tabInk,
            fontSize: Math.round(13 * fs), fontWeight: 600,
            border: 'none', cursor: 'pointer', fontFamily: 'inherit',
            transition: 'background .12s, color .12s',
          }}
          onMouseEnter={(e) => { if (!summaryOpen) e.currentTarget.style.filter = 'brightness(1.1)' }}
          onMouseLeave={(e) => { e.currentTarget.style.filter = '' }}
        >
          <CookingPot style={{ width: Math.round(15 * fs), height: Math.round(15 * fs) }} />
          Σύνοψη
        </button>
      )}

      {/* right spacer — mirrors left spacer so clock+zoom stay right-aligned */}
      <div style={{ flex: '1 1 0' }} />

      {/* clock — single row: date · time · πμ/μμ */}
      <div style={{ display: 'flex', alignItems: 'center', gap: Math.round(6 * fs), flexShrink: 0, whiteSpace: 'nowrap' }}>
        <span style={{ fontSize: Math.round(14 * fs), fontWeight: 600, color: '#c8d0db', fontFamily: CLOCK_FONT }}>
          {clock.date}
        </span>
        <span style={{ fontSize: Math.round(14 * fs), fontWeight: 700, color: '#f1f4f8', fontFamily: CLOCK_FONT }}>
          {clock.time}
        </span>
        <span style={{ fontSize: Math.round(14 * fs), fontWeight: 600, color: 'rgba(200,208,219,0.50)', fontFamily: CLOCK_FONT }}>
          {clock.ap}
        </span>
      </div>

      {/* divider */}
      <div style={{ width: 1, height: Math.round(24 * fs), background: COLORS.railLine, flexShrink: 0 }} />

      {/* zoom − % + — same height as tabs */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 2, background: COLORS.tab, borderRadius: tabR, padding: `0 ${Math.round(4 * fs)}px`, flexShrink: 0, height: tabH }}>
        <button
          onClick={() => onFontScale(Math.max(FONT_SCALE_MIN, +(fontScale - FONT_SCALE_STEP).toFixed(2)))}
          title="Μικρότερο"
          style={{ width: tabH, height: tabH, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'none', border: 'none', cursor: 'pointer', color: COLORS.tabInk, borderRadius: Math.round(7 * fs) }}
          onMouseEnter={(e) => { e.currentTarget.style.background = '#333c47' }}
          onMouseLeave={(e) => { e.currentTarget.style.background = 'none' }}
        >
          <ZoomOut style={{ width: Math.round(15 * fs), height: Math.round(15 * fs) }} />
        </button>
        <span style={{ fontSize: Math.round(11 * fs), fontWeight: 700, color: COLORS.tabInk, minWidth: Math.round(34 * fs), textAlign: 'center', fontFamily: 'ui-monospace, monospace' }}>
          {Math.round(fontScale * 100)}%
        </span>
        <button
          onClick={() => onFontScale(Math.min(FONT_SCALE_MAX, +(fontScale + FONT_SCALE_STEP).toFixed(2)))}
          title="Μεγαλύτερο"
          style={{ width: tabH, height: tabH, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'none', border: 'none', cursor: 'pointer', color: COLORS.tabInk, borderRadius: Math.round(7 * fs) }}
          onMouseEnter={(e) => { e.currentTarget.style.background = '#333c47' }}
          onMouseLeave={(e) => { e.currentTarget.style.background = 'none' }}
        >
          <ZoomIn style={{ width: Math.round(15 * fs), height: Math.round(15 * fs) }} />
        </button>
      </div>
    </div>
  )
}

// ─────────────────────────────────────────────────────────────────────────────
// Settings modal — tabbed, fixed size, touch-friendly
// ─────────────────────────────────────────────────────────────────────────────

const COLOR_PRESETS = [
  { label: 'Green',  value: '#34b56a' },
  { label: 'Lime',   value: '#7ec828' },
  { label: 'Yellow', value: '#f5c518' },
  { label: 'Orange', value: '#f07d2a' },
  { label: 'Red',    value: '#e8513f' },
  { label: 'Pink',   value: '#e84f9a' },
  { label: 'Purple', value: '#9b5de5' },
  { label: 'Blue',   value: '#2f7ff0' },
  { label: 'Teal',   value: '#1fabbe' },
  { label: 'White',  value: '#e7ecf2' },
]

const COL_W_STEPS    = [200, 220, 240, 260, 280, 300, 320, 340, 360, 380, 400, 420, 440, 460, 480, 500, 540, 580, 620, 660, 700, 750, 800]
const FW_STEPS       = [300, 400, 500, 600, 700, 800]
const REFRESH_STEPS  = [10, 15, 20, 30, 45, 60, 90, 120]

function stepPrev(steps, val) { const i = steps.indexOf(val); return i > 0 ? steps[i - 1] : steps[0] }
function stepNext(steps, val) { const i = steps.indexOf(val); return i < steps.length - 1 ? steps[i + 1] : steps[steps.length - 1] }
function nearestStep(steps, val) { return steps.reduce((a, b) => Math.abs(b - val) < Math.abs(a - val) ? b : a) }

// Touch-friendly stepper:  [−]  value  [+]
function Stepper({ value, onDec, onInc, display, canDec, canInc }) {
  const btnStyle = (enabled) => ({
    width: 44, height: 44, borderRadius: 10, flexShrink: 0,
    display: 'flex', alignItems: 'center', justifyContent: 'center',
    background: enabled ? '#2e3a47' : '#232d38',
    color: enabled ? '#e7ecf2' : '#4a5568',
    border: `1px solid ${enabled ? '#3d4f5e' : '#2a3542'}`,
    cursor: enabled ? 'pointer' : 'default',
    fontFamily: 'inherit', fontSize: 22, fontWeight: 300,
    transition: 'background .1s, color .1s',
    userSelect: 'none',
  })
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 6, flexShrink: 0 }}>
      <button style={btnStyle(canDec)} onClick={canDec ? onDec : undefined}
        onMouseEnter={(e) => { if (canDec) e.currentTarget.style.background = '#3a4f62' }}
        onMouseLeave={(e) => { e.currentTarget.style.background = canDec ? '#2e3a47' : '#232d38' }}>
        −
      </button>
      <span style={{ minWidth: 64, textAlign: 'center', fontSize: 16, fontWeight: 700, color: '#e7ecf2', fontFamily: 'ui-monospace, monospace' }}>
        {display}
      </span>
      <button style={btnStyle(canInc)} onClick={canInc ? onInc : undefined}
        onMouseEnter={(e) => { if (canInc) e.currentTarget.style.background = '#3a4f62' }}
        onMouseLeave={(e) => { e.currentTarget.style.background = canInc ? '#2e3a47' : '#232d38' }}>
        +
      </button>
    </div>
  )
}

function Toggle({ on, onToggle, disabled }) {
  return (
    <button onClick={disabled ? undefined : onToggle} style={{
      width: 56, height: 32, borderRadius: 16, flexShrink: 0,
      background: on ? COLORS.accent : '#3a4450',
      position: 'relative', border: 'none', cursor: disabled ? 'default' : 'pointer',
      transition: 'background .15s',
      opacity: disabled ? 0.4 : 1,
    }}>
      <span style={{
        position: 'absolute', top: 4, left: 4, width: 24, height: 24,
        borderRadius: '50%', background: '#fff',
        transform: on ? 'translateX(24px)' : 'none',
        transition: 'transform .15s',
      }} />
    </button>
  )
}

function TextRow({ label, sub, value, onChange, placeholder }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16, padding: '10px 0', borderBottom: '1px solid #252e38' }}>
      <div style={{ flex: '1 1 auto', minWidth: 0 }}>
        <span style={{ fontSize: 15, fontWeight: 500, color: '#e7ecf2' }}>{label}</span>
        {sub && <div style={{ fontSize: 12.5, color: '#6b7a8d', marginTop: 3, lineHeight: 1.4 }}>{sub}</div>}
      </div>
      <input
        type="text"
        value={value}
        onChange={(e) => onChange(e.target.value)}
        placeholder={placeholder}
        style={{
          width: 160, height: 44, borderRadius: 10, flexShrink: 0,
          background: '#232d38', border: '1px solid #3d4f5e', color: '#e7ecf2',
          fontSize: 14, fontWeight: 600, padding: '0 12px',
          fontFamily: 'inherit', outline: 'none',
        }}
      />
    </div>
  )
}

function DropdownRow({ label, sub, value, onChange, options }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16, padding: '10px 0', borderBottom: '1px solid #252e38' }}>
      <div style={{ flex: '1 1 auto', minWidth: 0 }}>
        <span style={{ fontSize: 15, fontWeight: 500, color: '#e7ecf2' }}>{label}</span>
        {sub && <div style={{ fontSize: 12.5, color: '#6b7a8d', marginTop: 3, lineHeight: 1.4 }}>{sub}</div>}
      </div>
      <select
        value={value}
        onChange={(e) => onChange(e.target.value)}
        style={{
          flexShrink: 0, height: 44, padding: '0 10px', borderRadius: 10,
          background: '#232d38', border: '1px solid #3d4f5e', color: '#e7ecf2',
          fontSize: 13, fontWeight: 600, fontFamily: 'inherit', outline: 'none', cursor: 'pointer',
          maxWidth: 200,
        }}
      >
        {options.map(o => <option key={o.value} value={o.value}>{o.label}</option>)}
      </select>
    </div>
  )
}

function SRow({ label, sub, children, comingSoon }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16, padding: '10px 0', borderBottom: '1px solid #252e38' }}>
      <div style={{ flex: '1 1 auto', minWidth: 0 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <span style={{ fontSize: 15, fontWeight: 500, color: '#e7ecf2' }}>{label}</span>
          {comingSoon && (
            <span style={{ fontSize: 10, fontWeight: 700, letterSpacing: '.08em', textTransform: 'uppercase', background: '#2c3a4a', color: '#6b8aa8', borderRadius: 5, padding: '2px 6px' }}>
              Σύντομα
            </span>
          )}
        </div>
        {sub && <div style={{ fontSize: 12.5, color: '#6b7a8d', marginTop: 3, lineHeight: 1.4 }}>{sub}</div>}
      </div>
      <div style={{ flexShrink: 0 }}>{children}</div>
    </div>
  )
}

function ColorSwatchBtn({ colorVal, onColorChange }) {
  const [open, setOpen] = useState(false)
  return (
    <div style={{ position: 'relative', flexShrink: 0 }}>
      <button
        onClick={() => setOpen((v) => !v)}
        style={{ width: 44, height: 44, borderRadius: 10, border: '2px solid #3d4f5e', background: colorVal, cursor: 'pointer' }}
      />
      {open && (
        <div
          style={{ position: 'absolute', top: 52, left: 0, zIndex: 50, background: '#1c242d', border: '1px solid #3d4f5e', borderRadius: 12, padding: 8, display: 'grid', gridTemplateColumns: 'repeat(5, 36px)', gap: 6, boxShadow: '0 8px 24px rgba(0,0,0,.5)' }}
          onMouseLeave={() => setOpen(false)}
        >
          {COLOR_PRESETS.map((p) => (
            <button
              key={p.value}
              title={p.label}
              onClick={() => { onColorChange(p.value); setOpen(false) }}
              style={{ width: 36, height: 36, borderRadius: 8, border: colorVal === p.value ? '2px solid #fff' : '2px solid transparent', background: p.value, cursor: 'pointer' }}
            />
          ))}
        </div>
      )}
    </div>
  )
}

function ColorRow({ label, colorVal, onColorChange, minutes, onMinutesChange, minMinutes, maxMinutes }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 0', borderBottom: '1px solid #252e38' }}>
      <ColorSwatchBtn colorVal={colorVal} onColorChange={onColorChange} />
      <span style={{ flex: '1 1 auto', fontSize: 15, fontWeight: 500, color: '#e7ecf2' }}>{label}</span>
      <Stepper
        value={minutes}
        display={`${minutes}m`}
        canDec={minutes > minMinutes}
        canInc={minutes < maxMinutes}
        onDec={() => onMinutesChange(Math.max(minMinutes, minutes - 1))}
        onInc={() => onMinutesChange(Math.min(maxMinutes, minutes + 1))}
      />
    </div>
  )
}

const SETTINGS_TABS = ['Ζώνη', 'Λειτουργία', 'Εμφάνιση', 'Χρώματα', 'Ειδοποιήσεις']


function SettingsModal({ settings, onSave, onClose }) {
  const [local, setLocal] = useState(() => ({
    ...SETTINGS_DEFAULTS,
    ...settings,
    // deep-merge flashColors so new keys (inactive) always have a value
    flashColors: { ...SETTINGS_DEFAULTS.flashColors, ...(settings.flashColors ?? {}) },
  }))
  const [tab, setTab] = useState(0)
  const { data: prepZones = [] } = useQuery({
    queryKey: ['prep-zones'],
    queryFn: () => client.get('/api/prep-zones').then(r => r.data),
  })

  const set    = (k, v) => setLocal((s) => ({ ...s, [k]: v }))
  const setThr = (k, v) => setLocal((s) => ({ ...s, thresholds: { ...s.thresholds, [k]: +v } }))
  const setFlashColor = (k, v) => setLocal((s) => ({ ...s, flashColors: { ...(s.flashColors ?? {}), [k]: v } }))
  const thr = local.thresholds ?? THR_DEFAULT
  const fc  = local.flashColors ?? SETTINGS_DEFAULTS.flashColors

  const handleZoneSelect = (zoneId) => { set('kdsZoneId', zoneId) }

  const colWSnapped      = nearestStep(COL_W_STEPS, local.colW ?? COL_W_DEFAULT)
  const fontWeightSnapped = nearestStep(FW_STEPS, local.fontWeight ?? 600)
  const refreshSnapped   = nearestStep(REFRESH_STEPS, local.autoRefresh ?? 30)

  const TAB_CONTENT = [
    // ── PREP ZONE ──
    <div key="zone" style={{ display: 'flex', flexDirection: 'column' }}>
      <div style={{ fontSize: 12.5, color: '#6b7a8d', padding: '12px 0 14px', lineHeight: 1.5 }}>
        Επιλέξτε τη ζώνη προετοιμασίας για αυτή την οθόνη KDS. Θα εμφανίζονται μόνο παραγγελίες με αντικείμενα ανήκοντα στην επιλεγμένη ζώνη.
      </div>
      {prepZones.length === 0 ? (
        <div style={{ textAlign: 'center', padding: '24px 0', color: '#5e6772', fontSize: 14 }}>
          Δεν έχουν οριστεί ζώνες. Δημιουργήστε ζώνες από Management → Prep Zones.
        </div>
      ) : (
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8 }}>
          {/* All zones option */}
          <button
            onClick={() => set('kdsZoneId', null)}
            style={{
              padding: '12px 8px', borderRadius: 10, textAlign: 'center',
              background: !local.kdsZoneId ? 'rgba(47,127,240,.15)' : '#1e2832',
              border: `2px solid ${!local.kdsZoneId ? COLORS.accent : '#2c3744'}`,
              color: !local.kdsZoneId ? COLORS.accent : '#7b8694',
              fontSize: 12, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit',
              display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 4,
              minHeight: 64,
              transition: 'background .1s, border-color .1s, color .1s',
            }}
          >
            {!local.kdsZoneId && <Check style={{ width: 14, height: 14, flexShrink: 0 }} />}
            <span style={{ lineHeight: 1.3 }}>Όλες οι ζώνες</span>
          </button>
          {prepZones.map(z => {
            const active = local.kdsZoneId === z.id
            return (
              <button
                key={z.id}
                onClick={() => handleZoneSelect(z.id)}
                style={{
                  padding: '12px 8px', borderRadius: 10, textAlign: 'center',
                  background: active ? 'rgba(47,127,240,.15)' : '#1e2832',
                  border: `2px solid ${active ? COLORS.accent : '#2c3744'}`,
                  color: active ? '#e7ecf2' : '#9ca8b6', fontSize: 12, fontWeight: active ? 700 : 500,
                  cursor: 'pointer', fontFamily: 'inherit',
                  display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 4,
                  minHeight: 64,
                  transition: 'background .1s, border-color .1s',
                }}
              >
                {active && <Check style={{ width: 14, height: 14, color: COLORS.accent, flexShrink: 0 }} />}
                <span style={{ lineHeight: 1.3 }}>{z.name}</span>
                {z.description && <span style={{ fontSize: 10, color: '#6b7a8d', lineHeight: 1.3 }}>{z.description}</span>}
              </button>
            )
          })}
        </div>
      )}
    </div>,

    // ── FUNCTIONALITY ──
    <div key="func" style={{ display: 'flex', flexDirection: 'column' }}>
      <SRow label="Αυτόματη ανανέωση" sub="Ανανέωση από server ανά X δευτερόλεπτα (πέραν του SSE)">
        <Stepper
          value={refreshSnapped}
          display={`${refreshSnapped}s`}
          canDec={REFRESH_STEPS.indexOf(refreshSnapped) > 0}
          canInc={REFRESH_STEPS.indexOf(refreshSnapped) < REFRESH_STEPS.length - 1}
          onDec={() => set('autoRefresh', stepPrev(REFRESH_STEPS, refreshSnapped))}
          onInc={() => set('autoRefresh', stepNext(REFRESH_STEPS, refreshSnapped))}
        />
      </SRow>
      <SRow label="Τριπλή κατάσταση παραγγελιών" sub="Pending → Preparing → Done. Αν είναι off, παραλείπεται το Preparing.">
        <Toggle on={local.tripleState} onToggle={() => set('tripleState', !local.tripleState)} />
      </SRow>
      <SRow label="Κατηγορίες ως Φίλτρα" sub="ON: επιλέγεις πολλαπλές κατηγορίες ταυτόχρονα. OFF: μία κατηγορία κάθε φορά (tab mode).">
        <Toggle on={local.categoriesAsFilters ?? true} onToggle={() => set('categoriesAsFilters', !(local.categoriesAsFilters ?? true))} />
      </SRow>
      <SRow label="Group and sort by Course" sub="When enabled, items on each card are grouped under their course name. Items with no course appear last under 'No course'.">
        <Toggle on={local.groupByCourse ?? false} onToggle={() => set('groupByCourse', !(local.groupByCourse ?? false))} />
      </SRow>
      <DropdownRow
        label="Ομαδοποίηση αντικειμένων"
        sub="Συνολική εικόνα των αντικειμένων όλων των παραγγελιών (μόνο pending/preparing, ίδια προϊόντα με ίδιες προτιμήσεις)."
        value={local.groupItems ?? 'off'}
        onChange={(v) => set('groupItems', v)}
        options={[
          { value: 'off',            label: 'Απενεργοποιημένο' },
          { value: 'floating',       label: 'Floating (πάντα ορατό)' },
          { value: 'right_sidebar',  label: 'Sidebar δεξιά (25%)' },
          { value: 'left_sidebar',   label: 'Sidebar αριστερά (25%)' },
          { value: 'topbar_button',  label: 'Κουμπί στη γραμμή τίτλου' },
        ]}
      />
      <DropdownRow
        label="Συγκέντρωση Κοινής Παραγγελίας"
        sub="Πώς εμφανίζονται οι παρτίδες (batches) όταν ένα τραπέζι παραγγέλνει πολλές φορές στην ίδια παραγγελία."
        value={local.batchMode ?? 'merged'}
        onChange={(v) => set('batchMode', v)}
        options={[
          { value: 'merged',  label: 'Χωρίς Γκρουπ — μία κάρτα, όλα μαζί' },
          { value: 'grouped', label: 'Με Γκρουπ — μία κάρτα με διαχωριστικά ανά batch' },
          { value: 'split',   label: 'Απενεργοποιημένο — ξεχωριστή κάρτα ανά batch' },
        ]}
      />
    </div>,

    // ── DISPLAY ──
    <div key="disp" style={{ display: 'flex', flexDirection: 'column' }}>
      <SRow label="Πλάτος κάρτας" sub="Βασικό πλάτος κάθε κάρτας παραγγελίας (px).">
        <Stepper
          value={colWSnapped}
          display={`${colWSnapped}px`}
          canDec={COL_W_STEPS.indexOf(colWSnapped) > 0}
          canInc={COL_W_STEPS.indexOf(colWSnapped) < COL_W_STEPS.length - 1}
          onDec={() => set('colW', stepPrev(COL_W_STEPS, colWSnapped))}
          onInc={() => set('colW', stepNext(COL_W_STEPS, colWSnapped))}
        />
      </SRow>
      <SRow label="Βάρος γραμματοσειράς" sub="Πάχος γραμμάτων ονομάτων προϊόντων.">
        <Stepper
          value={fontWeightSnapped}
          display={`${fontWeightSnapped}`}
          canDec={FW_STEPS.indexOf(fontWeightSnapped) > 0}
          canInc={FW_STEPS.indexOf(fontWeightSnapped) < FW_STEPS.length - 1}
          onDec={() => set('fontWeight', stepPrev(FW_STEPS, fontWeightSnapped))}
          onInc={() => set('fontWeight', stepNext(FW_STEPS, fontWeightSnapped))}
        />
      </SRow>
      <SRow label="Flash αργοπορημένων παραγγελιών" sub="Το header αναβοσβήνει όταν η παραγγελία αργεί υπερβολικά.">
        <Toggle on={local.flashLate} onToggle={() => set('flashLate', !local.flashLate)} />
      </SRow>
      <SRow label="Συγχώνευση modifiers ίδιου τύπου" sub="Π.χ. «− ντομάτα · σάλτσα» αντί για ξεχωριστές γραμμές.">
        <Toggle on={local.combineModifiers} onToggle={() => set('combineModifiers', !local.combineModifiers)} />
      </SRow>
    </div>,

    // ── COLORS ──
    <div key="colors" style={{ display: 'flex', flexDirection: 'column' }}>
      <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '.1em', textTransform: 'uppercase', color: '#6b7a8d', padding: '8px 0 4px' }}>
        Χρόνος αναμονής
      </div>
      <ColorRow
        label="Fresh" colorVal={fc.green ?? COLORS.green}
        onColorChange={(v) => setFlashColor('green', v)}
        minutes={thr.green} minMinutes={1} maxMinutes={thr.amber - 1}
        onMinutesChange={(v) => setThr('green', v)}
      />
      <ColorRow
        label="Warning" colorVal={fc.amber ?? COLORS.amber}
        onColorChange={(v) => setFlashColor('amber', v)}
        minutes={thr.amber} minMinutes={thr.green + 1} maxMinutes={thr.flash - 1}
        onMinutesChange={(v) => setThr('amber', v)}
      />
      <ColorRow
        label="Αργά / Flash" colorVal={fc.red ?? COLORS.red}
        onColorChange={(v) => setFlashColor('red', v)}
        minutes={thr.flash} minMinutes={thr.amber + 1} maxMinutes={120}
        onMinutesChange={(v) => setThr('flash', v)}
      />
      <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '.1em', textTransform: 'uppercase', color: '#6b7a8d', padding: '14px 0 4px' }}>
        Κατάσταση
      </div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 0', borderBottom: '1px solid #252e38' }}>
        <ColorSwatchBtn colorVal={fc.inactive ?? COLORS.inactive} onColorChange={(v) => setFlashColor('inactive', v)} />
        <div style={{ flex: '1 1 auto' }}>
          <div style={{ fontSize: 15, fontWeight: 500, color: '#e7ecf2' }}>Inactive (Done)</div>
          <div style={{ fontSize: 12.5, color: '#6b7a8d', marginTop: 3 }}>Χρώμα header για ολοκληρωμένες παραγγελίες.</div>
        </div>
      </div>
    </div>,

    // ── NOTIFICATIONS ──
    <div key="notif" style={{ display: 'flex', flexDirection: 'column' }}>
      <SRow label="Ήχος για νέες παραγγελίες" sub="Global ρύθμιση ήχου. Το κουμπί στο side nav είναι προσωρινή εναλλαγή — και τα δύο πρέπει να είναι ON.">
        <Toggle on={local.sound} onToggle={() => set('sound', !local.sound)} />
      </SRow>
      <SRow label="Ειδοποίηση σερβιτόρου (ολοκλήρωση παραγγελίας)" sub="Ειδοποίηση στους σερβιτόρους της παραγγελίας όταν τελειώσει η προετιμασία.">
        <Toggle on={local.notifyOrderComplete} onToggle={() => set('notifyOrderComplete', !local.notifyOrderComplete)} />
      </SRow>
      <SRow label="Ειδοποίηση σερβιτόρου (ολοκλήρωση αντικειμένου)" sub="Ειδοποίηση όταν ολοκληρωθεί μεμονωμένο αντικείμενο (πριν το σύνολο της παραγγελίας).">
        <Toggle on={local.notifyItemComplete} onToggle={() => set('notifyItemComplete', !local.notifyItemComplete)} />
      </SRow>
    </div>,
  ]

  return (
    <div
      onClick={onClose}
      style={{
        position: 'absolute', inset: 0, zIndex: 200,
        background: 'rgba(10,13,17,.75)',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          width: 680, background: '#1a2029',
          border: '1px solid #2c3744', borderRadius: 18,
          boxShadow: '0 32px 80px rgba(0,0,0,.6)',
          color: '#e7ecf2', fontFamily: 'inherit',
          display: 'flex', flexDirection: 'column',
          overflow: 'hidden',
        }}
      >
        {/* title bar */}
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '18px 22px 0' }}>
          <h2 style={{ margin: 0, fontSize: 20, fontWeight: 700 }}>Ρυθμίσεις KDS</h2>
          <button onClick={onClose} style={{ width: 40, height: 40, borderRadius: 10, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#6b7a8d', background: 'none', border: 'none', cursor: 'pointer' }}
            onMouseEnter={(e) => { e.currentTarget.style.background = '#252e38' }}
            onMouseLeave={(e) => { e.currentTarget.style.background = 'none' }}>
            <X style={{ width: 20, height: 20 }} />
          </button>
        </div>

        {/* tab bar */}
        <div style={{ display: 'flex', gap: 4, padding: '14px 22px 0', borderBottom: '1px solid #252e38' }}>
          {SETTINGS_TABS.map((t, i) => (
            <button
              key={t}
              onClick={() => setTab(i)}
              style={{
                height: 40, padding: '0 18px', borderRadius: '9px 9px 0 0',
                background: tab === i ? '#252e38' : 'none',
                color: tab === i ? '#f1f4f8' : '#6b7a8d',
                border: 'none', cursor: 'pointer', fontFamily: 'inherit',
                fontSize: 14, fontWeight: tab === i ? 700 : 500,
                borderBottom: tab === i ? '2px solid #2f7ff0' : '2px solid transparent',
                transition: 'color .12s, background .12s',
              }}
              onMouseEnter={(e) => { if (tab !== i) e.currentTarget.style.color = '#aab8c8' }}
              onMouseLeave={(e) => { if (tab !== i) e.currentTarget.style.color = '#6b7a8d' }}
            >
              {t}
            </button>
          ))}
        </div>

        {/* content area — each tab absolutely fills the same fixed box so modal never resizes */}
        <div style={{ position: 'relative', height: 470 }}>
          {TAB_CONTENT.map((content, i) => (
            <div
              key={i}
              style={{
                position: 'absolute', inset: 0,
                padding: '6px 22px 12px',
                overflowY: tab === i ? 'auto' : 'hidden',
                visibility: tab === i ? 'visible' : 'hidden',
                pointerEvents: tab === i ? 'auto' : 'none',
              }}
            >
              {content}
            </div>
          ))}
        </div>

        {/* footer */}
        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 10, padding: '14px 22px', borderTop: '1px solid #252e38' }}>
          <button onClick={onClose}
            style={{ height: 48, padding: '0 22px', borderRadius: 11, background: '#252e38', color: '#aab8c8', fontSize: 15, fontWeight: 600, border: 'none', cursor: 'pointer', fontFamily: 'inherit' }}
            onMouseEnter={(e) => { e.currentTarget.style.background = '#2e3a47' }}
            onMouseLeave={(e) => { e.currentTarget.style.background = '#252e38' }}>
            Ακύρωση
          </button>
          <button
            onClick={() => { onSave(local); onClose() }}
            style={{ height: 48, padding: '0 24px', borderRadius: 11, background: COLORS.accent, color: '#fff', fontSize: 15, fontWeight: 700, border: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 8, fontFamily: 'inherit' }}
            onMouseEnter={(e) => { e.currentTarget.style.filter = 'brightness(1.12)' }}
            onMouseLeave={(e) => { e.currentTarget.style.filter = '' }}>
            <Check style={{ width: 18, height: 18 }} /> Αποθήκευση
          </button>
        </div>
      </div>
    </div>
  )
}

// ─────────────────────────────────────────────────────────────────────────────
// Toast
// ─────────────────────────────────────────────────────────────────────────────

function Toasts({ toasts }) {
  return (
    <div style={{ position: 'absolute', bottom: 18, left: '50%', transform: 'translateX(-50%)', zIndex: 300, display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'center', pointerEvents: 'none' }}>
      {toasts.map((t) => (
        <div key={t.id} style={{
          background: '#2b333d', color: '#eef2f6', border: '1px solid #3a4450',
          padding: '11px 18px', borderRadius: 10, fontSize: 14, fontWeight: 600,
          boxShadow: '0 8px 22px rgba(0,0,0,.4)',
        }}>
          {t.msg}
        </div>
      ))}
    </div>
  )
}

// ─────────────────────────────────────────────────────────────────────────────
// Call Waiter modal — shows on-shift waiters, touch-friendly multi-select
// ─────────────────────────────────────────────────────────────────────────────

function CallWaiterModal({ zoneName, onSend, onClose }) {
  const [waiters, setWaiters] = useState([])
  const [selected, setSelected] = useState(new Set())
  const [loading, setLoading] = useState(true)
  const [sending, setSending] = useState(false)

  useEffect(() => {
    client.get('/api/kds/on-shift-waiters')
      .then((r) => setWaiters(r.data))
      .catch(() => setWaiters([]))
      .finally(() => setLoading(false))
  }, [])

  const toggle = (id) => setSelected((prev) => {
    const next = new Set(prev)
    if (next.has(id)) next.delete(id); else next.add(id)
    return next
  })

  const selectAll = () => setSelected(new Set(waiters.map((w) => w.id)))

  const send = async () => {
    if (!selected.size) return
    setSending(true)
    try {
      await onSend([...selected])
      onClose()
    } finally {
      setSending(false)
    }
  }

  return (
    <div
      onClick={onClose}
      style={{
        position: 'absolute', inset: 0, zIndex: 200,
        background: 'rgba(10,13,17,.78)',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          width: 420, background: '#1a2029',
          border: '1px solid #2c3744', borderRadius: 18,
          boxShadow: '0 32px 80px rgba(0,0,0,.6)',
          color: '#e7ecf2', fontFamily: 'inherit',
          display: 'flex', flexDirection: 'column',
          overflow: 'hidden',
        }}
      >
        {/* header */}
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '18px 20px 14px' }}>
          <div>
            <div style={{ fontSize: 18, fontWeight: 700 }}>Κάλεσε Σερβιτόρο</div>
            <div style={{ fontSize: 12.5, color: '#6b7a8d', marginTop: 2 }}>
              Η <span style={{ color: '#e7ecf2', fontWeight: 600 }}>{zoneName}</span> θέλει βοήθεια
            </div>
          </div>
          <button onClick={onClose} style={{ width: 40, height: 40, borderRadius: 10, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#6b7a8d', background: 'none', border: 'none', cursor: 'pointer' }}
            onMouseEnter={(e) => { e.currentTarget.style.background = '#252e38' }}
            onMouseLeave={(e) => { e.currentTarget.style.background = 'none' }}>
            <X style={{ width: 20, height: 20 }} />
          </button>
        </div>

        {/* waiter list */}
        <div style={{ padding: '0 14px', maxHeight: 320, overflowY: 'auto' }}>
          {loading && (
            <div style={{ textAlign: 'center', padding: '32px 0', color: '#5e6772', fontSize: 14 }}>Φόρτωση…</div>
          )}
          {!loading && waiters.length === 0 && (
            <div style={{ textAlign: 'center', padding: '32px 0', color: '#5e6772', fontSize: 14 }}>Κανένας σερβιτόρος σε βάρδια</div>
          )}
          {!loading && waiters.length > 0 && (
            <>
              {/* Select all row */}
              <button
                onClick={selected.size === waiters.length ? () => setSelected(new Set()) : selectAll}
                style={{
                  width: '100%', height: 52, borderRadius: 12, marginBottom: 8,
                  background: selected.size === waiters.length ? 'rgba(47,127,240,.18)' : '#1e2832',
                  border: `2px solid ${selected.size === waiters.length ? COLORS.accent : '#2c3744'}`,
                  color: selected.size === waiters.length ? COLORS.accent : '#7b8694',
                  fontSize: 14, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit',
                  display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
                  transition: 'background .1s, border-color .1s, color .1s',
                }}
              >
                {selected.size === waiters.length ? <Check style={{ width: 16, height: 16 }} /> : null}
                Όλοι οι σερβιτόροι
              </button>
              {waiters.map((w) => {
                const sel = selected.has(w.id)
                return (
                  <button
                    key={w.id}
                    onClick={() => toggle(w.id)}
                    style={{
                      width: '100%', height: 60, borderRadius: 12, marginBottom: 8,
                      background: sel ? 'rgba(47,127,240,.18)' : '#1e2832',
                      border: `2px solid ${sel ? COLORS.accent : '#2c3744'}`,
                      color: '#e7ecf2', fontSize: 15, fontWeight: 600,
                      cursor: 'pointer', fontFamily: 'inherit',
                      display: 'flex', alignItems: 'center', gap: 14, padding: '0 16px',
                      transition: 'background .1s, border-color .1s',
                    }}
                  >
                    <span style={{
                      width: 36, height: 36, borderRadius: '50%', flexShrink: 0,
                      background: sel ? COLORS.accent : '#2c3744',
                      display: 'flex', alignItems: 'center', justifyContent: 'center',
                      fontSize: 16, fontWeight: 700,
                      transition: 'background .1s',
                    }}>
                      {sel ? <Check style={{ width: 18, height: 18 }} /> : (w.nickname || w.username)?.[0]?.toUpperCase()}
                    </span>
                    <span style={{ flex: 1, textAlign: 'left' }}>{w.nickname || w.username}</span>
                  </button>
                )
              })}
            </>
          )}
        </div>

        {/* footer */}
        <div style={{ display: 'flex', gap: 10, padding: '14px 20px', borderTop: '1px solid #252e38' }}>
          <button onClick={onClose}
            style={{ flex: 1, height: 52, borderRadius: 11, background: '#252e38', color: '#aab8c8', fontSize: 15, fontWeight: 600, border: 'none', cursor: 'pointer', fontFamily: 'inherit' }}
            onMouseEnter={(e) => { e.currentTarget.style.background = '#2e3a47' }}
            onMouseLeave={(e) => { e.currentTarget.style.background = '#252e38' }}>
            Ακύρωση
          </button>
          <button
            onClick={send}
            disabled={!selected.size || sending}
            style={{
              flex: 2, height: 52, borderRadius: 11, background: selected.size && !sending ? COLORS.amber : '#2c3744',
              color: selected.size && !sending ? '#0e1217' : '#5e6772',
              fontSize: 15, fontWeight: 700, border: 'none',
              cursor: selected.size && !sending ? 'pointer' : 'default',
              fontFamily: 'inherit', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
              transition: 'background .12s, color .12s',
            }}
          >
            <BellRing style={{ width: 18, height: 18 }} />
            {sending ? 'Αποστολή…' : `Κάλεσε${selected.size > 0 ? ` (${selected.size})` : ''}`}
          </button>
        </div>
      </div>
    </div>
  )
}

// ─────────────────────────────────────────────────────────────────────────────
// Main KDS component
// ─────────────────────────────────────────────────────────────────────────────

const SETTINGS_DEFAULTS = {
  thresholds: THR_DEFAULT,
  flashColors: { green: COLORS.green, amber: COLORS.amber, red: COLORS.red, inactive: COLORS.inactive },
  sound: true,
  flashLate: true,
  tripleState: false,
  categoriesAsFilters: true,
  colW: COL_W_DEFAULT,
  fontScale: 1,
  autoRefresh: 30,
  fontWeight: 600,
  combineModifiers: false,
  batchMode: 'merged',
  notifyOrderComplete: false,
  notifyItemComplete: false,
  kdsZoneName: 'Κουζίνα',
  kdsZoneId: null,
  groupItems: 'off',
  groupByCourse: false,
}

function loadSettings() {
  try {
    const raw = localStorage.getItem(STORAGE_KEY)
    if (raw) return { ...SETTINGS_DEFAULTS, ...JSON.parse(raw) }
  } catch { /* ignore */ }
  return { ...SETTINGS_DEFAULTS }
}

export default function KdsPage() {
  const qc    = useQueryClient()
  const token = useAuthStore((s) => s.token)

  const [settings, setSettings] = useState(loadSettings)
  const saveSettings = useCallback((s) => {
    setSettings(s)
    try { localStorage.setItem(STORAGE_KEY, JSON.stringify(s)) } catch { /* ignore */ }
  }, [])

  // quick font-scale tweak without opening modal (Fix #4)
  const setFontScale = useCallback((v) => {
    setSettings((s) => {
      const next = { ...s, fontScale: v }
      try { localStorage.setItem(STORAGE_KEY, JSON.stringify(next)) } catch { /* ignore */ }
      return next
    })
  }, [])

  const colW = settings.colW ?? COL_W_DEFAULT
  const fontScale = settings.fontScale ?? 1

  // Multi-select status toggles — default: future + pending + preparing visible
  const [activeFilters, setActiveFilters] = useState(() => new Set(['future', 'pending', 'preparing']))
  const [tabFilter,    setTabFilter]    = useState('all')
  const [focusId,      setFocusId]      = useState(null)
  const [now,          setNow]          = useState(Date.now())
  const [showSettings,     setShowSettings]     = useState(false)
  const [showCallModal,    setShowCallModal]    = useState(false)
  const [showSummary,      setShowSummary]      = useState(false)
  const [soundOn,          setSoundOn]          = useState(true)
  const [toasts,       setToasts]       = useState([])
  const [boardH,       setBoardH]       = useState(0)
  const [measured,     setMeasured]     = useState({})

  const undoStack = useRef([])
  const redoStack = useRef([])
  const [, setHistVer] = useState(0)

  const pushHistory = useCallback((entry) => {
    undoStack.current.push(entry)
    if (undoStack.current.length > 10) undoStack.current.shift()
    redoStack.current = []
    setHistVer((v) => v + 1)
  }, [])

  const kdsZoneId = settings.kdsZoneId ?? null

  const { data: prepZonesData = [] } = useQuery({
    queryKey: ['prep-zones'],
    queryFn: () => client.get('/api/prep-zones').then(r => r.data),
    staleTime: 60_000,
  })

  const { data: posSettingsData } = useQuery({
    queryKey: ['pos-settings'],
    queryFn: () => client.get('/api/settings/').then(r => r.data),
    staleTime: 60_000,
  })
  const kdsCoursesEnabled = posSettingsData?.['orders.courses_enabled']?.value === 'true'
  const kdsCourses = kdsCoursesEnabled ? (() => { try { return JSON.parse(posSettingsData?.['orders.courses']?.value || '[]') } catch { return [] } })() : []

  const activeKdsZone = prepZonesData.find(z => z.id === kdsZoneId) ?? null

  const { data, isLoading } = useQuery({
    queryKey: ['kds-orders', kdsZoneId],
    queryFn:  () => {
      const url = kdsZoneId ? `/api/kds/orders?zone_id=${kdsZoneId}` : '/api/kds/orders'
      return client.get(url).then((r) => r.data)
    },
    staleTime: 0,
    refetchInterval: (settings.autoRefresh ?? 30) * 1000,
  })

  const [orders, setOrders] = useState([])
  const prevOrderIds = useRef(new Set())

  useEffect(() => {
    if (!data?.orders) return
    setOrders(data.orders)
    const newIds  = new Set(data.orders.map((o) => o.id))
    const arrived = [...newIds].filter((id) => !prevOrderIds.current.has(id))
    if (arrived.length && prevOrderIds.current.size > 0 && soundOn && settings.sound) {
      chime()
      pushToast(`${arrived.length} νέα παραγγελία`)
    }
    prevOrderIds.current = newIds
  }, [data])

  const esRef = useRef(null)
  useEffect(() => {
    if (!token) return
    const es = new EventSource(`/api/sse/stream?token=${encodeURIComponent(token)}`)
    esRef.current = es
    es.onmessage = (e) => {
      try {
        const { type } = JSON.parse(e.data)
        if (['kds_order_updated', 'kds_item_updated', 'order_updated', 'order_paid', 'order_closed', 'item_status_changed'].includes(type)) {
          qc.invalidateQueries({ queryKey: ['kds-orders'] })
        }
      } catch { /* ignore */ }
    }
    return () => { es.close(); esRef.current = null }
  }, [token, qc])

  useEffect(() => {
    const id = setInterval(() => setNow(Date.now()), 1000)
    return () => clearInterval(id)
  }, [])

  const boardRef = useRef(null)
  useEffect(() => {
    const el = boardRef.current
    if (!el) return
    const measure = () => setBoardH((p) => { const h = el.clientHeight; return Math.abs(p - h) > 1 ? h : p })
    measure()
    const ro = new ResizeObserver(measure)
    ro.observe(el)
    window.addEventListener('resize', measure)
    const t1 = setTimeout(measure, 60), t2 = setTimeout(measure, 300)
    return () => { ro.disconnect(); window.removeEventListener('resize', measure); clearTimeout(t1); clearTimeout(t2) }
  }, [])

  // Drag-to-scroll on the board: capture pointer events before cards intercept them.
  // Cards use touchAction:'none' so native scroll is blocked; we re-implement it here.
  useEffect(() => {
    const el = boardRef.current
    if (!el) return
    let startX = 0, startScroll = 0, dragging = false, moved = false
    const onDown = (e) => {
      if (e.button !== 0 && e.pointerType === 'mouse') return
      startX = e.clientX
      startScroll = el.scrollLeft
      dragging = true
      moved = false
    }
    const onMove = (e) => {
      if (!dragging) return
      const dx = e.clientX - startX
      if (!moved && Math.abs(dx) < 6) return
      moved = true
      el.scrollLeft = startScroll - dx
    }
    const onUp = () => { dragging = false }
    // Use capture so we see events before cards do; passively listen on move for performance
    el.addEventListener('pointerdown', onDown, { capture: true })
    window.addEventListener('pointermove', onMove)
    window.addEventListener('pointerup', onUp)
    return () => {
      el.removeEventListener('pointerdown', onDown, { capture: true })
      window.removeEventListener('pointermove', onMove)
      window.removeEventListener('pointerup', onUp)
    }
  }, [])

  const pushToast = useCallback((msg) => {
    const id = Math.random().toString(36).slice(2)
    setToasts((ts) => [...ts, { id, msg }])
    setTimeout(() => setToasts((ts) => ts.filter((x) => x.id !== id)), 2200)
  }, [])

  const setOrderStatus = useMutation({
    mutationFn: ({ orderId, status }) => client.put(`/api/kds/orders/${orderId}/kds_status`, { status }),
    onSuccess:  () => qc.invalidateQueries({ queryKey: ['kds-orders'] }),
  })
  const setItemStatus = useMutation({
    mutationFn: ({ orderId, itemId, status }) => client.put(`/api/kds/orders/${orderId}/items/${itemId}/kds_status`, { status }),
    onSuccess:  () => qc.invalidateQueries({ queryKey: ['kds-orders'] }),
  })

  const kdsZone = (() => {
    if (kdsZoneId) {
      const z = prepZonesData.find(zz => zz.id === kdsZoneId)
      if (z) return z.notification_name || z.name
    }
    return settings.kdsZoneName || 'Κουζίνα'
  })()

  const callWaiterOnOrder = useCallback((orderId) => {
    client.post(`/api/kds/orders/${orderId}/call-waiter`, { kds_zone: kdsZone })
      .then(() => pushToast('Ειδοποίηση σερβιτόρου στάλθηκε'))
      .catch(() => pushToast('Αποτυχία αποστολής'))
  }, [kdsZone])

  const handleSetOrderStatus = useCallback((orderId, rawStatus) => {
    if (rawStatus === '__call') { callWaiterOnOrder(orderId); return }
    const order = orders.find((o) => o.id === orderId)
    if (!order) return
    setOrders((prev) => prev.map((o) => {
      if (o.id !== orderId) return o
      const shouldCascade = ['pending', 'preparing', 'done'].includes(rawStatus)
      const items = shouldCascade
        ? o.items.map((it) => it.status !== 'cancelled' && it.kds_status !== 'served' ? { ...it, kds_status: rawStatus } : it)
        : o.items
      return { ...o, kds_status: rawStatus, items }
    }))
    pushHistory({ type: 'order', orderId, prev: order.kds_status, next: rawStatus, prevItems: order.items.map((it) => ({ id: it.id, kds_status: it.kds_status })) })
    setOrderStatus.mutate({ orderId, status: rawStatus })
    // Auto-notify waiters on order completion
    if (rawStatus === 'done' && settings.notifyOrderComplete) {
      client.post(`/api/kds/orders/${orderId}/notify-complete`, { kds_zone: kdsZone }).catch(() => {})
    }
    setFocusId(null)
    pushToast({ done: '✓ Ολοκληρώθηκε', preparing: '▶ Σε εκτέλεση', pending: '↩ Επαναφορά' }[rawStatus] ?? rawStatus)
  }, [orders, pushHistory, setOrderStatus, pushToast, settings.notifyOrderComplete, kdsZone])

  const handleBumpItem = useCallback((orderId, itemId) => {
    const order = orders.find((o) => o.id === orderId)
    if (!order) return
    const item = order.items.find((it) => it.id === itemId)
    if (!item) return
    const cycle = settings.tripleState
      ? { pending: 'preparing', preparing: 'done', done: 'pending' }
      : { pending: 'done', preparing: 'done', done: 'pending' }
    const nextStatus = cycle[item.kds_status] ?? 'done'
    setOrders((prev) => prev.map((o) => {
      if (o.id !== orderId) return o
      const items = o.items.map((it) => it.id === itemId ? { ...it, kds_status: nextStatus } : it)
      const active = items.filter((it) => it.status !== 'cancelled')
      const statuses = new Set(active.map((it) => it.kds_status))
      // Mirror _sync_order_kds_status from backend
      const isSubsetOf = (...vals) => [...statuses].every((s) => vals.includes(s))
      const orderKds = statuses.size === 0 ? o.kds_status
        : isSubsetOf('served') ? 'served'
        : isSubsetOf('done', 'served') ? 'done'
        : statuses.has('preparing') || statuses.has('done') ? 'preparing'
        : 'pending'
      return { ...o, items, kds_status: orderKds }
    }))
    pushHistory({ type: 'item', orderId, itemId, prev: item.kds_status, next: nextStatus })
    setItemStatus.mutate({ orderId, itemId, status: nextStatus })

    // Auto-notify on item completion (only when bumping TO done)
    if (nextStatus === 'done' && (settings.notifyOrderComplete || settings.notifyItemComplete)) {
      const updatedItems = order.items.map((it) => it.id === itemId ? { ...it, kds_status: nextStatus } : it)
      const active = updatedItems.filter((it) => it.status !== 'cancelled' && it.kds_status !== 'served')
      const doneCount = active.filter((it) => it.kds_status === 'done').length
      const totalCount = active.length
      const allDone = doneCount === totalCount
      if (allDone && settings.notifyOrderComplete) {
        client.post(`/api/kds/orders/${orderId}/notify-complete`, { kds_zone: kdsZone }).catch(() => {})
      } else if (!allDone && settings.notifyItemComplete) {
        client.post(`/api/kds/orders/${orderId}/notify-items-ready`, {
          kds_zone: kdsZone,
          ready_count: doneCount,
          total_count: totalCount,
        }).catch(() => {})
      }
    }
  }, [orders, settings.tripleState, settings.notifyOrderComplete, settings.notifyItemComplete, kdsZone, pushHistory, setItemStatus])

  // Decline state
  const [declineModal, setDeclineModal] = useState(null)  // { orderId, itemId, itemName } | null

  const handleDecline = useCallback((orderId, itemId) => {
    const order = orders.find(o => o.id === orderId)
    if (!order) return
    setDeclineModal({ orderId, itemId: null, itemName: null })
  }, [orders])

  const handleItemDecline = useCallback((orderId, itemId, itemName) => {
    setDeclineModal({ orderId, itemId, itemName })
  }, [])

  const confirmDecline = useCallback(async (orderId, itemId, note) => {
    try {
      if (itemId) {
        await client.put(`/api/kds/orders/${orderId}/items/${itemId}/decline`, { decline_note: note ?? null })
        setOrders(prev => prev.map(o => {
          if (o.id !== orderId) return o
          return { ...o, items: o.items.map(it => it.id === itemId ? { ...it, kds_status: 'declined' } : it) }
        }))
        pushToast('Αντικείμενο απορρίφθηκε')
      } else {
        await client.put(`/api/kds/orders/${orderId}/decline`, { decline_note: note ?? null })
        setOrders(prev => prev.map(o => {
          if (o.id !== orderId) return o
          return { ...o, kds_status: 'declined', items: o.items.map(it => it.status !== 'cancelled' ? { ...it, kds_status: 'declined', decline_note: note ?? null } : it) }
        }))
        pushToast('Παραγγελία απορρίφθηκε')
      }
      qc.invalidateQueries({ queryKey: ['kds-orders'] })
    } catch { pushToast('Σφάλμα απόρριψης') }
  }, [pushToast, qc])

  // KDS direct print — uses zone auto_print config (managed in Prep Zone settings)
  const handlePrint = useCallback(async (orderId) => {
    try {
      await client.post(`/api/orders/${orderId}/retry-print`)
      pushToast('Εκτύπωση εστάλη')
    } catch { pushToast('Σφάλμα εκτύπωσης') }
  }, [pushToast])

  const handleUndo = useCallback(() => {
    if (!undoStack.current.length) return
    const entry = undoStack.current.pop()
    redoStack.current.push(entry)
    setHistVer((v) => v + 1)
    if (entry.type === 'order') {
      setOrders((prev) => prev.map((o) => {
        if (o.id !== entry.orderId) return o
        const items = entry.prevItems
          ? o.items.map((it) => { const f = entry.prevItems.find((p) => p.id === it.id); return f ? { ...it, kds_status: f.kds_status } : it })
          : o.items
        return { ...o, kds_status: entry.prev, items }
      }))
      setOrderStatus.mutate({ orderId: entry.orderId, status: entry.prev })
    } else {
      setOrders((prev) => prev.map((o) => {
        if (o.id !== entry.orderId) return o
        return { ...o, items: o.items.map((it) => it.id === entry.itemId ? { ...it, kds_status: entry.prev } : it) }
      }))
      setItemStatus.mutate({ orderId: entry.orderId, itemId: entry.itemId, status: entry.prev })
    }
    pushToast('Αναίρεση')
  }, [setOrderStatus, setItemStatus, pushToast])

  const handleRedo = useCallback(() => {
    if (!redoStack.current.length) return
    const entry = redoStack.current.pop()
    undoStack.current.push(entry)
    setHistVer((v) => v + 1)
    if (entry.type === 'order') {
      setOrders((prev) => prev.map((o) => {
        if (o.id !== entry.orderId) return o
        const shouldCascade = ['pending', 'preparing', 'done'].includes(entry.next)
        const items = shouldCascade
          ? o.items.map((it) => it.status !== 'cancelled' && it.kds_status !== 'served' ? { ...it, kds_status: entry.next } : it)
          : o.items
        return { ...o, kds_status: entry.next, items }
      }))
      setOrderStatus.mutate({ orderId: entry.orderId, status: entry.next })
    } else {
      setOrders((prev) => prev.map((o) => {
        if (o.id !== entry.orderId) return o
        return { ...o, items: o.items.map((it) => it.id === entry.itemId ? { ...it, kds_status: entry.next } : it) }
      }))
      setItemStatus.mutate({ orderId: entry.orderId, itemId: entry.itemId, status: entry.next })
    }
    pushToast('Επαναφορά')
  }, [setOrderStatus, setItemStatus, pushToast])

  useEffect(() => {
    const h = (e) => {
      if (e.key === 'Escape') setFocusId(null)
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'z') {
        e.preventDefault(); if (e.shiftKey) handleRedo(); else handleUndo()
      }
    }
    window.addEventListener('keydown', h)
    return () => window.removeEventListener('keydown', h)
  }, [handleUndo, handleRedo])

  // Compute each order's display bucket (future is a virtual bucket for pending orders scheduled in the future)
  const orderBucket = useCallback((order) => {
    if (order.kds_status !== 'pending') return order.kds_status
    return minutesSince(order.opened_at, now) < 0 ? 'future' : 'pending'
  }, [now])

  const counts = useMemo(() => {
    const c = { future: 0, pending: 0, preparing: 0, done: 0 }
    for (const o of orders) {
      const b = orderBucket(o)
      c[b] = (c[b] || 0) + 1
    }
    return c
  }, [orders, orderBucket])

  const tabCounts = useMemo(() => {
    const c = { all: 0, here: 0, takeaway: 0, delivery: 0 }
    const inStatus = orders.filter((o) => activeFilters.has(orderBucket(o)))
    c.all = inStatus.length
    for (const o of inStatus) c[o.order_type] = (c[o.order_type] || 0) + 1
    return c
  }, [orders, activeFilters, orderBucket])

  const visible = useMemo(() => {
    let v = orders.filter((o) => activeFilters.has(orderBucket(o)))
    if (tabFilter !== 'all') v = v.filter((o) => o.order_type === tabFilter)
    v = v.slice().sort((a, b) => new Date(a.opened_at) - new Date(b.opened_at))

    // Apply zone-level item sorting if a zone is selected
    const sortBy = activeKdsZone?.sort_items_by ?? 'order_time'
    if (sortBy !== 'order_time') {
      v = v.map((o) => {
        if (!o.items?.length) return o
        const sorted = [...o.items].sort((a, b) => {
          if (sortBy === 'item_count') return b.quantity - a.quantity
          if (sortBy === 'alpha') return (a.product_name || '').localeCompare(b.product_name || '', 'el')
          return 0
        })
        return { ...o, items: sorted }
      })
    }

    const batchMode = settings.batchMode ?? 'merged'

    // Detect batches within each order by clustering items on added_at.
    // Items added within BATCH_GAP_MS of each other belong to the same batch.
    // This enriches every order with _batches[] and stamps items with _batchIndex.
    const BATCH_GAP_MS = 2 * 60 * 1000  // 2-minute gap = new batch
    v = v.map((o) => {
      if (!o.items?.length) return o
      const sorted = [...o.items].sort((a, b) => parseUTC(a.added_at) - parseUTC(b.added_at))
      const batches = []
      let curBatch = null
      for (const item of sorted) {
        const ts = item.added_at ? parseUTC(item.added_at).getTime() : 0
        if (!curBatch || (ts - curBatch.lastTs) > BATCH_GAP_MS) {
          curBatch = { opened_at: item.added_at || o.opened_at, items: [], lastTs: ts }
          batches.push(curBatch)
        }
        curBatch.items.push(item)
        curBatch.lastTs = ts
      }
      // stamp _batchIndex on each item so the card renderer can group them
      const stampedItems = batches.flatMap((b, bi) =>
        b.items.map(it => ({ ...it, _batchIndex: bi }))
      )
      return { ...o, _batches: batches.map(({ opened_at, items }) => ({ opened_at, items })), items: stampedItems }
    })

    if (batchMode === 'split') {
      // Explode each order: one virtual card per batch
      const exploded = []
      for (const o of v) {
        if (!o._batches || o._batches.length <= 1) { exploded.push(o); continue }
        o._batches.forEach((batch, bi) => {
          exploded.push({
            ...o,
            id: o.id + '_b' + bi,
            _realId: o.id,
            _batchIndex: bi,
            opened_at: batch.opened_at,
            items: batch.items,
            _batches: [batch],
          })
        })
      }
      return exploded
    }

    // merged / grouped: single card per order, _batches already set above
    return v
  }, [orders, activeFilters, tabFilter, orderBucket, settings.batchMode, activeKdsZone])

  const layout = useMemo(() => {
    if (!visible.length || !boardH) return { map: {}, totalW: BOARD_PAD * 2 }
    const maxH = boardH - BOARD_PAD * 2   // hard height limit: board minus top+bottom padding
    const COURSE_HEADER_H = Math.round(18 * fontScale) // height of each course group label row
    const sized = visible.map((o) => {
      const hasWaiter = o.waiters?.length > 0
      const hasNote   = !!o.notes
      // chrome scales with fontScale — critical for correct height at any zoom level
      const chrome = cardChrome(fontScale, hasWaiter, hasNote)
      const m = measured[o.id]
      let blocks = m ? m.blocks : o.items.map(() => Math.round(28 * fontScale))
      // When grouping by course, each group header adds height — inject phantom blocks
      if (settings.groupByCourse && kdsCourses?.length > 0 && o.items?.length > 0) {
        const groupIds = new Set()
        o.items.forEach(item => groupIds.add(item.course_id != null ? String(item.course_id) : '__none__'))
        const extraHeaders = groupIds.size
        const headerBlocks = Array(extraHeaders).fill(COURSE_HEADER_H)
        blocks = [...headerBlocks, ...blocks]
      }
      // In grouped batch mode, each batch divider adds a row of height
      const BATCH_DIV_H = Math.round(22 * fontScale)
      const batchMode = settings.batchMode ?? 'merged'
      if (batchMode === 'grouped' && o._batches?.length > 1) {
        const dividerBlocks = Array(o._batches.length).fill(BATCH_DIV_H)
        blocks = [...dividerBlocks, ...blocks]
      }
      const s = sizeCard({ blocks, blockGap: BLOCK_GAP, chrome, maxH, base: colW })
      return { id: o.id, ...s }
    })
    const { positions, totalW } = packBoard(sized, colW, maxH)
    const map = {}
    sized.forEach((s) => {
      const p = positions[s.id]
      if (!p) return
      map[s.id] = { cols: s.cols, x: p.x + BOARD_PAD, y: p.y + BOARD_PAD, w: s.cardW, h: s.cardH }
    })
    return { map, totalW: totalW + BOARD_PAD * 2 }
  }, [visible, measured, boardH, colW, fontScale])

  // clock — full Greek format
  const d = new Date(now)
  const GREEK_DAYS = ['Κυριακή', 'Δευτέρα', 'Τρίτη', 'Τετάρτη', 'Πέμπτη', 'Παρασκευή', 'Σάββατο']
  const GREEK_MONTHS = ['Ιανουαρίου', 'Φεβρουαρίου', 'Μαρτίου', 'Απριλίου', 'Μαΐου', 'Ιουνίου',
    'Ιουλίου', 'Αυγούστου', 'Σεπτεμβρίου', 'Οκτωβρίου', 'Νοεμβρίου', 'Δεκεμβρίου']
  const rawH = d.getHours()
  const ap = rawH >= 12 ? 'μμ' : 'πμ'
  const hh = rawH % 12 || 12
  const mm = String(d.getMinutes()).padStart(2, '0')
  const clock = {
    time: hh + ':' + mm,
    ap,
    date: `${GREEK_DAYS[d.getDay()]}, ${d.getDate()} ${GREEK_MONTHS[d.getMonth()]} ${d.getFullYear()}`,
  }

  return (
    <>
      <style>{FLASH_KEYFRAMES}</style>

      <div style={{
        display: 'flex', height: '100vh', width: '100vw',
        overflow: 'hidden', position: 'fixed', inset: 0,
        fontFamily: '"Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
        background: COLORS.bg, color: '#e7ecf2',
        WebkitFontSmoothing: 'antialiased',
        userSelect: 'none', WebkitUserSelect: 'none',
      }}>

        <SideRail
          activeFilters={activeFilters}
          counts={counts}
          fontScale={fontScale}
          onToggleFilter={(key) => {
            const asFilters = settings.categoriesAsFilters ?? true
            if (asFilters) {
              // multi-select: toggle on/off, never allow zero active
              setActiveFilters((prev) => {
                const next = new Set(prev)
                if (next.has(key)) { next.delete(key) } else { next.add(key) }
                if (next.size === 0) return prev
                return next
              })
            } else {
              // single-select tab mode: always exactly one active
              setActiveFilters(new Set([key]))
            }
            setTabFilter('all')
            setFocusId(null)
          }}
          soundOn={soundOn}
          onToggleSound={() => { setSoundOn((v) => { if (!v) chime(); return !v }) }}
          canUndo={undoStack.current.length > 0}
          canRedo={redoStack.current.length > 0}
          onUndo={handleUndo}
          onRedo={handleRedo}
          onSettings={() => setShowSettings(true)}
          onCallFloor={() => setShowCallModal(true)}
        />

        <div style={{ flex: '1 1 auto', display: 'flex', flexDirection: 'column', minWidth: 0 }}>
          <TopBar
            activeTab={tabFilter}
            counts={tabCounts}
            onTab={(k) => { setTabFilter(k); setFocusId(null) }}
            clock={clock}
            fontScale={fontScale}
            onFontScale={setFontScale}
            showSummaryBtn={settings.groupItems === 'topbar_button'}
            summaryOpen={showSummary}
            onToggleSummary={() => setShowSummary((v) => !v)}
          />

          {/* Board area: flex row so sidebars can sit alongside the board */}
          <div style={{ flex: '1 1 auto', display: 'flex', minHeight: 0 }}>

            {/* Left sidebar */}
            {settings.groupItems === 'left_sidebar' && (
              <div style={{ width: `${Math.round(20 * fontScale)}vw`, flexShrink: 0, minHeight: 0 }}>
                <ItemSummaryPanel orders={visible} mode="left_sidebar" fontScale={fontScale} fontWeight={settings.fontWeight} />
              </div>
            )}

            {/* Board */}
            <div style={{ flex: '1 1 auto', position: 'relative', minWidth: 0, minHeight: 0 }}>
              <div
                ref={boardRef}
                onClick={() => setFocusId(null)}
                style={{ position: 'absolute', inset: 0, overflowX: 'auto', overflowY: 'hidden' }}
              >
                {/* height: 100% so absolutely-positioned cards have a containing block */}
                {/* When floating panel is active, extra right padding keeps cards scrollable past the panel */}
                <div style={{ position: 'relative', height: '100%', width: Math.max(layout.totalW, 1), minWidth: '100%', paddingRight: settings.groupItems === 'floating' ? `calc(${Math.round(20 * fontScale)}vw + 32px)` : 0 }}>

                  {isLoading && (
                    <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#5e6772', fontSize: 14 }}>
                      Φόρτωση…
                    </div>
                  )}

                  {!isLoading && visible.length === 0 && (
                    <div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 14, color: '#5e6772' }}
                      onClick={(e) => e.stopPropagation()}
                    >
                      <CookingPot style={{ width: 54, height: 54, opacity: 0.5 }} />
                      <div style={{ fontSize: 17, fontWeight: 600, color: '#7b8694' }}>
                        Δεν υπάρχουν παραγγελίες για τα επιλεγμένα φίλτρα
                      </div>
                    </div>
                  )}

                  {visible.map((order) => {
                    const lo = layout.map[order.id]
                    if (!lo) return null
                    const batchMode = settings.batchMode ?? 'merged'
                    // Age clock: for multi-batch cards use the oldest batch with active items
                    let ageOrder = order
                    if (order._batches?.length > 1) {
                      const oldestActive = order._batches.find(b =>
                        b.items.some(it => it.kds_status !== 'done' && it.kds_status !== 'served' && it.status !== 'cancelled')
                      )
                      if (oldestActive) ageOrder = { ...order, opened_at: oldestActive.opened_at }
                    }
                    const ai = ageState(ageOrder, now, settings.thresholds)
                    // In split mode, actions must target the real order id
                    const realId = order._realId ?? order.id
                    return (
                      <OrderCard
                        key={order.id}
                        order={{ ...order, id: realId }}
                        ageInfo={ai}
                        layout={lo}
                        focused={focusId === order.id}
                        dimmed={focusId != null && focusId !== order.id}
                        isNew={false}
                        settings={settings}
                        activeFilters={activeFilters}
                        onFocus={(id) => setFocusId(id === realId ? order.id : id)}
                        onSetOrderStatus={handleSetOrderStatus}
                        onBumpItem={handleBumpItem}
                        onPrint={handlePrint}
                        onDecline={handleDecline}
                        onItemDecline={handleItemDecline}
                        courses={kdsCourses}
                        groupByCourse={settings.groupByCourse ?? false}
                        batchMode={batchMode}
                      />
                    )
                  })}

                </div>
              </div>

              {/* Floating summary panel — sits over the board viewport, not inside scroll content */}
              {settings.groupItems === 'floating' && (
                <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none', zIndex: 50 }}>
                  <ItemSummaryPanel orders={visible} mode="floating" onClose={() => {}} fontScale={fontScale} fontWeight={settings.fontWeight} />
                </div>
              )}

              {/* Topbar-button modal — rendered over the board */}
              {settings.groupItems === 'topbar_button' && showSummary && (
                <ItemSummaryPanel orders={visible} mode="topbar_button" open={showSummary} onClose={() => setShowSummary(false)} fontScale={fontScale} fontWeight={settings.fontWeight} />
              )}
            </div>

            {/* Right sidebar */}
            {settings.groupItems === 'right_sidebar' && (
              <div style={{ width: `${Math.round(20 * fontScale)}vw`, flexShrink: 0, minHeight: 0 }}>
                <ItemSummaryPanel orders={visible} mode="right_sidebar" fontScale={fontScale} fontWeight={settings.fontWeight} />
              </div>
            )}
          </div>
        </div>

        <MeasureLayer orders={visible} colW={colW} fontScale={fontScale} fontWeight={settings.fontWeight} combineModifiers={settings.combineModifiers} onResult={setMeasured} />

        {showSettings && (
          <SettingsModal
            settings={settings}
            onSave={saveSettings}
            onClose={() => setShowSettings(false)}
          />
        )}

        {showCallModal && (
          <CallWaiterModal
            zoneName={kdsZone}
            onSend={(waiterIds) =>
              client.post('/api/kds/call-waiter-general', {
                kds_zone: kdsZone,
                waiter_ids: waiterIds,
              }).then(() => pushToast('Ειδοποίηση στάλθηκε'))
                .catch(() => pushToast('Αποτυχία αποστολής'))
            }
            onClose={() => setShowCallModal(false)}
          />
        )}

        {declineModal && (
          <DeclineModal
            orderId={declineModal.orderId}
            itemId={declineModal.itemId}
            itemName={declineModal.itemName}
            onConfirm={confirmDecline}
            onClose={() => setDeclineModal(null)}
          />
        )}

        <Toasts toasts={toasts} />
      </div>
    </>
  )
}
