<template>
  <div class="cpTable" :class="mainClasses">
    <section
      ref="cpTableContainer"
      :aria-labelledby="uniqueId"
      class="cpTable__container"
      :class="containerClasses"
      tabindex="0"
    >
      <table class="cpTable__table">
        <caption v-if="caption" :id="uniqueId" class="cpTable__caption">
          {{
            caption
          }}
        </caption>
        <thead class="cpTable__header">
          <tr class="cpTable__row cpTable__row--header">
            <th
              v-for="column in visibleColumns"
              :key="column.id"
              class="cpTable__column"
              :class="getColumnClasses(column.id)"
              :style="getColumnStyle(column)"
            >
              <slot :column="column" name="column">
                {{ column.name }}
              </slot>
            </th>
            <th v-show="displayOptionsColumn" class="cpTable__column cpTable__column--isOptions">
              <cp-table-column-editor
                v-if="enableColumnEdition"
                v-model="columnsModel"
                class="cpTable__columnEditor"
                :columns="normalizedColumns"
              />
            </th>
          </tr>
        </thead>
        <tbody class="cpTable__body">
          <tr
            v-for="(rowData, rowIndex) in visibleRows"
            :key="rowIndex"
            class="cpTable__row cpTable__row--body"
            :class="getRowClasses(rowData, rowIndex)"
            :tabindex="getTabindex(rowData)"
            @click="handleRowClick(rowData, rowIndex)"
            @contextmenu.prevent="handleContextMenu({ rowData, rowIndex }, $event)"
            @keydown.enter="handleRowClick(rowData, rowIndex)"
          >
            <slot name="row" :row="rowData">
              <td
                v-for="(cellValue, cellKey) in rowData"
                :key="`${cellKey}_${rowIndex}`"
                class="cpTable__cell"
                :class="getCellClasses(cellKey)"
                :colspan="getColspan(cellKey)"
              >
                <slot :cell="cellValue" :name="cellKey">
                  <span v-if="isFullWidthRow(rowData)">{{ cellValue }}</span>
                  <template v-else>{{ cellValue }}</template>
                </slot>
              </td>
              <td v-show="displayOptionsCell(rowData)" class="cpTable__cell cpTable__cell--isOptions">
                <div v-if="enableRowOptions" class="cpTable__actions">
                  <slot name="row-quick-actions" :row="rowData">
                    <button
                      v-for="option in quickOptions"
                      :key="option.id"
                      v-tooltip="getQuickOptionTooltip(option)"
                      class="cpTable__action"
                      :class="getQuickOptionClasses(option)"
                      :disabled="option.isDisabled"
                      type="button"
                      @click.stop="($event) => option.action(getRowPayload(rowIndex), $event)"
                    >
                      <cp-icon size="16" :type="option.icon" />
                    </button>
                    <button
                      v-if="hasMoreQuickActionsThanLimit"
                      class="cpTable__action cpTable__action--isDefault"
                      type="button"
                      @click.stop="handleContextMenu({ rowData, rowIndex }, $event)"
                    >
                      <cp-icon size="16" type="more-vertical" />
                    </button>
                  </slot>
                </div>
              </td>
            </slot>
          </tr>
        </tbody>
      </table>
      <cp-table-empty-state v-if="hasNoResult" class="cpTable__emptyState" :placeholder="noResultPlaceholder" />
    </section>
    <cp-table-footer
      v-if="hasPagination"
      :is-loading="isLoading"
      :is-server-side-pagination="isServerSidePagination"
      :number-of-results="numberOfResults"
      :page-number="pageNumber"
      :pagination="pagination"
      :rows-per-page-limit="rowsPerPageLimit"
      :server-active-page="serverActivePage"
      @on-next-click="handleNavigationClick()"
      @on-previous-click="handleNavigationClick(false)"
    >
      <template v-if="hasFooterDetailsSlot" #footer-details>
        <slot name="footer-details" />
      </template>
    </cp-table-footer>
    <div class="cpTable__overlay" />
    <cp-contextual-menu
      v-if="hasRowOptions"
      ref="contextualMenu"
      :items="contextualMenuItems"
      @hide="resetCurrentRowData"
    />
  </div>
</template>

<script setup lang="ts">
import type { MenuItemCommandEvent } from 'primevue/menuitem'
import { computed, ref, useId, useSlots, watch } from 'vue'

