// frontend/src/pages/crm/customers/CustomerList.jsx

import { useState, useEffect, useCallback, useRef } from 'react'
import { useNavigate } from 'react-router-dom'
import { createPortal } from 'react-dom'
import api from '@/lib/api'
import { useAuth } from '@/hooks/useAuth'
import usePreferences from '@/hooks/usePreferences'
import PageHeader  from '@/components/ui/PageHeader'
import Button      from '@/components/ui/Button'
import DataTable   from '@/components/ui/DataTable'
import StatusBadge from '@/components/ui/StatusBadge'
import Pagination  from '@/components/ui/Pagination'
import SearchBar   from '@/components/ui/SearchBar'
import RowActions  from '@/components/ui/RowActions'
import Icon        from '@/components/ui/Icon'
import MailViewModal from '@/modals/crm/MailViewModal'
import CustomerListCardView from '@/pages/crm/customers/CustomerListCardView'

// ─── Status icon imports ───────────────────────────────────────────────────────

import clientIcon            from '@/assets/customer-status/client.svg?raw'
import negotiatingIcon       from '@/assets/customer-status/negotiating.svg?raw'
import awaitingQuotationIcon from '@/assets/customer-status/awating-quotation.svg?raw'
import awaitingConfirmIcon   from '@/assets/customer-status/awaiting-confirmation.svg?raw'
import quotationAcceptedIcon from '@/assets/customer-status/quotation-accepted.svg?raw'
import startedMfgIcon        from '@/assets/customer-status/started-mfg.svg?raw'
import awaitingPaymentIcon   from '@/assets/customer-status/awaiting-payment.svg?raw'
import shippedIcon           from '@/assets/customer-status/shipped.svg?raw'
import inactiveIcon          from '@/assets/customer-status/inactive.svg?raw'
import declinedIcon          from '@/assets/customer-status/declined.svg?raw'
import churnedIcon           from '@/assets/customer-status/churned.svg?raw'
import orderIcon             from '@/assets/customer-status/order.svg?raw'
import exclamationIcon       from '@/assets/customer-status/exclamation.svg?raw'
import wrenchIcon            from '@/assets/customer-status/wrench.svg?raw'

// ─── Constants ────────────────────────────────────────────────────────────────

const PREFS_KEY = 'crm_customers'

const REL_STATUS_VARIANT = {
  lead:     'neutral',
  prospect: 'info',
  active:   'success',
  inactive: 'neutral',
  churned:  'danger',
}

const REL_STATUS_LABELS = {
  lead:     'Lead',
  prospect: 'Prospect',
  active:   'Active',
  inactive: 'Archived',
  churned:  'Went Cold',
}

// Active sub-statuses that come from the active order's status
const ACTIVE_SUB_OPTIONS = [
  { value: 'active__negotiating',                    label: 'Negotiating' },
  { value: 'active__awaiting_quotation',             label: 'Awaiting Quotation' },
  { value: 'active__awaiting_customer_confirmation', label: 'Awaiting Confirmation' },
  { value: 'active__awaiting_fulfilment',            label: 'Accepted - Waiting' },
  { value: 'active__awaiting_payment',               label: 'Awaiting Payment' },
  { value: 'active__manufacturing',                  label: 'Manufacturing' },
  { value: 'active__shipped',                        label: 'Shipped' },
  { value: 'active__installed',                      label: 'Installed' },
]

const TOP_STATUS_OPTIONS = [
  { value: 'lead',     label: 'Lead' },
  { value: 'prospect', label: 'Prospect' },
  { value: 'active',   label: 'Active' },
  { value: 'inactive', label: 'Archived' },
  { value: 'churned',  label: 'Went Cold' },
]

// Columns that are always visible (cannot be toggled off)
const ALL_COLUMNS_DEF = [
  { key: 'name',         label: 'Name',         alwaysOn: true  },
  { key: 'status',       label: 'Status'                        },
  { key: 'rel_status',   label: 'Relationship'                  },
  { key: 'support',      label: 'Support'                       },
  { key: 'organization', label: 'Organization'                  },
  { key: 'location',     label: 'Location'                      },
  { key: 'email',        label: 'Email'                         },
  { key: 'phone',        label: 'Phone'                         },
  { key: 'latest_comm',  label: 'Latest Comm'                   },
  { key: 'tags',         label: 'Tags'                          },
  { key: 'religion',     label: 'Religion'                      },
  { key: 'language',     label: 'Language'                      },
]

const DEFAULT_VISIBLE = ['name', 'status', 'rel_status', 'support', 'organization', 'location', 'email', 'phone', 'latest_comm', 'tags']

// 4-way sort modes for the Name column
// Each mode: { key, dir, label } — label shown in the column header
const NAME_SORT_MODES = [
  { sortKey: 'name',    sortDir: 'asc',  label: 'name ↑'  },
  { sortKey: 'name',    sortDir: 'desc', label: 'name ↓'  },
  { sortKey: 'surname', sortDir: 'asc',  label: 'surn ↑'  },
  { sortKey: 'surname', sortDir: 'desc', label: 'surn ↓'  },
]

const ORDER_STATUS_LABELS = {
  negotiating:                    'Negotiating',
  awaiting_quotation:             'Awaiting Quotation',
  awaiting_customer_confirmation: 'Awaiting Confirmation',
  awaiting_fulfilment:            'Accepted - Waiting',
  awaiting_payment:               'Awaiting Payment',
  manufacturing:                  'Manufacturing',
  shipped:                        'Shipped',
  installed:                      'Installed',
  declined:                       'Declined',
  complete:                       'Complete',
}

// Comm type display metadata
const COMM_TYPE_META = {
  email:     { label: 'email',     color: 'var(--color-info)',       bg: 'var(--color-info-bg)' },
  whatsapp:  { label: 'whatsapp',  color: 'var(--color-success)',    bg: 'var(--color-success-bg)' },
  call:      { label: 'call',      color: 'var(--color-warning)',    bg: 'var(--color-warning-bg)' },
  sms:       { label: 'sms',       color: 'var(--color-warning)',    bg: 'var(--color-warning-bg)' },
  note:      { label: 'note',      color: 'var(--color-text-muted)', bg: 'var(--color-bg-elevated)' },
  in_person: { label: 'in person', color: 'var(--color-primary)',    bg: 'var(--color-primary-subtle)' },
}

