<script setup lang="ts">
import type { AggregationFunction, CalculatedField, PivotGroupStart, PivotLayout, PivotResult, PivotValueField } from '@smallwebco/tinypivot-core'
import { computeRowSpans, getAggregationLabel, getAggregationSymbol } from '@smallwebco/tinypivot-core'
/**
 * Pivot Table Skeleton + Data Display
 * Visual layout for pivot configuration and results
 */
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useLicense } from '../composables/useLicense'

interface ActiveFilter {
  column: string
  valueCount: number
  values?: string[]
  displayText?: string
  isRange?: boolean
}

const props = defineProps<{
  rowFields: string[]
  columnFields: string[]
  valueFields: PivotValueField[]
  calculatedFields?: CalculatedField[]
  isConfigured: boolean
  draggingField: string | null
  pivotResult: PivotResult | null
  fontSize?: 'xs' | 'sm' | 'base'
  activeFilters?: ActiveFilter[] | null
  totalRowCount?: number
  filteredRowCount?: number
  theme?: string
  enableDrillDown?: boolean
  enableDrillThrough?: boolean
  pivotLayout?: PivotLayout
}>()

const emit = defineEmits<{
  (e: 'addRowField', field: string): void
  (e: 'removeRowField', field: string): void
  (e: 'addColumnField', field: string): void
  (e: 'removeColumnField', field: string): void
  (e: 'addValueField', field: string, aggregation: AggregationFunction): void
  (e: 'removeValueField', field: string, aggregation: AggregationFunction): void
  (e: 'updateAggregation', field: string, oldAgg: AggregationFunction, newAgg: AggregationFunction): void
  (e: 'reorderRowFields', fields: string[]): void
  (e: 'reorderColumnFields', fields: string[]): void
  (e: 'toggleCollapse', key: string, altKey: boolean): void
  (e: 'drillThroughCell', payload: { rowPath: string[], columnPath: string[], valueFieldIndex: number }): void
}>()

// Helper to get display name for value fields (resolves calc IDs to names)
function getValueFieldDisplayName(field: string): string {
  if (field.startsWith('calc:')) {
    const calcId = field.replace('calc:', '')
    const calcField = props.calculatedFields?.find(c => c.id === calcId)
    return calcField?.name || field
  }
  return field
}

// Helper to check if field is a calculated field
function isCalculatedField(field: string): boolean {
  return field.startsWith('calc:')
}

const { showWatermark, canUsePivot, isDemo } = useLicense()

// Drag state
const dragOverArea = ref<'row' | 'column' | 'value' | null>(null)

// Reorder drag state
const reorderDragSource = ref<{ zone: 'row' | 'column', index: number } | null>(null)
const reorderDropTarget = ref<{ zone: 'row' | 'column', index: number } | null>(null)

// Use getAggregationLabel and getAggregationSymbol from core

// Font size
const currentFontSize = ref(props.fontSize || 'xs')
const fontSizeOptions = [
  { value: 'xs', label: 'S' },
  { value: 'sm', label: 'M' },
  { value: 'base', label: 'L' },
] as const

// Filter status
const hasActiveFilters = computed(() => props.activeFilters && props.activeFilters.length > 0)
const filterSummary = computed(() => {
  if (!props.activeFilters || props.activeFilters.length === 0)
    return ''
  const columns = props.activeFilters.map(f => f.column).join(', ')
  return columns
})

// Detailed filter tooltip
const filterTooltipDetails = computed(() => {
  if (!props.activeFilters || props.activeFilters.length === 0)
    return []
  return props.activeFilters.map((f) => {
    // Handle range filters
    if (f.isRange && f.displayText) {
      return {
        column: f.column,
        displayText: f.displayText,
        isRange: true,
        values: [],
        remaining: 0,
      }
    }
    // Handle value filters
    const values = f.values || []
    const maxDisplay = 5
    const displayValues = values.slice(0, maxDisplay)
    const remaining = values.length - maxDisplay
    return {
      column: f.column,
      values: displayValues,
      remaining: remaining > 0 ? remaining : 0,
      isRange: false,
    }
  })
})

// Show/hide tooltip
const showFilterTooltip = ref(false)

// Sorting
type SortDirection = 'asc' | 'desc'
type SortTarget = 'row' | number
const sortDirection = ref<SortDirection>('asc')
const sortTarget = ref<SortTarget>('row')

function toggleSort(target: SortTarget = 'row') {
  if (sortTarget.value === target) {
    sortDirection.value = sortDirection.value === 'asc' ? 'desc' : 'asc'
  }
  else {
    sortTarget.value = target
    sortDirection.value = 'asc'
  }
}

// Sorted row indices
const sortedRowIndices = computed(() => {
  if (!props.pivotResult)
    return []

  const indices = props.pivotResult.rowHeaders.map((_, i) => i)
  const headers = props.pivotResult.rowHeaders
  const data = props.pivotResult.data

  indices.sort((a, b) => {
    let cmp: number

    if (sortTarget.value === 'row') {
      const aHeader = headers[a]?.join(' / ') || ''
      const bHeader = headers[b]?.join(' / ') || ''
      cmp = aHeader.localeCompare(bHeader, undefined, { numeric: true, sensitivity: 'base' })
    }
    else {
      const colIdx = sortTarget.value as number
      const aVal = data[a]?.[colIdx]?.value ?? null
      const bVal = data[b]?.[colIdx]?.value ?? null

      if (aVal === null && bVal === null)
        cmp = 0
      else if (aVal === null)
        cmp = 1
      else if (bVal === null)
        cmp = -1
      else cmp = aVal - bVal
    }

    return sortDirection.value === 'asc' ? cmp : -cmp
  })

  return indices
})

// Grouped layout: per-cell rowspan information (indexed by [sortedPosition][columnIndex])
const rowSpans = computed(() => {
  if (!props.pivotResult || props.pivotLayout !== 'grouped')
    return null
  return computeRowSpans(props.pivotResult.rowHeaders, sortedRowIndices.value, props.rowFields.length)
})

// Column headers
const columnHeaderCells = computed(() => {
  if (!props.pivotResult || props.pivotResult.headers.length === 0) {
    return [props.valueFields.map(vf => ({
      label: isCalculatedField(vf.field)
        ? `${getValueFieldDisplayName(vf.field)} (${getAggregationLabel(vf.aggregation)})`
        : `${vf.field} (${getAggregationLabel(vf.aggregation)})`,
      colspan: 1,
    }))]
  }

  const result: Array<Array<{ label: string, colspan: number }>> = []

  for (let level = 0; level < props.pivotResult.headers.length; level++) {
    const headerRow = props.pivotResult.headers[level]
    const cells: Array<{ label: string, colspan: number }> = []

    let i = 0
    while (i < headerRow.length) {
      const value = headerRow[i]
      let colspan = 1

      while (i + colspan < headerRow.length && headerRow[i + colspan] === value) {
        colspan++
      }

      cells.push({ label: value, colspan })
      i += colspan
    }

    result.push(cells)
  }

  return result
})

// Selection for copy support with drag
const selectedCell = ref<{ row: number, col: number } | null>(null)
const selectionStart = ref<{ row: number, col: number } | null>(null)
const selectionEnd = ref<{ row: number, col: number } | null>(null)
const isSelecting = ref(false)
const showCopyToast = ref(false)
const copyToastMessage = ref('')

const selectionBounds = computed(() => {
  if (!selectionStart.value || !selectionEnd.value)
    return null
  return {
    minRow: Math.min(selectionStart.value.row, selectionEnd.value.row),
    maxRow: Math.max(selectionStart.value.row, selectionEnd.value.row),
    minCol: Math.min(selectionStart.value.col, selectionEnd.value.col),
    maxCol: Math.max(selectionStart.value.col, selectionEnd.value.col),
  }
})

function handleCellMouseDown(rowIndex: number, colIndex: number, event: MouseEvent) {
  event.preventDefault()

  if (event.shiftKey && selectedCell.value) {
    selectionEnd.value = { row: rowIndex, col: colIndex }
  }
  else {
    selectedCell.value = { row: rowIndex, col: colIndex }
    selectionStart.value = { row: rowIndex, col: colIndex }
    selectionEnd.value = { row: rowIndex, col: colIndex }
    isSelecting.value = true
  }
}