import CpContextualMenu from '@/components/CpContextualMenu.vue'
import CpTableEmptyState from '@/components/CpTableEmptyState.vue'
import CpTableFooter from '@/components/CpTableFooter.vue'

import { RESERVED_KEYS, VISIBLE_ROWS_MAX } from '@/constants'
import { CpTableColumnObject } from '@/constants/CpTableColumn'
import { capitalizeFirstLetter } from '@/helpers'
import { camelize, decamelize } from '@/helpers/string'

interface Emits {
  (evt: 'onRowClick', data: Record<string, unknown>): void
  (evt: 'onNextClick' | 'onPreviousClick'): void
  (evt: 'onColumnsChanged', columns: CpTableColumnObject[]): void
}

interface PaginationServer {
  activePage: number
  total: number
}

interface Pagination {
  enabled?: boolean
  limit?: number
  server?: PaginationServer
}

interface RowOptions {
  action: (rowData: Record<string, unknown>, $event: Event) => void
  icon: string
  id: string
  isAsync?: boolean
  isCritical?: boolean
  isDisabled?: boolean
  isLoading?: boolean
  label: string
}

interface GroupByData {
  groupBy: string
  rows: Record<string, unknown>[]
}

interface Props {
  areRowsClickable?: boolean
  caption?: string
  columns?: string[] | CpTableColumnObject[]
  data: (Record<string, unknown> | GroupByData)[]
  emptyCellPlaceholder?: string
  enableColumnEdition?: boolean
  enableRowOptions?: boolean
  isLoading?: boolean
  noResultPlaceholder?: string
  pagination?: Pagination
  quickOptionsLimit?: number
  rowOptions?: RowOptions[]
}

const props = withDefaults(defineProps<Props>(), {
  caption: '',
  columns: () => [],
  pagination: undefined,
  areRowsClickable: false,
  emptyCellPlaceholder: 'n/a',
  noResultPlaceholder: 'No results found',
  isLoading: false,
  enableRowOptions: false,
  enableColumnEdition: false,
  quickOptionsLimit: 3,
  rowOptions: () => [],
})

const emit = defineEmits<Emits>()
const slots = useSlots()

const FULL_WIDTH_SIZE = 1000

const uniqueId = useId()
const pageNumber = ref(0)
const cpTableContainer = ref<HTMLElement | null>(null)
const contextualMenu = ref<InstanceType<typeof CpContextualMenu>>()

const hasRowOptions = computed(() => props.enableRowOptions && props.rowOptions.length)
const hasMoreQuickActionsThanLimit = computed(() => props.rowOptions.length > props.quickOptionsLimit)

const quickOptions = computed(() => {
  if (!props.enableRowOptions || !props.rowOptions.length || props.quickOptionsLimit === 0) return []

  if (hasMoreQuickActionsThanLimit.value) {
    return props.rowOptions.slice(0, props.quickOptionsLimit)
  }

  return props.rowOptions
})

const hasFooterDetailsSlot = computed(() => !!slots.footerDetails)
const displayOptionsColumn = computed(() => props.enableRowOptions || props.enableColumnEdition)

const currentRowData = ref<Record<string, unknown>>({})
const contextualMenuItems = computed(() => {
  return props.rowOptions.map((option) => ({
    ...option,
    command: (event: MenuItemCommandEvent) => option.action(currentRowData.value, event.originalEvent),
  }))
})

const containerDOMElement = computed(() => cpTableContainer.value)

const mainClasses = computed(() => ({ 'cpTable--isLoading': props.isLoading }))
const containerClasses = computed(() => ({ 'cpTable__container--hasPagination': hasPagination.value }))

const formatColumns = (column: CpTableColumnObject | string) => {
  if (typeof column === 'string') {
    return {
      id: camelize(column),
      name: decamelize(column),
      isHidden: false,
    }
  }

  return {
    ...column,
    id: column.id || camelize(column.name),
  }
}

const normalizedColumns = computed<CpTableColumnObject[]>(() => {
  if (!props.columns) return []

  const columns = props.columns.length ? [...props.columns] : [...columnsFromRows.value]

  return columns.map(formatColumns)
})

const getVisibleColumnsIds = () => {
  return normalizedColumns.value.filter(({ isHidden, isProtected }) => !isHidden || isProtected).map(({ id }) => id)
}