// ─── Language map ─────────────────────────────────────────────────────────────

const LANGUAGE_NAMES = {
  el: 'Greek', en: 'English', de: 'German', fr: 'French',
  it: 'Italian', ru: 'Russian', ar: 'Arabic', tr: 'Turkish',
  es: 'Spanish', pt: 'Portuguese', sr: 'Serbian', uk: 'Ukrainian',
}

function resolveLanguage(val) {
  if (!val) return '—'
  return LANGUAGE_NAMES[val.trim().toLowerCase()] || val
}

// ─── Status icon renderer ─────────────────────────────────────────────────────

const STATUS_ICON_SIZE = 22

function renderMaskedIcon(icon, color, title, size = STATUS_ICON_SIZE) {
  const svgMarkup = icon
    .replace(/<\?xml[\s\S]*?\?>/gi, '')
    .replace(/<!DOCTYPE[\s\S]*?>/gi, '')
    .replace(/<!--[\s\S]*?-->/g, '')
    .replace(
      /<svg\b([^>]*)>/i,
      `<svg$1 width="${size}" height="${size}" aria-label="${title}" role="img" focusable="false" style="display:block;width:${size}px;height:${size}px;color:${color};fill:currentColor;">`,
    )
  return (
    <span
      title={title}
      aria-label={title}
      role="img"
      style={{
        width: size, height: size,
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
        flexShrink: 0, lineHeight: 0,
      }}
      dangerouslySetInnerHTML={{ __html: svgMarkup }}
    />
  )
}

function resolveStatusIcon(customer) {
  const status  = customer.relationship_status || 'lead'
  const summary = customer.crm_summary || {}
  const allOrders = summary.all_orders_statuses || []

  if (status === 'churned') return { icon: churnedIcon,  color: 'var(--color-text-muted)', title: 'Went Cold' }
  if (status === 'lead')    return { icon: clientIcon,   color: 'var(--color-text-muted)', title: 'Lead' }
  if (status === 'prospect')return { icon: clientIcon,   color: 'var(--color-info)',        title: 'Prospect' }
  if (status === 'inactive')return { icon: inactiveIcon, color: 'var(--color-text-muted)', title: 'Archived' }

  if (status === 'active') {
    const activeOrderStatus = summary.active_order_status
    const orderIconMap = {
      negotiating:                    { icon: negotiatingIcon,       color: 'var(--color-text-secondary)', title: 'Negotiating' },
      awaiting_quotation:             { icon: awaitingQuotationIcon, color: 'var(--color-text-secondary)', title: 'Awaiting Quotation' },
      awaiting_customer_confirmation: { icon: awaitingConfirmIcon,   color: 'var(--color-text-secondary)', title: 'Awaiting Confirmation' },
      awaiting_fulfilment:            { icon: quotationAcceptedIcon, color: 'var(--color-info)',            title: 'Accepted - Waiting' },
      awaiting_payment:               { icon: awaitingPaymentIcon,   color: 'var(--color-warning)',         title: 'Awaiting Payment' },
      manufacturing:                  { icon: startedMfgIcon,        color: 'var(--color-success)',         title: 'Manufacturing' },
      shipped:                        { icon: shippedIcon,           color: 'var(--color-success)',         title: 'Shipped' },
      installed:                      { icon: inactiveIcon,          color: 'var(--color-success)',         title: 'Installed' },
    }
    if (activeOrderStatus && orderIconMap[activeOrderStatus]) return orderIconMap[activeOrderStatus]
    const allDeclined = allOrders.length > 0 && allOrders.every((s) => s === 'declined')
    if (allDeclined) return { icon: declinedIcon, color: 'var(--color-danger)', title: 'All orders declined' }
    const allComplete = allOrders.length > 0 && allOrders.every((s) => s === 'complete')
    if (allComplete) return { icon: inactiveIcon, color: 'var(--color-success)', title: 'All orders complete' }
    return { icon: inactiveIcon, color: 'var(--color-info)', title: 'Active, no orders' }
  }

  return { icon: clientIcon, color: 'var(--color-text-muted)', title: status }
}

// ─── Helpers ──────────────────────────────────────────────────────────────────

function primaryContact(customer, type) {
  const contacts = customer.contacts || []
  const primary  = contacts.find((c) => c.type === type && c.primary)
  return primary?.value || contacts.find((c) => c.type === type)?.value || null
}

function Muted({ children }) {
  return <span style={{ color: 'var(--color-text-muted)' }}>{children}</span>
}

function formatRelative(val) {
  if (!val) return ''
  const diff = Date.now() - new Date(val).getTime()
  if (diff < 0) return 'just now'
  const s = Math.floor(diff / 1000)
  if (s < 60) return 'just now'
  const m = Math.floor(s / 60)
  if (m < 60) return `${m}m ago`
  const h = Math.floor(m / 60)
  if (h < 24) return `${h}h ago`
  const d = Math.floor(h / 24)
  if (d === 1) return 'yesterday'
  if (d < 7) return `${d}d ago`
  const w = Math.floor(d / 7)
  if (w < 5) return `${w}w ago`
  const mo = Math.floor(d / 30)
  if (mo < 12) return `${mo}mo ago`
  return `${Math.floor(d / 365)}y ago`
}

// ─── Status filter modal ──────────────────────────────────────────────────────