function handleCellMouseEnter(rowIndex: number, colIndex: number) {
  if (isSelecting.value) {
    selectionEnd.value = { row: rowIndex, col: colIndex }
  }
}

function handleMouseUp() {
  isSelecting.value = false
}

function isCellSelected(rowIndex: number, colIndex: number): boolean {
  if (!selectionBounds.value) {
    return selectedCell.value?.row === rowIndex && selectedCell.value?.col === colIndex
  }
  const { minRow, maxRow, minCol, maxCol } = selectionBounds.value
  return rowIndex >= minRow && rowIndex <= maxRow && colIndex >= minCol && colIndex <= maxCol
}

function copySelectionToClipboard() {
  if (!selectionBounds.value || !props.pivotResult)
    return

  const { minRow, maxRow, minCol, maxCol } = selectionBounds.value
  const lines: string[] = []

  for (let r = minRow; r <= maxRow; r++) {
    const sortedIdx = sortedRowIndices.value[r]
    if (sortedIdx === undefined)
      continue

    const rowValues: string[] = []
    for (let c = minCol; c <= maxCol; c++) {
      const cell = props.pivotResult.data[sortedIdx]?.[c]
      rowValues.push(cell?.formattedValue ?? '')
    }
    lines.push(rowValues.join('\t'))
  }

  const text = lines.join('\n')

  navigator.clipboard.writeText(text).then(() => {
    const cellCount = (maxRow - minRow + 1) * (maxCol - minCol + 1)
    copyToastMessage.value = `Copied ${cellCount} cell${cellCount > 1 ? 's' : ''}`
    showCopyToast.value = true
    setTimeout(() => { showCopyToast.value = false }, 2000)
  }).catch((err) => {
    console.error('Copy failed:', err)
  })
}

function handleKeydown(event: KeyboardEvent) {
  // Only handle if pivot has focus or selection
  if (!selectionBounds.value)
    return

  if ((event.ctrlKey || event.metaKey) && event.key === 'c') {
    event.preventDefault()
    copySelectionToClipboard()
    return
  }

  if (event.key === 'Escape') {
    selectedCell.value = null
    selectionStart.value = null
    selectionEnd.value = null
  }
}

// Selection statistics for the footer
const selectionStats = computed(() => {
  if (!selectionBounds.value || !props.pivotResult)
    return null

  const { minRow, maxRow, minCol, maxCol } = selectionBounds.value
  const values: number[] = []
  let count = 0

  for (let r = minRow; r <= maxRow; r++) {
    const sortedIdx = sortedRowIndices.value[r]
    if (sortedIdx === undefined)
      continue

    for (let c = minCol; c <= maxCol; c++) {
      const cell = props.pivotResult.data[sortedIdx]?.[c]
      count++
      if (cell?.value !== null && cell?.value !== undefined && typeof cell.value === 'number') {
        values.push(cell.value)
      }
    }
  }

  if (count <= 1)
    return null

  const sum = values.reduce((a, b) => a + b, 0)
  const avg = values.length > 0 ? sum / values.length : 0

  return {
    count,
    numericCount: values.length,
    sum,
    avg,
  }
})

function formatStatValue(val: number): string {
  if (Math.abs(val) >= 1_000_000)
    return `${(val / 1_000_000).toFixed(2)}M`
  if (Math.abs(val) >= 1_000)
    return `${(val / 1_000).toFixed(2)}K`
  return val.toFixed(2)
}

// Lifecycle hooks for event listeners
onMounted(() => {
  document.addEventListener('mouseup', handleMouseUp)
  document.addEventListener('keydown', handleKeydown)
})

onUnmounted(() => {
  document.removeEventListener('mouseup', handleMouseUp)
  document.removeEventListener('keydown', handleKeydown)
})

// Drag handlers
function handleDragOver(area: 'row' | 'column' | 'value', event: DragEvent) {
  event.preventDefault()
  event.dataTransfer!.dropEffect = 'move'
  dragOverArea.value = area
}

function handleDragLeave() {
  dragOverArea.value = null
}

function handleDrop(area: 'row' | 'column' | 'value', event: DragEvent) {
  event.preventDefault()
  const field = event.dataTransfer?.getData('text/plain')

  // Skip if this is a reorder operation (handled by chip drop)
  if (!field || field.startsWith('reorder:')) {
    dragOverArea.value = null
    return
  }

  if (props.rowFields.includes(field))
    emit('removeRowField', field)
  if (props.columnFields.includes(field))
    emit('removeColumnField', field)
  const existingValue = props.valueFields.find(v => v.field === field)
  if (existingValue)
    emit('removeValueField', field, existingValue.aggregation)

  switch (area) {
    case 'row':
      emit('addRowField', field)
      break
    case 'column':
      emit('addColumnField', field)
      break
    case 'value':
      emit('addValueField', field, 'sum')
      break
  }
  dragOverArea.value = null
}

// Reorder handlers for chips within zones
function handleChipDragStart(zone: 'row' | 'column', index: number, event: DragEvent) {
  reorderDragSource.value = { zone, index }
  event.dataTransfer!.effectAllowed = 'move'
  event.dataTransfer!.setData('text/plain', `reorder:${zone}:${index}`)
  // Add a slight delay to ensure visual feedback
  requestAnimationFrame(() => {
    dragOverArea.value = null
  })
}

function handleChipDragEnd() {
  reorderDragSource.value = null
  reorderDropTarget.value = null
}

function handleChipDragOver(zone: 'row' | 'column', index: number, event: DragEvent) {
  event.preventDefault()
  // Only handle reorder within same zone
  if (reorderDragSource.value && reorderDragSource.value.zone === zone) {
    event.dataTransfer!.dropEffect = 'move'
    reorderDropTarget.value = { zone, index }
  }
}

function handleChipDragLeave() {
  reorderDropTarget.value = null
}

function handleChipDrop(zone: 'row' | 'column', targetIndex: number, event: DragEvent) {
  event.preventDefault()
  event.stopPropagation()

  if (!reorderDragSource.value || reorderDragSource.value.zone !== zone) {
    return
  }

  const sourceIndex = reorderDragSource.value.index
  if (sourceIndex === targetIndex) {
    reorderDragSource.value = null
    reorderDropTarget.value = null
    return
  }

  // Create reordered array
  const fields = zone === 'row' ? [...props.rowFields] : [...props.columnFields]
  const [movedField] = fields.splice(sourceIndex, 1)
  fields.splice(targetIndex, 0, movedField)

  // Emit reorder event
  if (zone === 'row') {
    emit('reorderRowFields', fields)
  }
  else {
    emit('reorderColumnFields', fields)
  }

  reorderDragSource.value = null
  reorderDropTarget.value = null
}

function isChipDragSource(zone: 'row' | 'column', index: number): boolean {
  return reorderDragSource.value?.zone === zone && reorderDragSource.value?.index === index
}

function isChipDropTarget(zone: 'row' | 'column', index: number): boolean {
  return reorderDropTarget.value?.zone === zone && reorderDropTarget.value?.index === index
}

// Column width
const rowHeaderWidth = ref(180)
const dataColWidth = ref(80)

// Calculate width per row header column
const rowHeaderColWidth = computed(() => {
  const numCols = Math.max(props.rowFields.length, 1)
  return Math.max(rowHeaderWidth.value / numCols, 80)
})

// Calculate left offset for each row header column (for sticky positioning)
function getRowHeaderLeftOffset(fieldIdx: number): number {
  return fieldIdx * rowHeaderColWidth.value
}