const fullWidthColumn = computed(() => normalizedColumns.value.find(({ isFull }) => isFull))

const fullWidthColumnIndex = computed(() => {
  if (!fullWidthColumn.value) return -1
  return normalizedColumns.value.findIndex((col) => col.id === fullWidthColumn.value!.id)
})

/**
 * When the isFull column is hidden:
 * - If there is at least one visible column before the full column in the normalized order → we keep the last of those columns at 1000px width.
 * - If there are none (e.g. A, B, C with A full and in the first position, and we hide A) → we apply the 1000px width to the first visible column (here B).
 */

/**
 * Last visible column before the full column in the normalized order (to apply FULL_WIDTH_SIZE when full is hidden).
 */
const columnBeforeFullWidthId = computed(() => {
  if (fullWidthColumnIndex.value <= 0) return null

  const maxIndex = fullWidthColumnIndex.value

  const visibleColumnsWithIndex = columnsModel.value
    .map((id) => ({ id, index: normalizedColumns.value.findIndex((col) => col.id === id) }))
    .filter(({ index }) => index >= 0 && index < maxIndex)

  const bestColumnBeforeFullWidth = visibleColumnsWithIndex.reduce<{ id: string; index: number } | null>(
    (acc, curr) => (curr.index > (acc?.index || -1) ? curr : acc),
    null,
  )

  return bestColumnBeforeFullWidth?.id || null
})

/**
 * First visible column in the normalized order (fallback when the full column is hidden and no visible column precedes it).
 */
const firstVisibleColumnId = computed(() => {
  const visibleWithIndex = columnsModel.value
    .map((id) => ({ id, index: normalizedColumns.value.findIndex((col) => col.id === id) }))
    .filter(({ index }) => index >= 0)

  const firstVisibleColumn = visibleWithIndex.reduce<{ id: string; index: number } | null>(
    (acc, curr) => (curr.index < (acc?.index || Infinity) ? curr : acc),
    null,
  )
  return firstVisibleColumn?.id || null
})

/**
 * Column that receives FULL_WIDTH_SIZE when the isFull column is hidden (last visible before full, or first visible if none precedes full).
 */
const columnThatGetsFullWidthId = computed(() => columnBeforeFullWidthId.value || firstVisibleColumnId.value)

const hasAFullWidthColumnVisible = computed(() => {
  if (!fullWidthColumn.value) return true
  return columnsModel.value.includes(fullWidthColumn.value.id)
})

const isColumnThatGetsFullWidth = (column: CpTableColumnObject) => {
  return !!(columnThatGetsFullWidthId.value && column.id === columnThatGetsFullWidthId.value)
}

const columnsModel = ref<string[]>(getVisibleColumnsIds())

watch(columnsModel, (newColumnsModel) => {
  const newColumns = normalizedColumns.value.map((col) => ({ ...col, isHidden: !newColumnsModel.includes(col.id) }))
  emit('onColumnsChanged', newColumns)
})

const visibleColumns = computed(() => normalizedColumns.value.filter(({ id }) => columnsModel.value.includes(id)))

const numberOfColumns = computed(() => visibleColumns.value.length)

const hasGroupBy = computed(() => {
  if (!props.data.length) return false
  return props.data.some((item) => RESERVED_KEYS.GROUP_BY in item)
})

const columnsFromRows = computed(() => {
  if (!props.data.length) return []
  const firstItem = props.data[0]
  const firstRow = hasGroupBy.value && 'rows' in firstItem ? (firstItem as GroupByData).rows[0] : firstItem
  return Object.keys(firstRow as Record<string, unknown>)
})

const numberOfResults = computed(() => {
  return isServerSidePagination.value ? props.pagination?.server?.total || 0 : flattenedRows.value.length
})

const hasNoResult = computed(() => numberOfResults.value === 0)

const rowsPerPageLimit = computed(() => {
  if (typeof props.pagination === 'object' && props.pagination.limit) return props.pagination.limit
  return VISIBLE_ROWS_MAX
})