function StatusFilterModal({ open, anchorRect, selectedValues, onChange, onClose }) {
  const ref = useRef(null)

  useEffect(() => {
    if (!open) return
    function onDown(e) {
      if (ref.current && !ref.current.contains(e.target)) onClose()
    }
    document.addEventListener('mousedown', onDown)
    return () => document.removeEventListener('mousedown', onDown)
  }, [open, onClose])

  if (!open || !anchorRect) return null

  const style = {
    position:        'fixed',
    top:             anchorRect.bottom + 4,
    left:            anchorRect.left,
    zIndex:          9999,
    minWidth:        220,
    backgroundColor: 'var(--color-bg-elevated)',
    border:          '1px solid var(--color-border-strong)',
    borderRadius:    'var(--radius-lg)',
    boxShadow:       'var(--shadow-lg)',
    padding:         'var(--space-2) 0',
  }

  const selected = new Set(selectedValues)

  // Derive states
  const activeSubValues = ACTIVE_SUB_OPTIONS.map((o) => o.value)
  const activeSubSelected = activeSubValues.filter((v) => selected.has(v))
  const activeTopSelected = selected.has('active')

  function toggleTop(value) {
    const next = new Set(selected)
    if (value === 'active') {
      // Uncheck if fully selected (parent checked + all subs checked, or just parent checked)
      const fullyChecked = activeParentChecked && !activeParentIndeterminate
      if (fullyChecked) {
        next.delete('active')
        activeSubValues.forEach((v) => next.delete(v))
      } else {
        // Partially checked or unchecked → check everything
        next.add('active')
        activeSubValues.forEach((v) => next.add(v))
      }
    } else {
      if (next.has(value)) next.delete(value)
      else next.add(value)
    }
    onChange([...next])
  }

  function toggleSub(value) {
    const next = new Set(selected)
    if (next.has(value)) {
      next.delete(value)
      // If not all subs checked, remove the parent "active" check
      const remaining = activeSubValues.filter((v) => next.has(v))
      if (remaining.length < activeSubValues.length) next.delete('active')
    } else {
      next.add(value)
      // If all subs now checked, auto-check parent
      const remaining = activeSubValues.filter((v) => next.has(v))
      if (remaining.length === activeSubValues.length) next.add('active')
    }
    onChange([...next])
  }

  const activeParentChecked = activeTopSelected || activeSubSelected.length === activeSubValues.length
  const activeParentIndeterminate = !activeParentChecked && activeSubSelected.length > 0

  const itemStyle = {
    display:     'flex',
    alignItems:  'center',
    gap:         'var(--space-2)',
    padding:     'var(--space-2) var(--space-3)',
    cursor:      'pointer',
    fontSize:    'var(--font-size-sm)',
    color:       'var(--color-text-primary)',
    userSelect:  'none',
    lineHeight:  1.4,
  }
  const subItemStyle = {
    ...itemStyle,
    paddingLeft: 'var(--space-8)',
    color:       'var(--color-text-secondary)',
    fontSize:    'var(--font-size-xs)',
  }

  return createPortal(
    <div ref={ref} style={style} role="listbox" aria-multiselectable="true" aria-label="Filter by status">

      {/* Clear filter */}
      {selected.size > 0 && (
        <>
          <button
            style={{ ...itemStyle, color: 'var(--color-text-muted)', width: '100%', background: 'none', border: 'none' }}
            onClick={() => onChange([])}
          >
            <Icon name="close" size={12} color="var(--color-text-muted)" />
            Clear filter
          </button>
          <div style={{ height: 1, backgroundColor: 'var(--color-border)', margin: 'var(--space-1) 0' }} />
        </>
      )}

      {TOP_STATUS_OPTIONS.map((opt) => {
        if (opt.value === 'active') {
          return (
            <div key="active-group">
              {/* Active parent row */}
              <button
                style={{ ...itemStyle, width: '100%', background: 'none', border: 'none' }}
                onClick={() => toggleTop('active')}
              >
                <IndeterminateCheckbox
                  checked={activeParentChecked}
                  indeterminate={activeParentIndeterminate}
                />
                <StatusBadge variant="success" size="sm">Active</StatusBadge>
              </button>

              {/* Active sub-options */}
              {ACTIVE_SUB_OPTIONS.map((sub) => (
                <button
                  key={sub.value}
                  style={{ ...subItemStyle, width: '100%', background: 'none', border: 'none' }}
                  onClick={() => toggleSub(sub.value)}
                >
                  <input
                    type="checkbox"
                    checked={selected.has(sub.value)}
                    onChange={() => {}}
                    style={{ accentColor: 'var(--color-primary)', flexShrink: 0, cursor: 'pointer' }}
                  />
                  {sub.label}
                </button>
              ))}
            </div>
          )
        }

        const checked = selected.has(opt.value)
        return (
          <button
            key={opt.value}
            style={{ ...itemStyle, width: '100%', background: 'none', border: 'none' }}
            onClick={() => toggleTop(opt.value)}
          >
            <input
              type="checkbox"
              checked={checked}
              onChange={() => {}}
              style={{ accentColor: 'var(--color-primary)', flexShrink: 0, cursor: 'pointer' }}
            />
            <StatusBadge variant={REL_STATUS_VARIANT[opt.value] ?? 'neutral'} size="sm">
              {opt.label}
            </StatusBadge>
          </button>
        )
      })}
    </div>,
    document.body
  )
}

// Renders a checkbox that supports an indeterminate state
function IndeterminateCheckbox({ checked, indeterminate }) {
  const ref = useRef(null)
  useEffect(() => {
    if (ref.current) ref.current.indeterminate = indeterminate
  }, [indeterminate])
  return (
    <input
      ref={ref}
      type="checkbox"
      checked={checked}
      onChange={() => {}}
      style={{ accentColor: 'var(--color-primary)', flexShrink: 0, cursor: 'pointer' }}
    />
  )
}

// ─── CustomerList ─────────────────────────────────────────────────────────────