// Computed column paths: for each colIdx, the column field path
const columnPaths = computed((): string[][] => {
  if (!props.pivotResult || props.columnFields.length === 0)
    return []

  const numCols = props.pivotResult.data[0]?.length ?? 0
  const numValueFields = props.valueFields.length || 1
  const paths: string[][] = []

  for (let colIdx = 0; colIdx < numCols; colIdx++) {
    const colKeyIndex = Math.floor(colIdx / numValueFields)
    const path: string[] = []
    for (let h = 0; h < props.columnFields.length; h++) {
      const headerRow = props.pivotResult.headers[h]
      if (headerRow) {
        path.push(headerRow[colKeyIndex * numValueFields] ?? '')
      }
    }
    paths.push(path)
  }

  return paths
})

// Find groupStart for a given row and column index
function findGroupStart(sortedIdx: number, idx: number): PivotGroupStart | null {
  if (!props.pivotResult)
    return null
  const meta = props.pivotResult.rowMeta[sortedIdx]
  if (!meta)
    return null
  return meta.groupStarts.find(gs => gs.depth === idx) ?? null
}

// Handle chevron click
function onChevronClick(groupStart: PivotGroupStart, event: MouseEvent) {
  emit('toggleCollapse', groupStart.key, event.altKey)
}

// Handle drill-through on a data cell
function onDrillThroughCell(sortedIdx: number, colIdx: number) {
  if (!props.pivotResult)
    return
  const meta = props.pivotResult.rowMeta[sortedIdx]
  const rowPath = meta ? meta.path : []
  const colPath = columnPaths.value[colIdx] ?? []
  const numValueFields = props.valueFields.length || 1
  const valueFieldIndex = colIdx % numValueFields
  emit('drillThroughCell', { rowPath, columnPath: colPath, valueFieldIndex })
}

// Handle drill-through on a row total cell
function onDrillThroughRowTotal(sortedIdx: number) {
  if (!props.pivotResult)
    return
  const meta = props.pivotResult.rowMeta[sortedIdx]
  const rowPath = meta ? meta.path : []
  emit('drillThroughCell', { rowPath, columnPath: [], valueFieldIndex: 0 })
}

// Handle drill-through on a column total cell
function onDrillThroughColTotal(colIdx: number) {
  const colPath = columnPaths.value[colIdx] ?? []
  const numValueFields = props.valueFields.length || 1
  const valueFieldIndex = colIdx % numValueFields
  emit('drillThroughCell', { rowPath: [], columnPath: colPath, valueFieldIndex })
}

// Handle drill-through on the grand total cell
function onDrillThroughGrandTotal() {
  emit('drillThroughCell', { rowPath: [], columnPath: [], valueFieldIndex: 0 })
}
</script>