const flattenedRows = computed(() => {
  if (!props.data) return []
  if (!hasGroupBy.value) return props.data as Record<string, unknown>[]

  return props.data.reduce((flattenedRows: Record<string, unknown>[], item) => {
    if ('groupBy' in item) {
      const groupByItem = item as GroupByData
      const fullWidthRow = { [RESERVED_KEYS.GROUP_BY]: groupByItem.groupBy }
      return [...flattenedRows, fullWidthRow, ...groupByItem.rows]
    }
    return [...flattenedRows, item as Record<string, unknown>]
  }, [])
})

const rawVisibleRows = computed(() => {
  const dataCopy = [...flattenedRows.value]
  if (isServerSidePagination.value) return dataCopy
  return hasPagination.value ? dataCopy.splice(pagesStartIndex.value, rowsPerPageLimit.value) : dataCopy
})

const visibleRows = computed(() =>
  rawVisibleRows.value.map((rawRow) => {
    const normalizedRow = normalizeRowData({ rowPayload: rawRow })
    return mapCellToColumn({ rowPayload: normalizedRow })
  }),
)

const paginationState = computed(() => {
  return typeof props.pagination === 'boolean' ? props.pagination : props.pagination?.enabled
})

const hasPagination = computed(() => paginationState.value || numberOfResults.value > VISIBLE_ROWS_MAX)
const pagesStartIndex = computed(() => pageNumber.value * rowsPerPageLimit.value)

const isServerSidePagination = computed(() => {
  if (!props.pagination) return false
  return 'server' in props.pagination
})

const serverActivePage = computed(() => props.pagination?.server?.activePage || 0)

const handleContextMenu = (
  { rowData, rowIndex }: { rowData: Record<string, unknown>; rowIndex: number },
  event: MouseEvent,
) => {
  if (!hasRowOptions.value || isFullWidthRow(rowData)) return

  currentRowData.value = getRowPayload(rowIndex)

  showContextualMenu(event)
}

const showContextualMenu = (event: MouseEvent) => contextualMenu.value?.show(event)
const hideContextualMenu = () => contextualMenu.value?.hide()
const resetCurrentRowData = () => (currentRowData.value = {})

const getQuickOptionTooltip = (option: RowOptions) => (!option.isDisabled ? option.label : '')

const getQuickOptionClasses = (option: RowOptions) => {
  return { 'cpTable__action--isCritical': option.isCritical }
}

const getRowPayload = (rowIndex: number) => rawVisibleRows.value[rowIndex]

const handleRowClick = (rowData: Record<string, unknown>, rowIndex: number) => {
  if (isFullWidthRow(rowData)) return

  const data = getRowPayload(rowIndex)
  emit('onRowClick', data)
}

const handleNavigationClick = (isNext = true) => {
  resetScrollPosition()

  if (isNext) {
    augmentOffset()
    emit('onNextClick')
    return
  }

  decreaseOffset()
  emit('onPreviousClick')
}

const normalizeRowData = ({
  columns = visibleColumns.value,
  rowPayload,
}: {
  columns?: CpTableColumnObject[]
  rowPayload: Record<string, unknown>
}) => {
  if (!Array.isArray(rowPayload)) {
    return { ...rowPayload }
  }

  // from ['cell 1', 'cell 2', 'cell 3']
  // to { column1: 'cell 1', column2: 'cell 2', column3: 'cell 3' }
  return rowPayload.reduce((row, cell, cellIndex) => {
    const normalizedCell = {
      [columns[cellIndex]?.id]: cell,
    }
    return { ...row, ...normalizedCell }
  }, {})
}

const mapCellToColumn = ({
  columns = visibleColumns.value,
  rowPayload,
}: {
  columns?: CpTableColumnObject[]
  rowPayload: Record<string, unknown>
}) => {
  if (isFullWidthRow(rowPayload)) return rowPayload
  // Bind column id with each row key
  // and replace row missing keys with the emptyCellPlaceholder value
  return columns.reduce((finalRow, currentColumn) => {
    const correspondingColumn = currentColumn.id
    const correspondingValue = rowPayload[correspondingColumn] || props.emptyCellPlaceholder
    const cellValue = { [correspondingColumn]: correspondingValue }

    return { ...finalRow, ...cellValue }
  }, {})
}

const augmentOffset = () => {
  if (isServerSidePagination.value) return

  const numberOfPages = Math.ceil(numberOfResults.value / rowsPerPageLimit.value)
  if (pageNumber.value + 1 < numberOfPages) pageNumber.value++
}