export default function CustomerList() {
  const navigate = useNavigate()
  const { hasPermission } = useAuth()
  const canAdd  = hasPermission('crm_customers', 'add')
  const canEdit = hasPermission('crm_customers', 'edit')

  const { prefs, setPref, setPrefBatch, ready } = usePreferences(PREFS_KEY)

  // Data
  const [customers,   setCustomers]   = useState([])
  const [loading,     setLoading]     = useState(true)  // true only on first load (no data yet)
  const [error,       setError]       = useState('')
  // Latest comm per customer: { [customerId]: { id, type, occurred_at } }
  const [latestComms, setLatestComms] = useState({})

  // Filters — initialised from prefs once ready
  const [search,        setSearch]        = useState('')
  const [statusFilters, setStatusFilters] = useState([])   // array of selected values

  // Sort — 4-way name/surname cycling; also sortable by latest_comm and organization
  // nameSortIdx: 0–3 cycles through NAME_SORT_MODES; -1 means a non-name sort is active
  const [nameSortIdx,  setNameSortIdx]  = useState(0)   // default: name asc
  const [sortKey,      setSortKey]      = useState('name')
  const [sortDir,      setSortDir]      = useState('asc')

  // Pagination
  const [page,     setPage]     = useState(1)
  const [pageSize, setPageSize] = useState(25)

  // Column visibility + order
  const [visibleKeys, setVisibleKeys] = useState(DEFAULT_VISIBLE)

  // View mode: 'quick' | 'expanded' | 'card'
  const [viewMode, setViewMode] = useState('quick')

  // Show titles (Mr., Fr., etc.) in the Name column
  const [showTitles, setShowTitles] = useState(true)

  // Comm preview modal
  const [viewComm, setViewComm] = useState(null)

  // Status filter popover
  const filterBtnRef = useRef(null)
  const [filterOpen,  setFilterOpen]  = useState(false)
  const [filterAnchor, setFilterAnchor] = useState(null)

  // Settings cog popover
  const cogBtnRef = useRef(null)
  const [cogOpen,    setCogOpen]    = useState(false)
  const [cogAnchor,  setCogAnchor]  = useState(null)

  // ── Load prefs once ready ─────────────────────────────────────────────────
  useEffect(() => {
    if (!ready) return
    if (prefs.status_filters !== undefined) setStatusFilters(prefs.status_filters)
    if (prefs.sort_key  !== undefined) {
      setSortKey(prefs.sort_key)
      setSortDir(prefs.sort_dir ?? 'asc')
      // Restore nameSortIdx if it was a name/surname sort
      const idx = NAME_SORT_MODES.findIndex(
        (m) => m.sortKey === prefs.sort_key && m.sortDir === (prefs.sort_dir ?? 'asc')
      )
      setNameSortIdx(idx !== -1 ? idx : -1)
    }
    if (prefs.page_size !== undefined)  setPageSize(prefs.page_size)
    if (prefs.columns   !== undefined)  setVisibleKeys(prefs.columns)
    if (prefs.view_mode    !== undefined) setViewMode(prefs.view_mode)
    if (prefs.show_titles  !== undefined) setShowTitles(prefs.show_titles)
  }, [ready]) // eslint-disable-line react-hooks/exhaustive-deps

  // ── Fetch ─────────────────────────────────────────────────────────────────
  const fetchCustomers = useCallback(async () => {
    // First load (no data yet): show skeleton. Subsequent fetches: keep old data visible.
    const isFirstLoad = customers.length === 0 && !error
    if (isFirstLoad) setLoading(true)
    setError('')
    try {
      const params = new URLSearchParams()
      if (search) params.set('search', search)
      const qs   = params.toString()
      const data = await api.get(`/crm/customers${qs ? `?${qs}` : ''}`)
      const list = data.customers ?? []
      setCustomers(list)

      // Fetch latest comm for every customer in one batch request
      if (list.length > 0) {
        const ids = list.map((c) => c.id).join(',')
        api.get(`/crm/comms/latest-batch?ids=${encodeURIComponent(ids)}`)
          .then((batch) => setLatestComms(batch || {}))
          .catch(() => {}) // non-critical — column just shows dashes
      } else {
        setLatestComms({})
      }
    } catch (err) {
      setError(err.message || 'Failed to load customers.')
    } finally {
      setLoading(false)
    }
  }, [search]) // eslint-disable-line react-hooks/exhaustive-deps

  useEffect(() => { fetchCustomers() }, [fetchCustomers])
  useEffect(() => { setPage(1) }, [search, statusFilters])

  // ── Column management ─────────────────────────────────────────────────────
  const handleColumnChange = (keys) => {
    setVisibleKeys(keys)
    setPref('columns', keys)
  }

  // ── Sort helpers ──────────────────────────────────────────────────────────

  // Called when the Name column header is clicked — cycles through 4 modes
  function cycleNameSort() {
    const nextIdx = (nameSortIdx + 1) % NAME_SORT_MODES.length
    const mode = NAME_SORT_MODES[nextIdx]
    setNameSortIdx(nextIdx)
    setSortKey(mode.sortKey)
    setSortDir(mode.sortDir)
    setPrefBatch({ sort_key: mode.sortKey, sort_dir: mode.sortDir })
    setPage(1)
  }

  // Called by DataTable's onSort for other sortable columns
  function handleSort(key, dir) {
    setSortKey(key)
    setSortDir(dir)
    setNameSortIdx(-1) // deactivate name cycling
    setPrefBatch({ sort_key: key, sort_dir: dir })
    setPage(1)
  }

  // ── Client-side filter ────────────────────────────────────────────────────

  function customerMatchesFilter(c) {
    if (statusFilters.length === 0) return true
    const rel = c.relationship_status || 'lead'
    const summary = c.crm_summary || {}
    const activeOrderStatus = summary.active_order_status

    return statusFilters.some((f) => {
      if (!f.startsWith('active__')) return f === rel
      // sub-filter: e.g. active__manufacturing
      const subStatus = f.replace('active__', '')
      return rel === 'active' && activeOrderStatus === subStatus
    })
  }

  const filtered = customers.filter(customerMatchesFilter)

  // ── Sort ──────────────────────────────────────────────────────────────────

  function getSortValue(c, key) {
    switch (key) {
      case 'name':          return (c.name    || '').toLowerCase()
      case 'surname':       return (c.surname || '').toLowerCase()
      case 'organization':  return (c.organization || '').toLowerCase()
      case 'latest_comm': {
        const ts = latestComms[c.id]?.occurred_at
        return ts ? new Date(ts).getTime() : 0
      }
      default: return 0
    }
  }

  const sorted = sortKey
    ? [...filtered].sort((a, b) => {
        const va = getSortValue(a, sortKey)
        const vb = getSortValue(b, sortKey)
        if (va < vb) return sortDir === 'asc' ? -1 : 1
        if (va > vb) return sortDir === 'asc' ?  1 : -1
        return 0
      })
    : filtered

  const total = sorted.length
  const paged = sorted.slice((page - 1) * pageSize, page * pageSize)

  // ── Name column label ─────────────────────────────────────────────────────
  // Shows a small indicator when a name/surname sort is active
  const nameSortMode = nameSortIdx >= 0 ? NAME_SORT_MODES[nameSortIdx] : null

  // ── Column definitions ────────────────────────────────────────────────────

  const allColumnDefs = [
    ...ALL_COLUMNS_DEF.map((col) => {
      switch (col.key) {

        case 'name':
          return {
            ...col,
            // Custom label with sort indicator
            label: nameSortMode ? (
              <span>
                Name{' '}
                <span style={{
                  color:      'var(--color-text-muted)',
                  fontSize:   'var(--font-size-xs)',
                  fontWeight: 'var(--font-weight-normal)',
                }}>
                  ({nameSortMode.label})
                </span>
              </span>
            ) : 'Name',
            sortable: false, // handled manually via cycleNameSort
            render: (c) => {
              const parts = showTitles ? [c.title, c.name, c.surname] : [c.name, c.surname]
              return (
                <span style={{ fontWeight: 'var(--font-weight-medium)', color: 'var(--color-text-primary)' }}>
                  {parts.filter(Boolean).join(' ') || <Muted>Unnamed</Muted>}
                </span>
              )
            },
          }

        case 'status':
          return {
            ...col,
            width: '52px',
            render: (c) => {
              const { icon, color, title } = resolveStatusIcon(c)
              return (
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                  {renderMaskedIcon(icon, color, title)}
                </div>
              )
            },
          }

        case 'rel_status':
          return {
            ...col,
            render: (c) => {
              const rel = c.relationship_status || 'lead'
              return (
                <StatusBadge variant={REL_STATUS_VARIANT[rel] ?? 'neutral'} size="sm">
                  {REL_STATUS_LABELS[rel] ?? rel}
                </StatusBadge>
              )
            },
          }

        case 'support':
          return {
            ...col,
            width: '80px',
            render: (c) => {
              const summary    = c.crm_summary || {}
              const hasIssue   = (summary.active_issues_count  || 0) > 0
              const hasSupport = (summary.active_support_count || 0) > 0
              if (!hasIssue && !hasSupport) return null
              return (
                <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-1)' }}>
                  {hasIssue && (
                    <span title={`${summary.active_issues_count} active issue(s)`}
                      style={{ display: 'inline-flex', alignItems: 'center', gap: 'var(--space-1)' }}>
                      <span style={{ width: 8, height: 8, borderRadius: '50%', backgroundColor: 'var(--color-danger)', display: 'inline-block', flexShrink: 0 }} />
                      <span style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-danger)', fontWeight: 'var(--font-weight-semibold)' }}>
                        {summary.active_issues_count}
                      </span>
                    </span>
                  )}
                  {hasSupport && (
                    <span title={`${summary.active_support_count} active support item(s)`}
                      style={{ display: 'inline-flex', alignItems: 'center', gap: 'var(--space-1)' }}>
                      <span style={{ width: 8, height: 8, borderRadius: '50%', backgroundColor: 'var(--color-warning)', display: 'inline-block', flexShrink: 0 }} />
                      <span style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-warning)', fontWeight: 'var(--font-weight-semibold)' }}>
                        {summary.active_support_count}
                      </span>
                    </span>
                  )}
                </div>
              )
            },
          }

        case 'organization':
          return {
            ...col,
            sortable: true,
            render: (c) => c.organization
              ? <span style={{ color: 'var(--color-text-primary)' }}>{c.organization}</span>
              : <Muted>—</Muted>,
          }

        case 'location':
          return {
            ...col,
            render: (c) => {
              const loc     = c.location || {}
              const country = (loc.country || '').trim()
              const city    = (loc.city || '').trim()
              // Omit country when it's Greece (either English or Greek name)
              const isGreece = /^(greece|ελλάδα|ellada)$/i.test(country)
              const display  = isGreece
                ? city
                : [city, country].filter(Boolean).join(', ')
              return display ? (
                <span style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)', whiteSpace: 'nowrap' }}>
                  {display}
                </span>
              ) : <Muted>—</Muted>
            },
          }

        case 'email':
          return {
            ...col,
            render: (c) => {
              const email = primaryContact(c, 'email')
              return email
                ? <span style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)' }}>{email}</span>
                : <Muted>—</Muted>
            },
          }

        case 'phone':
          return {
            ...col,
            render: (c) => {
              const phone = primaryContact(c, 'phone')
              return phone
                ? <span style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)', whiteSpace: 'nowrap' }}>{phone}</span>
                : <Muted>—</Muted>
            },
          }

        case 'latest_comm':
          return {
            ...col,
            sortable: true,
            render: (c) => {
              const lc     = latestComms[c.id]
              const type   = lc?.type
              const date   = lc?.occurred_at
              const commId = lc?.id
              if (!type || !date) return <Muted>—</Muted>
              const meta = COMM_TYPE_META[type] || COMM_TYPE_META.note
              return (
                <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)', flexWrap: 'nowrap' }}>
                  <button
                    onClick={(e) => {
                      e.stopPropagation()
                      // Build a minimal comm entry object for MailViewModal
                      if (commId) {
                        setViewComm({ id: commId, type, date })
                      }
                    }}
                    title={`View ${meta.label} communication`}
                    style={{
                      display:         'inline-flex',
                      alignItems:      'center',
                      gap:             'var(--space-1)',
                      padding:         '2px var(--space-2)',
                      borderRadius:    'var(--radius-sm)',
                      backgroundColor: meta.bg,
                      color:           meta.color,
                      fontSize:        'var(--font-size-xs)',
                      fontWeight:      'var(--font-weight-medium)',
                      border:          'none',
                      cursor:          commId ? 'pointer' : 'default',
                      flexShrink:      0,
                      lineHeight:      1.4,
                    }}
                  >
                    {meta.label}
                  </button>
                  <span style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', whiteSpace: 'nowrap' }}>
                    {formatRelative(date)}
                  </span>
                </div>
              )
            },
          }

        case 'tags':
          return {
            ...col,
            render: (c) => {
              const tags = c.tags || []
              if (!tags.length) return <Muted>—</Muted>
              return (
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--space-1)' }}>
                  {tags.slice(0, 3).map((tag) => (
                    <span
                      key={tag}
                      style={{
                        padding:         '2px var(--space-2)',
                        fontSize:        'var(--font-size-xs)',
                        borderRadius:    'var(--radius-full)',
                        backgroundColor: 'var(--color-bg-elevated)',
                        color:           'var(--color-text-secondary)',
                        border:          '1px solid var(--color-border)',
                      }}
                    >
                      {tag}
                    </span>
                  ))}
                  {tags.length > 3 && (
                    <span style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)' }}>
                      +{tags.length - 3}
                    </span>
                  )}
                </div>
              )
            },
          }

        case 'religion':
          return {
            ...col,
            render: (c) => c.religion
              ? <span style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)' }}>{c.religion}</span>
              : <Muted>—</Muted>,
          }

        case 'language':
          return {
            ...col,
            render: (c) => (
              <span style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)' }}>
                {resolveLanguage(c.language)}
              </span>
            ),
          }

        default:
          return col
      }
    }),

    // Actions column — always last, always rendered
    {
      key: '__actions',
      label: '',
      width: '110px',
      alwaysOn: true,
      render: (c) => (
        <div onClick={(e) => e.stopPropagation()} style={{ display: 'flex', justifyContent: 'flex-end' }}>
          <RowActions
            actions={[
              {
                label: 'View',
                icon: <Icon name="eye" size={14} />,
                onClick: () => navigate(`/crm/customers/${c.id}`),
              },
              ...(canEdit ? [{
                label: 'Edit',
                icon: <Icon name="edit" size={14} />,
                onClick: () => navigate(`/crm/customers/${c.id}/edit`),
              }] : []),
            ]}
          />
        </div>
      ),
    },
  ]

  // Name column header click handler — injected via a wrapper
  // We intercept th clicks for the 'name' column in the DataTable via a modified column def
  const nameColDef = allColumnDefs.find((c) => c.key === 'name')
  if (nameColDef) {
    nameColDef.onHeaderClick = cycleNameSort
    nameColDef.sortable = false // DataTable won't handle it, we do it manually
  }

  const tableColumns  = allColumnDefs.filter((col) => {
    if (col.key === 'support' && viewMode === 'expanded') return false
    return col.alwaysOn || visibleKeys.includes(col.key)
  })
  const pickerColumns = allColumnDefs.filter((col) => col.key !== '__actions')

  // ── Expanded row sub-content ──────────────────────────────────────────────
  function renderSubRows(c) {
    const summary           = c.crm_summary || {}
    const activeOrderStatus = summary.active_order_status
    const activeOrderNumber = summary.active_order_number
    const activeOrderTitle  = summary.active_order_title
    const issueCount        = summary.active_issues_count  || 0
    const supportCount      = summary.active_support_count || 0
    if (!activeOrderStatus && issueCount === 0 && supportCount === 0) return null
    const { color: statusColor } = resolveStatusIcon(c)
    const rows = []
    if (activeOrderStatus) {
      const label = ORDER_STATUS_LABELS[activeOrderStatus] || activeOrderStatus
      rows.push(
        <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)', whiteSpace: 'nowrap' }}>
          {renderMaskedIcon(orderIcon, statusColor, label, 13)}
          <span style={{ fontSize: 'var(--font-size-xs)', color: statusColor, fontWeight: 'var(--font-weight-medium)' }}>
            {['Order', activeOrderNumber, label].filter(Boolean).join(' · ')}
            {activeOrderTitle && (
              <span style={{ color: 'var(--color-text-muted)', fontWeight: 'var(--font-weight-normal)' }}> · {activeOrderTitle}</span>
            )}
          </span>
        </div>
      )
    }
    if (issueCount > 0) {
      rows.push(
        <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)', whiteSpace: 'nowrap' }}>
          {renderMaskedIcon(exclamationIcon, 'var(--color-danger)', 'Issue', 13)}
          <span style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-danger)', fontWeight: 'var(--font-weight-medium)' }}>
            {issueCount} active technical issue{issueCount > 1 ? 's' : ''}
          </span>
        </div>
      )
    }
    if (supportCount > 0) {
      rows.push(
        <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)', whiteSpace: 'nowrap' }}>
          {renderMaskedIcon(wrenchIcon, 'var(--color-warning)', 'Support', 13)}
          <span style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-warning)', fontWeight: 'var(--font-weight-medium)' }}>
            {supportCount} active support ticket{supportCount > 1 ? 's' : ''}
          </span>
        </div>
      )
    }
    return rows
  }

  // ── Status filter badge label ─────────────────────────────────────────────
  function filterBadgeLabel() {
    if (statusFilters.length === 0) return 'All Statuses'
    if (statusFilters.length === 1) {
      const v = statusFilters[0]
      if (v.startsWith('active__')) {
        const sub = ACTIVE_SUB_OPTIONS.find((o) => o.value === v)
        return sub ? `Active: ${sub.label}` : v
      }
      return REL_STATUS_LABELS[v] ?? v
    }
    return `${statusFilters.length} statuses`
  }

  // ─────────────────────────────────────────────────────────────────────────

  // Keep showing stale count while a search is in flight — no jank
  const countLabel = loading
    ? '\u00a0'  // non-breaking space — holds line height on very first load
    : filtered.length === 0
      ? 'No customers found'
      : `${filtered.length} ${filtered.length === 1 ? 'customer' : 'customers'}`

  if (viewMode === 'card') {
    return (
      <div className="page-wrapper">
        <PageHeader
          title="Customers"
          subtitle={countLabel}
        >
          {canAdd && (
            <Button variant="primary" size="md" onClick={() => navigate('/crm/customers/new')}>
              New Customer
            </Button>
          )}
        </PageHeader>

        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--space-3)', alignItems: 'center', minWidth: 0 }}>
          <div style={{ flex: '1 1 0', minWidth: '200px' }}>
            <SearchBar value={search} onChange={setSearch} placeholder="Search by name, organization…" />
          </div>

          {/* Status filter button */}
          <Button
            ref={filterBtnRef}
            variant="secondary"
            size="md"
            onClick={() => {
              if (filterBtnRef.current) {
                setFilterAnchor(filterBtnRef.current.getBoundingClientRect())
              }
              setFilterOpen((v) => !v)
            }}
            style={{ flexShrink: 0, position: 'relative', padding: 'var(--space-3) var(--space-4)', lineHeight: 'var(--line-height-base)' }}
          >
            {filterBadgeLabel()}
            {statusFilters.length > 0 && (
              <span style={{
                display:         'inline-flex',
                alignItems:      'center',
                justifyContent:  'center',
                width:           16,
                height:          16,
                borderRadius:    '50%',
                backgroundColor: 'var(--color-primary)',
                color:           'var(--color-text-inverse)',
                fontSize:        'var(--font-size-xs)',
                fontWeight:      'var(--font-weight-bold)',
                marginLeft:      'var(--space-1)',
              }}>
                {statusFilters.length}
              </span>
            )}
          </Button>

          <StatusFilterModal
            open={filterOpen}
            anchorRect={filterAnchor}
            selectedValues={statusFilters}
            onChange={(vals) => {
              setStatusFilters(vals)
              setPref('status_filters', vals)
            }}
            onClose={() => setFilterOpen(false)}
          />

          <ViewModeSelector value={viewMode} onChange={(v) => { setViewMode(v); setPref('view_mode', v) }} />

          {/* Settings cog */}
          <Button
            ref={cogBtnRef}
            variant="secondary"
            size="md"
            aria-label="List settings"
            onClick={() => {
              if (cogBtnRef.current) setCogAnchor(cogBtnRef.current.getBoundingClientRect())
              setCogOpen((v) => !v)
            }}
            style={{ flexShrink: 0, padding: 'var(--space-3)', lineHeight: 'var(--line-height-base)' }}
          >
            <Icon name="settings" size={16} />
          </Button>

          <SettingsCogMenu
            open={cogOpen}
            anchorRect={cogAnchor}
            showTitles={showTitles}
            onToggleTitles={() => {
              const next = !showTitles
              setShowTitles(next)
              setPref('show_titles', next)
            }}
            onClose={() => setCogOpen(false)}
          />
        </div>

        {!loading && total > 0 && (
          <Pagination
            page={page} pageSize={pageSize} total={total}
            onPageChange={setPage}
            onSizeChange={(s) => { setPageSize(s); setPage(1); setPref('page_size', s) }}
            pageSizes={[10, 25, 50, 100]}
          />
        )}

        <CustomerListCardView
          customers={paged}
          latestComms={latestComms}
          loading={loading}
          canEdit={canEdit}
          showTitles={showTitles}
          onNavigate={(c) => navigate(`/crm/customers/${c.id}`)}
          onEdit={(c) => navigate(`/crm/customers/${c.id}/edit`)}
        />
      </div>
    )
  }

  return (
    <div className="page-wrapper">

      <PageHeader
        title="Customers"
        subtitle={countLabel}
      >
        {canAdd && (
          <Button variant="primary" size="md" onClick={() => navigate('/crm/customers/new')}>
            New Customer
          </Button>
        )}
      </PageHeader>

      {/* Filter toolbar */}
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--space-3)', alignItems: 'center', minWidth: 0 }}>
        <div style={{ flex: '1 1 0', minWidth: '200px' }}>
          <SearchBar value={search} onChange={setSearch} placeholder="Search by name, organization…" />
        </div>

        {/* Status filter button */}
        <Button
          ref={filterBtnRef}
          variant="secondary"
          size="md"
          onClick={() => {
            if (filterBtnRef.current) {
              setFilterAnchor(filterBtnRef.current.getBoundingClientRect())
            }
            setFilterOpen((v) => !v)
          }}
          style={{ flexShrink: 0, position: 'relative', padding: 'var(--space-3) var(--space-4)', lineHeight: 'var(--line-height-base)' }}
        >
          {filterBadgeLabel()}
          {statusFilters.length > 0 && (
            <span style={{
              display:         'inline-flex',
              alignItems:      'center',
              justifyContent:  'center',
              width:           16,
              height:          16,
              borderRadius:    '50%',
              backgroundColor: 'var(--color-primary)',
              color:           'var(--color-text-inverse)',
              fontSize:        'var(--font-size-xs)',
              fontWeight:      'var(--font-weight-bold)',
              marginLeft:      'var(--space-1)',
            }}>
              {statusFilters.length}
            </span>
          )}
        </Button>

        <StatusFilterModal
          open={filterOpen}
          anchorRect={filterAnchor}
          selectedValues={statusFilters}
          onChange={(vals) => {
            setStatusFilters(vals)
            setPref('status_filters', vals)
          }}
          onClose={() => setFilterOpen(false)}
        />

        <ViewModeSelector
          value={viewMode}
          onChange={(v) => { setViewMode(v); setPref('view_mode', v) }}
        />

        {/* Settings cog */}
        <Button
          ref={cogBtnRef}
          variant="secondary"
          size="md"
          aria-label="List settings"
          onClick={() => {
            if (cogBtnRef.current) setCogAnchor(cogBtnRef.current.getBoundingClientRect())
            setCogOpen((v) => !v)
          }}
          style={{ flexShrink: 0, padding: 'var(--space-3)', lineHeight: 'var(--line-height-base)' }}
        >
          <Icon name="settings" size={16} />
        </Button>

        <SettingsCogMenu
          open={cogOpen}
          anchorRect={cogAnchor}
          showTitles={showTitles}
          onToggleTitles={() => {
            const next = !showTitles
            setShowTitles(next)
            setPref('show_titles', next)
          }}
          onClose={() => setCogOpen(false)}
        />
      </div>

      {/* Pagination — above the table */}
      {!loading && total > 0 && (
        <Pagination
          page={page}
          pageSize={pageSize}
          total={total}
          onPageChange={setPage}
          onSizeChange={(s) => { setPageSize(s); setPage(1); setPref('page_size', s) }}
          pageSizes={[10, 25, 50, 100]}
        />
      )}

      {/* Error banner */}
      {error && (
        <div
          role="alert"
          style={{
            backgroundColor: 'var(--color-danger-bg)',
            border:          '1px solid var(--color-danger)',
            borderRadius:    'var(--radius-md)',
            padding:         'var(--space-3) var(--space-4)',
            color:           'var(--color-danger)',
            fontSize:        'var(--font-size-sm)',
          }}
        >
          {error}
        </div>
      )}

      {/* Table */}
      <DataTable
        columns={tableColumns}
        data={paged}
        loading={loading}
        keyField="id"
        emptyMessage="No customers found"
        emptyDescription="Try adjusting your search or filters, or add a new customer."
        onRowClick={(c) => navigate(`/crm/customers/${c.id}`)}
        onRowMiddleClick={(c) => window.open(`/crm/customers/${c.id}`, '_blank', 'noopener,noreferrer')}
        skeletonRows={10}
        sortKey={sortKey}
        sortDir={sortDir}
        onSort={handleSort}
        onNameColHeaderClick={cycleNameSort}
        allColumns={pickerColumns}
        visibleKeys={visibleKeys}
        onColumnChange={handleColumnChange}
        renderSubRows={viewMode === 'expanded' ? renderSubRows : undefined}
        expandedRowPadding={true}
      />

      {/* Comm preview modal */}
      {viewComm && (
        <CommPreviewLoader
          commId={viewComm.id}
          onClose={() => setViewComm(null)}
        />
      )}

    </div>
  )
}