<template>
  <div
    class="vpg-pivot-skeleton"
    :class="[
      `vpg-font-${currentFontSize}`,
      theme ? `vpg-theme-${theme}` : '',
      { 'vpg-is-dragging': draggingField },
    ]"
  >
    <!-- Copy Toast -->
    <Transition name="vpg-toast">
      <div v-if="showCopyToast" class="vpg-toast">
        <svg class="vpg-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
        </svg>
        {{ copyToastMessage }}
      </div>
    </Transition>

    <!-- Header Bar -->
    <div class="vpg-skeleton-header">
      <div class="vpg-skeleton-title">
        <svg class="vpg-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z" />
        </svg>
        <span>Pivot Table</span>
      </div>

      <div class="vpg-header-right">
        <!-- Filter indicator with tooltip -->
        <div
          v-if="hasActiveFilters"
          class="vpg-filter-indicator"
          @mouseenter="showFilterTooltip = true"
          @mouseleave="showFilterTooltip = false"
        >
          <svg class="vpg-filter-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
          </svg>
          <span class="vpg-filter-text">
            Filtered: <strong>{{ filterSummary }}</strong>
            <span v-if="filteredRowCount !== undefined && totalRowCount !== undefined" class="vpg-filter-count">
              ({{ filteredRowCount.toLocaleString() }} of {{ totalRowCount.toLocaleString() }} rows)
            </span>
          </span>

          <!-- Tooltip -->
          <div v-if="showFilterTooltip" class="vpg-filter-tooltip">
            <div class="vpg-tooltip-header">
              Active Filters
            </div>
            <div v-for="filter in filterTooltipDetails" :key="filter.column" class="vpg-tooltip-filter">
              <div class="vpg-tooltip-column">
                {{ filter.column }}
              </div>
              <div class="vpg-tooltip-values">
                <!-- Range filter display -->
                <template v-if="filter.isRange">
                  <span class="vpg-tooltip-value vpg-range-value">{{ filter.displayText }}</span>
                </template>
                <!-- Value filter display -->
                <template v-else>
                  <span v-for="(val, idx) in filter.values" :key="idx" class="vpg-tooltip-value">
                    {{ val }}
                  </span>
                  <span v-if="filter.remaining > 0" class="vpg-tooltip-more">
                    +{{ filter.remaining }} more
                  </span>
                </template>
              </div>
            </div>
            <div v-if="filteredRowCount !== undefined && totalRowCount !== undefined" class="vpg-tooltip-summary">
              Showing {{ filteredRowCount.toLocaleString() }} of {{ totalRowCount.toLocaleString() }} rows
            </div>
          </div>
        </div>

        <div v-if="isConfigured" class="vpg-config-summary">
          <span class="vpg-summary-badge vpg-rows">{{ rowFields.length }} row{{ rowFields.length !== 1 ? 's' : '' }}</span>
          <span class="vpg-summary-badge vpg-cols">{{ columnFields.length }} col{{ columnFields.length !== 1 ? 's' : '' }}</span>
          <span class="vpg-summary-badge vpg-vals">{{ valueFields.length }} val{{ valueFields.length !== 1 ? 's' : '' }}</span>
        </div>

        <div v-if="isConfigured && pivotResult" class="vpg-font-size-toggle">
          <button
            v-for="opt in fontSizeOptions"
            :key="opt.value"
            class="vpg-font-size-btn"
            :class="{ active: currentFontSize === opt.value }"
            @click="currentFontSize = opt.value"
          >
            {{ opt.label }}
          </button>
        </div>
      </div>
    </div>

    <!-- Fallback Message -->
    <div v-if="!canUsePivot" class="vpg-pro-required">
      <div class="vpg-pro-content">
        <svg class="vpg-pro-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
        </svg>
        <h3>Pivot Unavailable</h3>
        <p>Pivot mode could not be enabled in this session. Try reloading the page.</p>
      </div>
    </div>

    <!-- Content when licensed -->
    <template v-else>
      <!-- Config Bar -->
      <div class="vpg-config-bar">
        <!-- Row drop zone -->
        <div
          class="vpg-drop-zone vpg-row-zone"
          :class="{ 'vpg-drag-over': dragOverArea === 'row' }"
          @dragover="handleDragOver('row', $event)"
          @dragleave="handleDragLeave"
          @drop="handleDrop('row', $event)"
        >
          <div class="vpg-zone-header">
            <span class="vpg-zone-icon vpg-row-icon">↓</span>
            <span class="vpg-zone-label">Rows</span>
          </div>
          <div class="vpg-zone-chips">
            <div
              v-for="(field, idx) in rowFields"
              :key="field"
              class="vpg-mini-chip vpg-row-chip"
              :class="{
                'vpg-chip-dragging': isChipDragSource('row', idx),
                'vpg-chip-drop-target': isChipDropTarget('row', idx),
              }"
              draggable="true"
              @dragstart="handleChipDragStart('row', idx, $event)"
              @dragend="handleChipDragEnd"
              @dragover="handleChipDragOver('row', idx, $event)"
              @dragleave="handleChipDragLeave"
              @drop="handleChipDrop('row', idx, $event)"
            >
              <span class="vpg-drag-handle">⋮⋮</span>
              <span class="vpg-mini-name">{{ field }}</span>
              <button class="vpg-mini-remove" @click.stop="emit('removeRowField', field)">
                ×
              </button>
            </div>
            <span v-if="rowFields.length === 0" class="vpg-zone-hint">Drop here</span>
          </div>
        </div>

        <!-- Column drop zone -->
        <div
          class="vpg-drop-zone vpg-column-zone"
          :class="{ 'vpg-drag-over': dragOverArea === 'column' }"
          @dragover="handleDragOver('column', $event)"
          @dragleave="handleDragLeave"
          @drop="handleDrop('column', $event)"
        >
          <div class="vpg-zone-header">
            <span class="vpg-zone-icon vpg-column-icon">→</span>
            <span class="vpg-zone-label">Columns</span>
          </div>
          <div class="vpg-zone-chips">
            <div
              v-for="(field, idx) in columnFields"
              :key="field"
              class="vpg-mini-chip vpg-column-chip"
              :class="{
                'vpg-chip-dragging': isChipDragSource('column', idx),
                'vpg-chip-drop-target': isChipDropTarget('column', idx),
              }"
              draggable="true"
              @dragstart="handleChipDragStart('column', idx, $event)"
              @dragend="handleChipDragEnd"
              @dragover="handleChipDragOver('column', idx, $event)"
              @dragleave="handleChipDragLeave"
              @drop="handleChipDrop('column', idx, $event)"
            >
              <span class="vpg-drag-handle">⋮⋮</span>
              <span class="vpg-mini-name">{{ field }}</span>
              <button class="vpg-mini-remove" @click.stop="emit('removeColumnField', field)">
                ×
              </button>
            </div>
            <span v-if="columnFields.length === 0" class="vpg-zone-hint">Drop here</span>
          </div>
        </div>

        <!-- Values drop zone -->
        <div
          class="vpg-drop-zone vpg-value-zone"
          :class="{ 'vpg-drag-over': dragOverArea === 'value' }"
          @dragover="handleDragOver('value', $event)"
          @dragleave="handleDragLeave"
          @drop="handleDrop('value', $event)"
        >
          <div class="vpg-zone-header">
            <span class="vpg-zone-icon vpg-value-icon">Σ</span>
            <span class="vpg-zone-label">Values</span>
          </div>
          <div class="vpg-zone-chips">
            <div
              v-for="vf in valueFields"
              :key="`${vf.field}-${vf.aggregation}`"
              class="vpg-mini-chip vpg-value-chip"
              :class="{ 'vpg-calc-chip': isCalculatedField(vf.field) }"
            >
              <span class="vpg-agg-symbol">{{ isCalculatedField(vf.field) ? 'ƒ' : getAggregationSymbol(vf.aggregation) }}</span>
              <span class="vpg-mini-name">{{ getValueFieldDisplayName(vf.field) }}</span>
              <button class="vpg-mini-remove" @click="emit('removeValueField', vf.field, vf.aggregation)">
                ×
              </button>
            </div>
            <span v-if="valueFields.length === 0" class="vpg-zone-hint">Drop numeric</span>
          </div>
        </div>
      </div>

      <!-- Placeholder when not configured -->
      <div v-if="!isConfigured || !pivotResult" class="vpg-placeholder">
        <div class="vpg-placeholder-content">
          <svg class="vpg-placeholder-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
          </svg>
          <span class="vpg-placeholder-text">
            <template v-if="valueFields.length === 0">
              Add a <strong>Values</strong> field to see your pivot table
            </template>
            <template v-else-if="rowFields.length === 0 && columnFields.length === 0">
              Add <strong>Row</strong> or <strong>Column</strong> fields to group your data
            </template>
            <template v-else>
              Your pivot table will appear here
            </template>
          </span>
        </div>
      </div>

      <!-- Data Table -->
      <div v-else class="vpg-table-container">
        <table class="vpg-pivot-table">
          <thead>
            <tr v-for="(headerRow, levelIdx) in columnHeaderCells" :key="`header-${levelIdx}`" class="vpg-column-header-row">
              <template v-if="levelIdx === 0">
                <th
                  v-for="(field, fieldIdx) in (rowFields.length > 0 ? rowFields : ['Rows'])"
                  :key="`row-header-${fieldIdx}`"
                  class="vpg-row-header-label"
                  :rowspan="columnHeaderCells.length"
                  :style="{ width: `${rowHeaderColWidth}px`, minWidth: '80px', left: `${getRowHeaderLeftOffset(fieldIdx)}px` }"
                  @click="toggleSort('row')"
                >
                  <div class="vpg-header-content">
                    <span>{{ field }}</span>
                    <span v-if="fieldIdx === rowFields.length - 1 || rowFields.length === 0" class="vpg-sort-indicator" :class="{ active: sortTarget === 'row' }">
                      {{ sortTarget === 'row' ? (sortDirection === 'asc' ? '↑' : '↓') : '⇅' }}
                    </span>
                  </div>
                </th>
              </template>
              <th
                v-for="(cell, idx) in headerRow"
                :key="idx"
                class="vpg-column-header-cell"
                :colspan="cell.colspan"
                :style="{ width: `${dataColWidth * cell.colspan}px` }"
                @click="levelIdx === columnHeaderCells.length - 1 && toggleSort(idx)"
              >
                <div class="vpg-header-content">
                  <span>{{ cell.label }}</span>
                  <span v-if="levelIdx === columnHeaderCells.length - 1" class="vpg-sort-indicator" :class="{ active: sortTarget === idx }">
                    {{ sortTarget === idx ? (sortDirection === 'asc' ? '↑' : '↓') : '⇅' }}
                  </span>
                </div>
              </th>
              <th
                v-if="pivotResult.rowTotals.length > 0 && levelIdx === 0"
                class="vpg-total-header"
                :rowspan="columnHeaderCells.length"
              >
                Total
              </th>
            </tr>
          </thead>

          <tbody>
            <tr
              v-for="(sortedIdx, rowPos) in sortedRowIndices"
              :key="sortedIdx"
              class="vpg-data-row"
              :class="{ 'vpg-subtotal-row': pivotResult.rowMeta[sortedIdx]?.isSubtotal }"
            >
              <!-- Grouped layout: merge repeated parent cells via rowspan; omit skipped cells -->
              <template v-if="rowSpans">
                <template
                  v-for="(val, idx) in pivotResult.rowHeaders[sortedIdx]"
                  :key="`row-${sortedIdx}-${idx}`"
                >
                  <th
                    v-if="rowSpans[rowPos][idx].render"
                    class="vpg-row-header-cell"
                    :class="{ 'vpg-row-header-collapsible': enableDrillDown !== false && findGroupStart(sortedIdx, idx) }"
                    :rowspan="rowSpans[rowPos][idx].rowspan"
                    :style="{ width: `${rowHeaderColWidth}px`, minWidth: '80px', left: `${getRowHeaderLeftOffset(idx)}px` }"
                    @click.stop="enableDrillDown !== false && findGroupStart(sortedIdx, idx) ? onChevronClick(findGroupStart(sortedIdx, idx)!, $event) : undefined"
                  >
                    <span
                      v-if="enableDrillDown !== false && findGroupStart(sortedIdx, idx)"
                      class="vpg-collapse-toggle"
                    >{{ findGroupStart(sortedIdx, idx)!.isCollapsed ? '▸' : '▾' }}</span>
                    {{ val }}
                  </th>
                </template>
              </template>

              <!-- Tabular layout: render every cell, always rowspan=1 (current behavior) -->
              <template v-else>
                <th
                  v-for="(val, idx) in pivotResult.rowHeaders[sortedIdx]"
                  :key="`row-${sortedIdx}-${idx}`"
                  class="vpg-row-header-cell"
                  :style="{ width: `${rowHeaderColWidth}px`, minWidth: '80px', left: `${getRowHeaderLeftOffset(idx)}px` }"
                >
                  <span
                    v-if="enableDrillDown !== false && findGroupStart(sortedIdx, idx)"
                    class="vpg-collapse-toggle"
                    @click.stop="onChevronClick(findGroupStart(sortedIdx, idx)!, $event)"
                  >{{ findGroupStart(sortedIdx, idx)!.isCollapsed ? '▸' : '▾' }}</span>
                  {{ val }}
                </th>
              </template>

              <td
                v-for="(cell, colIdx) in pivotResult.data[sortedIdx]"
                :key="colIdx"
                class="vpg-data-cell"
                :class="[
                  isCellSelected(sortedRowIndices.indexOf(sortedIdx), colIdx) && 'selected',
                  cell.value === null && 'vpg-is-null',
                ]"
                :style="{ width: `${dataColWidth}px` }"
                @mousedown="handleCellMouseDown(sortedRowIndices.indexOf(sortedIdx), colIdx, $event)"
                @mouseenter="handleCellMouseEnter(sortedRowIndices.indexOf(sortedIdx), colIdx)"
                @dblclick="enableDrillThrough !== false && onDrillThroughCell(sortedIdx, colIdx)"
              >
                {{ cell.formattedValue }}
              </td>

              <td
                v-if="pivotResult.rowTotals[sortedIdx]"
                class="vpg-data-cell vpg-total-cell"
                @dblclick="enableDrillThrough !== false && onDrillThroughRowTotal(sortedIdx)"
              >
                {{ pivotResult.rowTotals[sortedIdx].formattedValue }}
              </td>
            </tr>

            <tr v-if="pivotResult.columnTotals.length > 0" class="vpg-totals-row">
              <th
                class="vpg-row-header-cell vpg-total-label"
                :colspan="Math.max(rowFields.length, 1)"
                :style="{ width: `${rowHeaderWidth}px` }"
              >
                Total
              </th>
              <td
                v-for="(cell, colIdx) in pivotResult.columnTotals"
                :key="colIdx"
                class="vpg-data-cell vpg-total-cell"
                :style="{ width: `${dataColWidth}px` }"
                @dblclick="enableDrillThrough !== false && onDrillThroughColTotal(colIdx)"
              >
                {{ cell.formattedValue }}
              </td>
              <td
                v-if="pivotResult.rowTotals.length > 0"
                class="vpg-data-cell vpg-grand-total-cell"
                @dblclick="enableDrillThrough !== false && onDrillThroughGrandTotal()"
              >
                {{ pivotResult.grandTotal.formattedValue }}
              </td>
            </tr>
          </tbody>
        </table>
      </div>

      <!-- Footer -->
      <div v-if="isConfigured && pivotResult" class="vpg-skeleton-footer">
        <span class="vpg-footer-info">{{ pivotResult.rowHeaders.length }} rows × {{ pivotResult.data[0]?.length || 0 }} columns</span>

        <div v-if="selectionStats && selectionStats.count > 1" class="vpg-selection-stats">
          <span class="vpg-stat">
            <span class="vpg-stat-label">Count:</span>
            <span class="vpg-stat-value">{{ selectionStats.count }}</span>
          </span>
          <template v-if="selectionStats.numericCount > 0">
            <span class="vpg-stat-divider">|</span>
            <span class="vpg-stat">
              <span class="vpg-stat-label">Sum:</span>
              <span class="vpg-stat-value">{{ formatStatValue(selectionStats.sum) }}</span>
            </span>
            <span class="vpg-stat-divider">|</span>
            <span class="vpg-stat">
              <span class="vpg-stat-label">Avg:</span>
              <span class="vpg-stat-value">{{ formatStatValue(selectionStats.avg) }}</span>
            </span>
          </template>
        </div>
      </div>
    </template>

    <!-- Watermark / Demo Banner -->
    <div v-if="showWatermark && canUsePivot" class="vpg-watermark" :class="{ 'vpg-demo-mode': isDemo }">
      <template v-if="isDemo">
        <span class="vpg-demo-badge">DEMO</span>
        <span>Pro features unlocked for evaluation</span>
        <a href="https://tiny-pivot.com/#pricing" target="_blank" rel="noopener" class="vpg-get-pro">
          Get Pro License →
        </a>
      </template>
      <template v-else>
        <a href="https://tiny-pivot.com" target="_blank" rel="noopener">
          Powered by TinyPivot
        </a>
      </template>
    </div>
  </div>