const decreaseOffset = () => {
  if (isServerSidePagination.value) return
  if (pageNumber.value > 0) pageNumber.value--
}

const resetScrollPosition = () => {
  if (containerDOMElement.value) {
    containerDOMElement.value.scrollTop = 0
  }
}

const getColumnClasses = (columnId: string) => `cpTable__column--is${capitalizeFirstLetter(columnId)}`

const getColumnStyle = (column: CpTableColumnObject) => {
  let width: string | undefined

  if (!hasAFullWidthColumnVisible.value && isColumnThatGetsFullWidth(column)) {
    width = `${FULL_WIDTH_SIZE}px`
  } else if (column.isFull) {
    width = `${FULL_WIDTH_SIZE}px`
  } else if (column.width) {
    width = `${column.width}px`
  }

  return { width }
}

const getRowClasses = (rowData: Record<string, unknown>, rowIndex: number) => {
  return {
    'cpTable__row--isFullWidth': isFullWidthRow(rowData),
    'cpTable__row--isClickable': !isFullWidthRow(rowData) && props.areRowsClickable,
    'cpTable__row--isSelected': isRowSelected(rowIndex),
  }
}

const getCellClasses = (cellKey: RESERVED_KEYS) => {
  return { 'cpTable__cell--isFullWidth': isFullWidthCell(cellKey) }
}

const getColspan = (cellKey: RESERVED_KEYS) => {
  const numberOfColumnsValue = displayOptionsColumn.value ? numberOfColumns.value + 1 : numberOfColumns.value
  return isFullWidthCell(cellKey) ? numberOfColumnsValue : undefined
}

const getTabindex = (rowData: Record<string, unknown>) => (isFullWidthRow(rowData) ? -1 : 0)

const fullWidthCellKeys = [RESERVED_KEYS.FULL_WIDTH, RESERVED_KEYS.GROUP_BY]
const isFullWidthCell = (cellKey: RESERVED_KEYS) => fullWidthCellKeys.includes(cellKey)
const isFullWidthRow = (rowData: Record<string, unknown>) => fullWidthCellKeys.some((key) => rowData[key])

const isRowSelected = (rowIndex: number) => {
  const row = rawVisibleRows.value[rowIndex] as Record<string, unknown>
  return row?.[RESERVED_KEYS.IS_SELECTED] || false
}

const displayOptionsCell = (rowData: Record<string, unknown>) => {
  if (isFullWidthRow(rowData)) return false
  return props.enableColumnEdition || props.enableRowOptions
}

const resetPagination = () => (pageNumber.value = 0)

defineExpose({ hideContextualMenu, resetPagination, currentRowData })
</script>