// ─── ViewModeSelector ─────────────────────────────────────────────────────────

function ViewModeSelector({ value, onChange }) {
  const options = [
    { value: 'quick',    label: 'Quick' },
    { value: 'expanded', label: 'Expanded' },
    { value: 'card',     label: 'Card View' },
  ]
  return (
    <div style={{
      display:         'inline-flex',
      alignItems:      'stretch',
      borderRadius:    'var(--radius-md)',
      border:          '1px solid var(--color-border-strong)',
      overflow:        'hidden',
      flexShrink:      0,
    }}>
      {options.map((opt, i) => (
        <button
          key={opt.value}
          onClick={() => onChange(opt.value)}
          style={{
            padding:         'var(--space-3) var(--space-4)',
            fontSize:        'var(--font-size-base)',
            fontWeight:      value === opt.value ? 'var(--font-weight-semibold)' : 'var(--font-weight-normal)',
            color:           value === opt.value ? 'var(--color-text-primary)' : 'var(--color-text-muted)',
            backgroundColor: value === opt.value ? 'var(--color-bg-island)' : 'transparent',
            border:          'none',
            borderRight:     i < options.length - 1 ? '1px solid var(--color-border-strong)' : 'none',
            cursor:          'pointer',
            transition:      'background-color 0.15s, color 0.15s',
            whiteSpace:      'nowrap',
            lineHeight:      'var(--line-height-base)',
          }}
        >
          {opt.label}
        </button>
      ))}
    </div>
  )
}