</template>

<style scoped>
.vpg-pivot-skeleton {
  background: var(--vpg-surface-bg);
  border: 1px solid var(--vpg-border-default);
  border-radius: 0.75rem;
  box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  height: 100%;
}

.vpg-pivot-skeleton.vpg-is-dragging {
  box-shadow: 0 0 0 2px var(--vpg-accent);
}

.vpg-icon {
  width: 1rem;
  height: 1rem;
}

/* Header */
.vpg-skeleton-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.5rem 1rem;
  background: var(--vpg-surface-panel);
  border-bottom: 1px solid var(--vpg-border-default);
  flex-shrink: 0;
}

.vpg-skeleton-title {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--vpg-text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.vpg-header-right {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.vpg-config-summary {
  display: flex;
  align-items: center;
  gap: 0.375rem;
}

.vpg-summary-badge {
  padding: 0.125rem 0.375rem;
  font-size: 0.625rem;
  font-weight: 600;
  border-radius: 0.25rem;
}

.vpg-summary-badge.vpg-rows {
  background: var(--vpg-dim-row-bg);
  color: var(--vpg-dim-row-text);
}

.vpg-summary-badge.vpg-cols {
  background: var(--vpg-dim-col-bg);
  color: var(--vpg-dim-col-text);
}

.vpg-summary-badge.vpg-vals {
  background: var(--vpg-dim-value-bg);
  color: var(--vpg-dim-value-text);
}

/* Filter indicator */
.vpg-filter-indicator {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.25rem 0.625rem;
  background: var(--vpg-highlight-bg);
  border: 1px solid var(--vpg-highlight-border);
  border-radius: 0.375rem;
  font-size: 0.6875rem;
  color: var(--vpg-highlight-text);
  cursor: help;
}

.vpg-filter-icon {
  width: 0.875rem;
  height: 0.875rem;
  flex-shrink: 0;
  color: var(--vpg-highlight-text);
}

.vpg-filter-text {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  white-space: nowrap;
}

.vpg-filter-text strong {
  font-weight: 600;
  color: var(--vpg-highlight-text);
  max-width: 150px;
  overflow: hidden;
  text-overflow: ellipsis;
}

.vpg-filter-count {
  color: var(--vpg-highlight-text);
  font-size: 0.625rem;
}

/* Filter tooltip */
.vpg-filter-tooltip {
  position: absolute;
  top: calc(100% + 0.5rem);
  right: 0;
  min-width: 220px;
  max-width: 320px;
  background: var(--vpg-surface-elevated);
  border: 1px solid var(--vpg-border-default);
  border-radius: 0.5rem;
  box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.15), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
  z-index: 100;
  overflow: hidden;
}

.vpg-tooltip-header {
  padding: 0.5rem 0.75rem;
  font-size: 0.6875rem;
  font-weight: 700;
  color: var(--vpg-text-secondary);
  background: var(--vpg-surface-panel);
  border-bottom: 1px solid var(--vpg-border-default);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.vpg-tooltip-filter {
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--vpg-border-subtle);
}

.vpg-tooltip-filter:last-of-type {
  border-bottom: none;
}

.vpg-tooltip-column {
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--vpg-text-primary);
  margin-bottom: 0.375rem;
}

.vpg-tooltip-values {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
}

.vpg-tooltip-value {
  padding: 0.125rem 0.375rem;
  font-size: 0.625rem;
  background: var(--vpg-highlight-bg);
  color: var(--vpg-highlight-text);
  border-radius: 0.25rem;
  border: 1px solid var(--vpg-highlight-border);
}

.vpg-tooltip-more {
  padding: 0.125rem 0.375rem;
  font-size: 0.625rem;
  color: var(--vpg-text-secondary);
  font-style: italic;
}

.vpg-tooltip-summary {
  padding: 0.5rem 0.75rem;
  font-size: 0.625rem;
  color: var(--vpg-text-secondary);
  background: var(--vpg-surface-panel);
  border-top: 1px solid var(--vpg-border-default);
  text-align: center;
}

.vpg-font-size-toggle {
  display: flex;
  background: var(--vpg-surface-elevated);
  border-radius: 0.25rem;
  border: 1px solid var(--vpg-border-default);
  overflow: hidden;
}