<style lang="scss">
.cpTable {
  position: relative;
  display: flex;
  flex-direction: column;

  &__container {
    position: relative;
    display: flex;
    flex: 1;
    flex-direction: column;

    &--hasPagination {
      border-bottom: var(--cp-dimensions-0_25) solid var(--cp-border-soft);
    }
  }

  &__table {
    width: 100%;
    border-collapse: collapse;
  }

  /* Scrolling Visual Cue */
  &__container[aria-labelledby][tabindex] {
    @extend %u-scrolling-visual-cue;

    overflow: auto;
    background:
      linear-gradient(
        to right,
        var(--cp-background-primary) 30%,
        color-mix(in srgb, #{var(--cp-background-primary)} 0%, transparent)
      ),
      linear-gradient(
          to right,
          color-mix(in srgb, #{var(--cp-background-primary)} 0%, transparent),
          var(--cp-background-primary) 70%
        )
        0 100%,
      radial-gradient(
        farthest-side at 0% 50%,
        color-mix(in srgb, #{var(--cp-colors-grey-shadow-color-50)} 20%, transparent),
        color-mix(in srgb, #{var(--cp-colors-grey-shadow-color-50)} 0%, transparent)
      ),
      radial-gradient(
          farthest-side at 100% 50%,
          color-mix(in srgb, #{var(--cp-colors-grey-shadow-color-50)} 20%, transparent),
          color-mix(in srgb, #{var(--cp-colors-grey-shadow-color-50)} 0%, transparent)
        )
        0 100%;
    background-attachment: local, local, scroll, scroll;
    background-color: var(--cp-background-white);
    background-position:
      0 0,
      100%,
      0 0,
      100%;
    background-repeat: no-repeat;
    background-size:
      var(--cp-dimensions-10) 100%,
      var(--cp-dimensions-10) 100%,
      var(--cp-dimensions-3_5) 100%,
      var(--cp-dimensions-3_5) 100%;
  }

  &__container[aria-labelledby][tabindex]:focus {
    border-radius: var(--cp-radius-md-lg);
    box-shadow: 0 0 0 calc(var(--cp-dimensions-0_5) * 1.5) var(--cp-focus);
  }

  &__container[aria-labelledby][tabindex] &__table {
    border: none;
    margin: 0;
  }

  &__caption {
    position: sticky;
    left: 0;
    margin-bottom: var(--cp-spacing-xl);
    font-size: var(--cp-text-size-lg);
    text-align: left;

    &::first-letter {
      text-transform: capitalize;
    }
  }

  &__body {
    vertical-align: middle;
  }

  &__row--body,
  &__cell {
    vertical-align: inherit;
  }

  &__row {
    &--body:not(:last-of-type) {
      border-bottom: var(--cp-dimensions-0_25) solid var(--cp-border-soft);
    }

    &--body:not(#{&}--isFullWidth):not(#{&}--isSelected):hover,
    &--body:not(#{&}--isFullWidth):not(#{&}--isSelected):focus,
    &--body:not(#{&}--isFullWidth):not(#{&}--isSelected):focus-within {
      background-color: var(--cp-background-primary-hover);
      transition: background-color 100ms ease-in-out;
    }

    &--body:not(#{&}--isFullWidth):not(#{&}--isSelected):focus,
    &--body:not(#{&}--isFullWidth):not(#{&}--isSelected):focus-within {
      color: var(--cp-text-accent-primary);
    }

    &--isFullWidth td {
      padding: var(--cp-spacing-md);
      background-color: var(--cp-background-secondary);
    }

    &--isClickable {
      cursor: pointer;
    }

    &--isSelected td {
      background-color: var(--cp-background-accent-primary);
      color: var(--cp-text-accent-primary);
    }

    &--body td {
      font-size: var(--cp-text-size-sm);
    }
  }

  &__column {
    position: sticky;
    z-index: 3;
    top: 0;
    padding: var(--cp-spacing-md) var(--cp-spacing-lg);
    background-color: var(--cp-background-primary);
    color: var(--cp-text-secondary);
    font-size: var(--cp-text-size-xs);
    font-weight: normal;
    line-height: var(--cp-line-height-xs);
    text-align: left;
    white-space: nowrap;

    &::first-letter {
      text-transform: capitalize;
    }

    /* Workaround for sticky header border-bottom */
    &::after {
      position: absolute;
      bottom: 0;
      left: 0;
      width: 100%;
      height: var(--cp-dimensions-0_25);
      background-color: var(--cp-border-soft);
      content: '';
    }

    &--isOptions {
      right: 0;
      padding: 0;

      &:empty {
        z-index: 1;
      }
    }
  }

  &__columnEditor {
    padding-right: calc(var(--cp-spacing-lg) + var(--cp-dimensions-0_5));

    @media (hover: none) and (pointer: coarse) {
      padding-right: var(--cp-spacing-lg);
    }
  }

  &__cell {
    &:not(#{&}--isFullWidth):not(#{&}--isOptions) {
      padding: var(--cp-spacing-xl) var(--cp-spacing-lg);
      white-space: nowrap;
    }

    &--isFullWidth > *,
    &--isOptions {
      position: sticky;
    }

    &--isFullWidth > * {
      left: 50%;
      display: inline-block;
      transform: translateX(-50%);
    }

    &--isOptions {
      right: 0;
      padding: 0 var(--cp-spacing-lg);
    }

    &--isOptions .cpTable__action {
      display: none;
      padding: var(--cp-spacing-sm-md) var(--cp-spacing-md);
      border: none;
      border-radius: 0;
      color: var(--cp-foreground-primary);

      // Add border-right only if the action is not the last one and the next one is not the default action
      &:not(:last-child) {
        border-right: 1px solid var(--cp-border-soft);
      }

      &:disabled {
        color: var(--cp-foreground-disabled);
        cursor: not-allowed;
      }

      &:is(:hover, :focus):not(:disabled) {
        background-color: var(--cp-background-primary-hover);
        box-shadow: none;
        color: var(--cp-foreground-primary);
      }

      &--isCritical:is(:hover, :focus):not(:disabled) {
        color: var(--cp-foreground-error-primary);
      }
    }

    &--isOptions .cpTable__action--isDefault {
      display: inline-flex;
      align-items: center;
      color: var(--cp-foreground-primary);

      &:hover {
        background-color: var(--cp-background-primary-hover);
        color: var(--cp-foreground-primary);
      }

      &:focus {
        box-shadow: 0 0 0 calc(var(--cp-dimensions-0_5) * 1.5) var(--cp-focus);
        outline: none !important;
      }
    }

    @mixin cp-table-only-default-action-visible-style {
      padding: var(--cp-spacing-sm);
      border-radius: var(--cp-radius-md);
      background-color: var(--cp-background-primary);
      box-shadow:
        var(--cp-shadows-3xs),
        0 0 0 var(--cp-dimensions-0_25) var(--cp-border-soft);
    }

    // Solo default action mode
    &--isOptions .cpTable__action:only-child.cpTable__action--isDefault {
      @include cp-table-only-default-action-visible-style;

      &:hover {
        background-color: var(--cp-background-primary-hover);
        box-shadow:
          var(--cp-shadows-3xs),
          0 0 0 var(--cp-dimensions-0_25) var(--cp-border-soft-hover);
      }
    }

    // Touch device mode : only default action visible
    @media (hover: none) and (pointer: coarse) {
      &--isOptions {
        padding: 0 var(--cp-dimensions-3_5) 0 var(--cp-spacing-lg);
      }

      &--isOptions .cpTable__action--isDefault {
        @include cp-table-only-default-action-visible-style;
      }
    }
  }

  &__actions {
    text-align: right;
  }

  // On desktop devices, display options only on row focus or hover
  @media (hover: hover) and (pointer: fine) {
    &__cell--isOptions {
      min-width: calc(var(--cp-dimensions-1) * 12.5);
      transition: none;
    }

    // Actions wrapper on desktop : displayed only if there are quick actions inside
    &__cell--isOptions .cpTable__actions {
      position: absolute;
      top: 50%;
      right: var(--cp-dimensions-3);
      display: inline-flex;
      overflow: hidden;
      border-radius: var(--cp-radius-md);
      background-color: var(--cp-background-primary);
      box-shadow:
        var(--cp-shadows-3xs),
        0 0 0 var(--cp-dimensions-0_25) var(--cp-border-soft);
      opacity: 0;
      transform: translateY(-50%);

      &:has(.cpTable__action:only-child) {
        right: var(--cp-dimensions-4);
      }
    }

    &__cell--isOptions .cpTable__action {
      display: flex;
    }

    &__row:focus &__cell--isOptions > .cpTable__actions,
    &__row:focus-within &__cell--isOptions > .cpTable__actions,
    &__row:hover &__cell--isOptions > .cpTable__actions {
      opacity: 1;
    }
  }

  &__emptyState {
    flex: 1;
  }


  &__overlay {
    position: absolute;
    z-index: 4;
    overflow: hidden;
    border-radius: var(--cp-radius-md-lg);
    background-color: var(--cp-background-primary);
    cursor: wait;
    inset: 0;
    opacity: 0;
    pointer-events: none;
    transition: 0.15s opacity ease-in-out;

    &::after {
      position: absolute;
      animation: shimmer 2s infinite paused;
      background-image: linear-gradient(
        90deg,
        color-mix(in srgb, #{var(--cp-colors-grey-shadow-color-20)} 0%, transparent) 0,
        color-mix(in srgb, #{var(--cp-colors-grey-shadow-color-20)} 20%, transparent) 20%,
        color-mix(in srgb, #{var(--cp-colors-grey-shadow-color-20)} 50%, transparent) 60%,
        color-mix(in srgb, #{var(--cp-colors-grey-shadow-color-20)} 0%, transparent)
      );
      content: '';
      inset: 0;
      transform: translate3d(-100%, 0, 0);
    }

    @keyframes shimmer {
      100% {
        transform: translate3d(100%, 0, 0);
      }
    }
  }

  &--isLoading &__overlay {
    opacity: 0.5;
    pointer-events: all;

    &::after {
      animation-play-state: running;
    }
  }
}
</style>