// ─── SettingsCogMenu ──────────────────────────────────────────────────────────

function SettingsCogMenu({ open, anchorRect, showTitles, onToggleTitles, onClose }) {
  const ref = useRef(null)

  useEffect(() => {
    if (!open) return
    function onDown(e) {
      if (ref.current && !ref.current.contains(e.target)) onClose()
    }
    document.addEventListener('mousedown', onDown)
    return () => document.removeEventListener('mousedown', onDown)
  }, [open, onClose])

  if (!open || !anchorRect) return null

  return createPortal(
    <div
      ref={ref}
      style={{
        position:        'fixed',
        top:             anchorRect.bottom + 4,
        right:           window.innerWidth - anchorRect.right,
        zIndex:          9999,
        minWidth:        200,
        backgroundColor: 'var(--color-bg-elevated)',
        border:          '1px solid var(--color-border-strong)',
        borderRadius:    'var(--radius-lg)',
        boxShadow:       'var(--shadow-lg)',
        padding:         'var(--space-2) 0',
      }}
    >
      <button
        onClick={onToggleTitles}
        style={{
          display:     'flex',
          alignItems:  'center',
          gap:         'var(--space-2)',
          padding:     'var(--space-2) var(--space-3)',
          width:       '100%',
          background:  'none',
          border:      'none',
          cursor:      'pointer',
          fontSize:    'var(--font-size-sm)',
          color:       'var(--color-text-primary)',
          userSelect:  'none',
        }}
      >
        <input
          type="checkbox"
          checked={showTitles}
          onChange={() => {}}
          style={{ accentColor: 'var(--color-primary)', flexShrink: 0, cursor: 'pointer' }}
        />
        Show Titles (Mr., Fr., etc.)
      </button>
    </div>,
    document.body
  )
}

// ─── CommPreviewLoader ────────────────────────────────────────────────────────
// Fetches the full comm entry then opens MailViewModal (which handles all types)

function CommPreviewLoader({ commId, onClose }) {
  const [entry, setEntry] = useState(null)

  useEffect(() => {
    api.get(`/crm/comms/${commId}`)
      .then(setEntry)
      .catch(() => onClose())
  }, [commId, onClose])

  if (!entry) return null

  return (
    <MailViewModal
      open={true}
      entry={entry}
      onClose={onClose}
      onReply={() => {}}
    />
  )
}