.vpg-font-size-btn {
  padding: 0.125rem 0.5rem;
  font-size: 0.625rem;
  font-weight: 500;
  color: var(--vpg-text-secondary);
  background: transparent;
  border: none;
  cursor: pointer;
  transition: all 0.15s;
}

.vpg-font-size-btn:hover {
  background: var(--vpg-surface-hover);
}

.vpg-font-size-btn.active {
  background: #10b981;
  color: var(--vpg-text-inverse);
}

/* Pro Required */
.vpg-pro-required {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--vpg-highlight-bg);
}

.vpg-pro-content {
  text-align: center;
  padding: 2rem;
}

.vpg-pro-icon {
  width: 3rem;
  height: 3rem;
  color: var(--vpg-highlight-text);
  margin: 0 auto 1rem;
}

.vpg-pro-content h3 {
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--vpg-highlight-text);
  margin-bottom: 0.5rem;
}

.vpg-pro-content p {
  font-size: 0.875rem;
  color: var(--vpg-highlight-text);
  margin-bottom: 1rem;
}

.vpg-pro-link {
  display: inline-block;
  padding: 0.5rem 1rem;
  background: var(--vpg-highlight-text);
  color: var(--vpg-text-inverse);
  font-weight: 500;
  border-radius: 0.375rem;
  text-decoration: none;
  transition: opacity 0.15s;
}

.vpg-pro-link:hover {
  opacity: 0.85;
}

/* Config Bar */
.vpg-config-bar {
  display: flex;
  align-items: stretch;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  background: var(--vpg-surface-panel);
  border-bottom: 1px solid var(--vpg-border-default);
  flex-shrink: 0;
}

.vpg-drop-zone {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  border-radius: 0.5rem;
  border: 2px dashed;
  transition: all 0.15s;
}

.vpg-drop-zone.vpg-row-zone {
  background: var(--vpg-dim-row-bg);
  border-color: var(--vpg-dim-row-border);
}

.vpg-drop-zone.vpg-column-zone {
  background: var(--vpg-dim-col-bg);
  border-color: var(--vpg-dim-col-border);
  flex: 1;
}

.vpg-drop-zone.vpg-value-zone {
  background: var(--vpg-dim-value-bg);
  border-color: var(--vpg-dim-value-border);
}

.vpg-drop-zone.vpg-drag-over {
  border-style: solid;
  box-shadow: 0 0 0 2px currentColor inset;
}

.vpg-drop-zone.vpg-row-zone.vpg-drag-over {
  background: var(--vpg-dim-row-bg);
  border-color: var(--vpg-dim-row-text);
}

.vpg-drop-zone.vpg-column-zone.vpg-drag-over {
  background: var(--vpg-dim-col-bg);
  border-color: var(--vpg-dim-col-text);
}

.vpg-drop-zone.vpg-value-zone.vpg-drag-over {
  background: var(--vpg-dim-value-bg);
  border-color: var(--vpg-dim-value-text);
}

.vpg-zone-header {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  flex-shrink: 0;
}

.vpg-zone-icon {
  width: 1.25rem;
  height: 1.25rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  font-weight: 700;
  border-radius: 0.25rem;
}

.vpg-zone-icon.vpg-row-icon {
  background: var(--vpg-dim-row-border);
  color: var(--vpg-dim-row-text);
}

.vpg-zone-icon.vpg-column-icon {
  background: var(--vpg-dim-col-border);
  color: var(--vpg-dim-col-text);
}

.vpg-zone-icon.vpg-value-icon {
  background: var(--vpg-dim-value-border);
  color: var(--vpg-dim-value-text);
}

.vpg-zone-label {
  font-size: 0.625rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.vpg-row-zone .vpg-zone-label {
  color: var(--vpg-dim-row-text);
}

.vpg-column-zone .vpg-zone-label {
  color: var(--vpg-dim-col-text);
}

.vpg-value-zone .vpg-zone-label {
  color: var(--vpg-dim-value-text);
}

.vpg-zone-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem;
}

.vpg-zone-hint {
  font-size: 0.625rem;
  color: var(--vpg-text-muted);
  font-style: italic;
}

.vpg-mini-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.25rem 0.5rem;
  border-radius: 0.25rem;
  font-size: 0.625rem;
  font-weight: 500;
  max-width: 100%;
  box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  cursor: grab;
  transition: all 0.15s ease;
}

.vpg-mini-chip:active {
  cursor: grabbing;
}

.vpg-drag-handle {
  opacity: 0.3;
  font-size: 0.625rem;
  letter-spacing: -0.1em;
  margin-right: 0.125rem;
  cursor: grab;
  flex-shrink: 0;
}

.vpg-mini-chip:hover .vpg-drag-handle {
  opacity: 0.6;
}

.vpg-mini-chip.vpg-chip-dragging {
  opacity: 0.4;
  transform: scale(0.95);
}

.vpg-mini-chip.vpg-chip-drop-target {
  transform: translateX(4px);
  box-shadow: -3px 0 0 0 currentColor, 0 1px 2px 0 rgb(0 0 0 / 0.05);
}

.vpg-mini-chip.vpg-row-chip {
  background: var(--vpg-surface-elevated);
  color: var(--vpg-dim-row-text);
  border: 1px solid var(--vpg-dim-row-border);
}

.vpg-mini-chip.vpg-column-chip {
  background: var(--vpg-surface-elevated);
  color: var(--vpg-dim-col-text);
  border: 1px solid var(--vpg-dim-col-border);
}

.vpg-mini-chip.vpg-value-chip {
  background: var(--vpg-surface-elevated);
  color: var(--vpg-dim-value-text);
  border: 1px solid var(--vpg-dim-value-border);
}

.vpg-mini-chip.vpg-value-chip.vpg-calc-chip {
  background: var(--vpg-dim-calc-bg);
  color: var(--vpg-dim-calc-text);
  border-color: var(--vpg-dim-calc-border);
}

.vpg-mini-chip.vpg-value-chip.vpg-calc-chip .vpg-agg-symbol {
  background: var(--vpg-dim-calc-border);
  color: var(--vpg-dim-calc-text);
}

.vpg-mini-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
}

.vpg-mini-remove {
  width: 1rem;
  height: 1rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.875rem;
  line-height: 1;
  opacity: 0.4;
  flex-shrink: 0;
  background: transparent;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  transition: all 0.15s;
}

.vpg-mini-remove:hover {
  opacity: 1;
  background: #fee2e2;
  color: #ef4444;
}

.vpg-agg-symbol {
  width: 1rem;
  height: 1rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.625rem;
  font-weight: 700;
  background: var(--vpg-dim-value-border);
  color: var(--vpg-dim-value-text);
  border-radius: 0.25rem;
  flex-shrink: 0;
}

/* Placeholder */
.vpg-placeholder {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, var(--vpg-surface-panel), var(--vpg-surface-bg), rgba(236, 253, 245, 0.3));
  border-top: 1px solid var(--vpg-border-subtle);
}

.vpg-placeholder-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  text-align: center;
  padding: 2rem;
}

.vpg-placeholder-icon {
  width: 4rem;
  height: 4rem;
  color: var(--vpg-border-strong);
}

.vpg-placeholder-text {
  font-size: 0.875rem;
  color: var(--vpg-text-secondary);
}

.vpg-placeholder-text strong {
  color: var(--vpg-text-primary);
  font-weight: 600;
}

/* Table */
.vpg-table-container {
  flex: 1;
  overflow: auto;
  max-height: 100%;
  isolation: isolate;
  background: var(--vpg-surface-bg);
}

.vpg-pivot-table {
  border-collapse: collapse;
  table-layout: fixed;
  min-width: max-content;
}

.vpg-pivot-table thead {
  position: sticky;
  top: 0;
  z-index: 30;
  box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.1);
}

.vpg-column-header-row {
  background: var(--vpg-surface-panel);
}

.vpg-column-header-row th {
  background: var(--vpg-surface-panel);
}

.vpg-row-header-label {
  position: sticky;
  /* left is set dynamically via inline style for multi-column row headers */
  z-index: 30;
  padding: 0.5rem 0.75rem;
  text-align: left;
  font-size: 0.625rem;
  font-weight: 600;
  color: var(--vpg-text-secondary);
  text-transform: uppercase;
  border-bottom: 1px solid var(--vpg-border-default);
  border-right: 1px solid var(--vpg-border-default);
  background: var(--vpg-surface-panel);
  cursor: pointer;
}

.vpg-row-header-label + .vpg-row-header-label {
  border-left: 1px solid var(--vpg-border-default);
}

.vpg-row-header-label:hover {
  background: var(--vpg-surface-hover);
}

.vpg-column-header-cell {
  padding: 0.5rem 0.75rem;
  text-align: center;
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--vpg-text-primary);
  border-bottom: 1px solid var(--vpg-border-default);
  border-right: 1px solid var(--vpg-border-default);
  white-space: nowrap;
  background: var(--vpg-surface-panel);
  cursor: pointer;
}

.vpg-column-header-cell:hover {
  background: var(--vpg-surface-hover);
}

.vpg-header-content {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
}

.vpg-sort-indicator {
  flex-shrink: 0;
  width: 1rem;
  height: 1rem;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--vpg-text-muted);
  font-size: 0.75rem;
}

.vpg-sort-indicator.active {
  color: var(--vpg-accent);
  font-weight: 700;
}

.vpg-total-header {
  padding: 0.5rem;
  text-align: center;
  font-size: 0.6875rem;
  font-weight: 700;
  color: var(--vpg-highlight-text);
  border-bottom: 1px solid var(--vpg-border-strong);
  border-left: 2px solid var(--vpg-highlight-border);
  background: var(--vpg-highlight-border);
  vertical-align: middle;
}

.vpg-data-row:hover {
  background: var(--vpg-accent-soft-bg);
}

.vpg-data-row:nth-child(even) {
  background: var(--vpg-surface-striped);
}

.vpg-row-header-cell {
  position: sticky;
  /* left is set dynamically via inline style for multi-column row headers */
  padding: 0.5rem 0.75rem;
  text-align: left;
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--vpg-text-primary);
  background: var(--vpg-surface-bg);
  border-bottom: 1px solid var(--vpg-border-default);
  border-right: 1px solid var(--vpg-border-default);
  white-space: nowrap;
  z-index: 10;
}

.vpg-data-row:nth-child(even) .vpg-row-header-cell {
  background: var(--vpg-surface-striped);
}

/* Row header cells now render as separate columns */
.vpg-row-header-cell + .vpg-row-header-cell {
  border-left: 1px solid var(--vpg-border-default);
}

.vpg-data-cell {
  padding: 0.5rem 0.75rem;
  text-align: right;
  font-size: 0.75rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  color: var(--vpg-text-primary);
  font-variant-numeric: tabular-nums;
  border-bottom: 1px solid var(--vpg-border-subtle);
  border-right: 1px solid var(--vpg-surface-panel);
  cursor: cell;
  white-space: nowrap;
}

.vpg-data-cell:hover {
  box-shadow: inset 0 0 0 2px rgba(16, 185, 129, 0.4);
}

.vpg-data-cell.selected {
  background: var(--vpg-dim-value-bg);
  box-shadow: inset 0 0 0 2px var(--vpg-dim-value-text);
}

.vpg-data-cell.vpg-is-null {
  color: var(--vpg-border-strong);
}

.vpg-data-cell.vpg-total-cell {
  background: var(--vpg-highlight-bg);
  font-weight: 600;
  color: var(--vpg-highlight-text);
}

.vpg-data-cell.vpg-grand-total-cell {
  background: var(--vpg-highlight-border);
  font-weight: 700;
  color: var(--vpg-highlight-text);
}

.vpg-totals-row {
  background: var(--vpg-highlight-bg);
}

.vpg-total-label {
  font-weight: 700;
  color: var(--vpg-highlight-text);
  background: var(--vpg-highlight-bg);
}

/* Font sizes */
.vpg-pivot-skeleton.vpg-font-xs .vpg-data-cell,
.vpg-pivot-skeleton.vpg-font-xs .vpg-row-header-cell {
  font-size: 0.75rem;
  padding: 0.375rem 0.75rem;
}

.vpg-pivot-skeleton.vpg-font-sm .vpg-data-cell,
.vpg-pivot-skeleton.vpg-font-sm .vpg-row-header-cell {
  font-size: 0.875rem;
  padding: 0.5rem 1rem;
}

.vpg-pivot-skeleton.vpg-font-base .vpg-data-cell,
.vpg-pivot-skeleton.vpg-font-base .vpg-row-header-cell {
  font-size: 1rem;
  padding: 0.625rem 1rem;
}

/* Footer */
.vpg-skeleton-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  font-size: 0.75rem;
  color: var(--vpg-text-secondary);
  background: var(--vpg-surface-panel);
  border-top: 1px solid var(--vpg-border-default);
  flex-shrink: 0;
}

.vpg-skeleton-footer .vpg-footer-info {
  color: var(--vpg-text-muted);
}

.vpg-skeleton-footer .vpg-selection-stats {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.125rem 0.5rem;
  background: rgba(16, 185, 129, 0.08);
  border: 1px solid rgba(16, 185, 129, 0.15);
  border-radius: 0.25rem;
  font-size: 0.6875rem;
}

.vpg-skeleton-footer .vpg-stat {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

.vpg-skeleton-footer .vpg-stat-label {
  color: var(--vpg-text-secondary);
  font-weight: 400;
}

.vpg-skeleton-footer .vpg-stat-value {
  color: #10b981;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

.vpg-skeleton-footer .vpg-stat-divider {
  color: var(--vpg-border-strong);
}

/* Watermark */
.vpg-watermark {
  padding: 0.375rem 1rem;
  background: var(--vpg-surface-hover);
  border-top: 1px solid var(--vpg-border-default);
  text-align: center;
  flex-shrink: 0;
}

.vpg-watermark a {
  font-size: 0.625rem;
  color: var(--vpg-text-muted);
  text-decoration: none;
  transition: color 0.15s;
}

.vpg-watermark a:hover {
  color: var(--vpg-text-secondary);
}

/* Demo Mode Banner */
.vpg-watermark.vpg-demo-mode {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  padding: 0.5rem 1rem;
  background: var(--vpg-highlight-bg);
  border-top: 1px solid var(--vpg-highlight-border);
  font-size: 0.75rem;
  color: var(--vpg-highlight-text);
}

.vpg-demo-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.125rem 0.5rem;
  background: var(--vpg-highlight-text);
  color: var(--vpg-text-inverse);
  font-size: 0.625rem;
  font-weight: 700;
  border-radius: 0.25rem;
  letter-spacing: 0.05em;
}

.vpg-get-pro {
  font-weight: 600;
  color: var(--vpg-highlight-text) !important;
}

.vpg-get-pro:hover {
  color: var(--vpg-highlight-text) !important;
  text-decoration: underline;
  opacity: 0.85;
}

/* Collapse toggle (chevron) */
.vpg-collapse-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  color: var(--vpg-text-secondary);
  font-size: 1.0625rem;
  margin-right: 0.375rem;
  flex-shrink: 0;
  transition: color 0.15s;
  pointer-events: none;
}

/* Whole-cell clickable affordance for grouped parent cells */
.vpg-row-header-collapsible {
  cursor: pointer;
  transition: background-color 0.15s;
}

.vpg-row-header-collapsible:hover {
  background-color: var(--vpg-surface-hover, rgba(0, 0, 0, 0.04));
}

.vpg-row-header-collapsible:hover .vpg-collapse-toggle {
  color: var(--vpg-accent);
}

/* Subtotal row styling */
.vpg-subtotal-row .vpg-row-header-cell {
  font-weight: 700;
  color: var(--vpg-highlight-text);
  background: var(--vpg-highlight-bg);
}

.vpg-subtotal-row .vpg-data-cell {
  font-weight: 600;
  background: var(--vpg-highlight-bg);
}

/* Scrollbar */
.vpg-table-container::-webkit-scrollbar {
  width: 0.5rem;
  height: 0.5rem;
}

.vpg-table-container::-webkit-scrollbar-track {
  background: var(--vpg-scrollbar-track);
}

.vpg-table-container::-webkit-scrollbar-thumb {
  background: var(--vpg-scrollbar-thumb);
  border-radius: 9999px;
}

.vpg-table-container::-webkit-scrollbar-thumb:hover {
  background: var(--vpg-text-muted);
}

.vpg-table-container::-webkit-scrollbar-corner {
  background: var(--vpg-scrollbar-track);
}

/* Toast notification */
.vpg-pivot-skeleton .vpg-toast {
  position: absolute;
  top: 1rem;
  right: 1rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  background: var(--vpg-state-success);
  color: var(--vpg-text-inverse);
  border-radius: 0.5rem;
  font-size: 0.875rem;
  font-weight: 500;
  box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1);
  z-index: 100;
}

.vpg-toast-enter-active,
.vpg-toast-leave-active {
  transition: all 0.2s ease;
}

.vpg-toast-enter-from,
.vpg-toast-leave-to {
  opacity: 0;
  transform: translateY(-0.5rem);
}
</style>

<style>
/* Dark Mode - PivotSkeleton */
/* Background uses surface-panel in dark vs surface-bg in light (token-mismatch). */
.vpg-theme-dark .vpg-pivot-skeleton {
  background: var(--vpg-surface-panel);
}

/* Header background flips to surface-bg in dark (token-mismatch with light=panel). */
.vpg-theme-dark .vpg-pivot-skeleton .vpg-skeleton-header {
  background: var(--vpg-surface-bg) !important;
}

/* Config bar background flips to surface-bg in dark (token-mismatch). */
.vpg-theme-dark .vpg-pivot-skeleton .vpg-config-bar {
  background: var(--vpg-surface-bg) !important;
}

.vpg-theme-dark .vpg-pivot-skeleton .vpg-drop-zones {
  background: var(--vpg-surface-bg) !important;
  border-color: var(--vpg-border-default) !important;
}

/* Dark-only fallback styling (light variants set bg/border per zone). */
.vpg-theme-dark .vpg-pivot-skeleton .vpg-drop-zone {
  background: var(--vpg-surface-panel) !important;
  border-color: var(--vpg-border-strong) !important;
}

.vpg-theme-dark .vpg-pivot-skeleton .vpg-drop-zone:hover {
  border-color: #64748b !important;
  background: var(--vpg-surface-hover) !important;
}

.vpg-theme-dark .vpg-pivot-skeleton .vpg-drop-zone.vpg-zone-active {
  border-color: #10b981 !important;
  background: rgba(16, 185, 129, 0.2) !important;
}

.vpg-theme-dark .vpg-pivot-skeleton .vpg-drag-handle {
  opacity: 0.4;
}

.vpg-theme-dark .vpg-pivot-skeleton .vpg-mini-chip:hover .vpg-drag-handle {
  opacity: 0.7;
}

/* Filter tooltip — border swaps to strong in dark (token-mismatch with light=default). */
.vpg-theme-dark .vpg-pivot-skeleton .vpg-filter-tooltip {
  border-color: var(--vpg-border-strong) !important;
  box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.4), 0 8px 10px -6px rgba(0, 0, 0, 0.3);
}

/* Tooltip header background flips to surface-bg in dark (token-mismatch). */
.vpg-theme-dark .vpg-pivot-skeleton .vpg-tooltip-header {
  background: var(--vpg-surface-bg) !important;
}

/* Filter row border swaps to default in dark (light=subtle). */
.vpg-theme-dark .vpg-pivot-skeleton .vpg-tooltip-filter {
  border-color: var(--vpg-border-default) !important;
}

/* Tooltip summary background flips to surface-bg in dark (token-mismatch). */
.vpg-theme-dark .vpg-pivot-skeleton .vpg-tooltip-summary {
  background: var(--vpg-surface-bg) !important;
}

.vpg-theme-dark .vpg-pivot-skeleton .vpg-table-container {
  background: var(--vpg-surface-panel);
}

.vpg-theme-dark .vpg-pivot-table {
  background: var(--vpg-surface-panel);
}

.vpg-theme-dark .vpg-pivot-table thead {
  box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.3);
}

/* Header rows/cells background flips to surface-bg in dark (token-mismatch). */
.vpg-theme-dark .vpg-column-header-row {
  background: var(--vpg-surface-bg) !important;
}

.vpg-theme-dark .vpg-column-header-row th {
  background: var(--vpg-surface-bg) !important;
}

.vpg-theme-dark .vpg-pivot-skeleton .vpg-column-header-cell,
.vpg-theme-dark .vpg-pivot-skeleton .vpg-corner-cell,
.vpg-theme-dark .vpg-pivot-skeleton .vpg-row-header-label {
  background: var(--vpg-surface-bg) !important;
}

/* Header hover uses surface-panel in dark (light=surface-hover; token-mismatch). */
.vpg-theme-dark .vpg-pivot-skeleton .vpg-row-header-label:hover,
.vpg-theme-dark .vpg-pivot-skeleton .vpg-column-header-cell:hover {
  background: var(--vpg-surface-panel) !important;
}

/* Data cell border in dark uses border-default for visibility. */
.vpg-theme-dark .vpg-pivot-skeleton .vpg-data-cell {
  border-color: var(--vpg-border-default);
}

/* Row hover is decorative emerald in light, chrome surface-hover in dark (role change). */
.vpg-theme-dark .vpg-pivot-skeleton .vpg-data-row:hover .vpg-row-header-cell,
.vpg-theme-dark .vpg-pivot-skeleton .vpg-data-row:hover .vpg-data-cell {
  background: var(--vpg-surface-hover);
}

/* Totals row needs to override the row hover/striping in dark; bg already
   uses --vpg-highlight-bg via the base rule, but row-level striping rules
   would otherwise win out. */
.vpg-theme-dark .vpg-pivot-skeleton .vpg-totals-row .vpg-row-header-cell,
.vpg-theme-dark .vpg-pivot-skeleton .vpg-totals-row .vpg-data-cell {
  background: var(--vpg-highlight-bg) !important;
}

/* Empty state — dark-only styling */
.vpg-theme-dark .vpg-pivot-skeleton .vpg-empty-state {
  background: var(--vpg-surface-bg);
  color: var(--vpg-text-muted);
}

/* Footer background flips to surface-bg in dark (token-mismatch). */
.vpg-theme-dark .vpg-skeleton-footer {
  background: var(--vpg-surface-bg);
}

.vpg-theme-dark .vpg-skeleton-footer .vpg-selection-stats {
  background: rgba(16, 185, 129, 0.1);
  border-color: rgba(16, 185, 129, 0.2);
}

/* Stat value uses brighter emerald variant in dark (decorative). */
.vpg-theme-dark .vpg-skeleton-footer .vpg-stat-value {
  color: #34d399;
}

/* Scrollbar uses different tones in dark than the global scrollbar tokens. */
.vpg-theme-dark .vpg-pivot-skeleton .vpg-table-container::-webkit-scrollbar-track {
  background: var(--vpg-surface-bg);
}

.vpg-theme-dark .vpg-pivot-skeleton .vpg-table-container::-webkit-scrollbar-thumb {
  background: var(--vpg-surface-hover);
}

.vpg-theme-dark .vpg-pivot-skeleton .vpg-table-container::-webkit-scrollbar-thumb:hover {
  background: var(--vpg-border-strong);
}

.vpg-theme-dark .vpg-pivot-skeleton .vpg-table-container::-webkit-scrollbar-corner {
  background: var(--vpg-surface-bg);
}

/* Placeholder gradient: first two stops resolve identically via tokens, but the
   decorative emerald tint and border-top role differ between light and dark. */
.vpg-theme-dark .vpg-pivot-skeleton .vpg-placeholder {
  background: linear-gradient(135deg, var(--vpg-surface-panel), var(--vpg-surface-bg), rgba(16, 185, 129, 0.05));
  border-top-color: var(--vpg-border-default);
}
</style>
