<template>
  <div>
    <v-data-table
      v-model="selected"
      :search="showSearch ? search : undefined"
      density="compact"
      :items-per-page="itemsPerPage"
      sticky
      fixed-header
      :headers="computedHeaders"
      :items="filteredItems"
      :loading="showLoading ? loading : false"
      :row-props="resolveRowProps"
      @click:row="handleRowClick"
      @update:current-items="(items) => emit('update:currentItems', items)"
      :class="{ itemsDeleted }"
      :show-select="showSelectColumn"
      :item-value
      :group-by="resolvedGroupBy"
      :sort-by="sortBy"
    >
      <template #top>
        <v-toolbar
          v-if="showTopToolbar"
          flat
          density="compact"
          class="base-data-table-toolbar tw:pr-2"
        >
          <slot name="toolbar-start">
            <v-toolbar-title v-if="dataName?.length">
              <v-icon
                color="medium-emphasis"
                icon="mdi-book-multiple"
                size="x-small"
                start
              />
              {{ dataName }}
            </v-toolbar-title>
          </slot>

          <template #append>
            <div
              class="tw:flex tw:min-w-0 tw:w-full tw:flex-wrap tw:items-center tw:justify-end tw:gap-2"
            >
              <slot name="toolbar-leading" />
              <slot name="toolbar-end" />
              <slot
                name="toolbar-filters-trigger"
                v-bind="filterTriggerSlotProps"
              >
                <v-btn
                  v-if="showFilters"
                  variant="outlined"
                  prepend-icon="mdi-filter-variant"
                  :text="$t('core.baseDataTable.filters')"
                  @click="openFilters"
                />
              </slot>
              <slot
                name="toolbar-actions-trigger"
                v-bind="actionsTriggerSlotProps"
              >
                <v-menu
                  v-if="showActionsOverflowMenu"
                  v-model="isActionsMenuOpen"
                  location="bottom end"
                >
                  <template #activator="{ props: menuProps }">
                    <v-btn
                      v-bind="menuProps"
                      icon="mdi-dots-vertical"
                      variant="text"
                      density="comfortable"
                      :aria-label="$t('core.baseDataTable.actionsMenu')"
                    />
                  </template>
                  <v-list density="compact" nav min-width="260">
                    <slot
                      name="action.export-mode"
                      v-bind="exportModeActionSlotProps"
                    >
                      <v-list-item
                        v-if="showExport"
                        :title="
                          isExportMode
                            ? $t('core.baseDataTable.disableExportMode')
                            : $t('core.baseDataTable.enableExportMode')
                        "
                        prepend-icon="mdi-checkbox-marked-outline"
                        @click="toggleExportMode"
                      />
                    </slot>
                    <slot name="action.import" v-bind="importActionSlotProps">
                      <v-list-item
                        v-if="onFileLoaded"
                        :title="$t('core.import')"
                        prepend-icon="mdi-database-import"
                        @click="importFileUploadRef?.openPicker()"
                      />
                    </slot>
                    <slot
                      name="toolbar-actions-menu"
                      v-bind="menuActionSlotProps"
                    />
                  </v-list>
                </v-menu>
              </slot>
            </div>
          </template>
        </v-toolbar>

        <slot v-if="showSearch" name="toolbar-search">
          <v-text-field
            v-model="search"
            :label="
              $t('core.baseDataTable.searchPlaceholder', {
                dataName,
              })
            "
            prepend-inner-icon="mdi-magnify"
            variant="outlined"
            hide-details
            single-line
            density="compact"
            class="tw:mt-2 tw:mb-4"
          />
        </slot>

        <FileUpload
          v-if="onFileLoaded"
          ref="importFileUploadRef"
          :on-file-loaded="onFileLoaded"
          :accept="importAccept"
          :read-as="importReadAs"
        />

        <slot name="importer" v-bind="importerSlotProps" />
        <slot name="exporter" v-bind="exporterSlotProps">
          <DataExporter
            v-if="showExport && isExportMode"
            :data="exportPayloadData"
            :disabled="!selected.length"
            :formats="exportFormats"
            :default-format="exportDefaultFormat"
            :button-label="$t('core.export')"
          />
        </slot>
      </template>

      <template
        #item.color="{ item }"
        v-if="!customSlots.includes('item.color')"
      >
        <div class="tw:flex tw:gap-2" v-if="item.color?.length">
          <v-chip :color="item.color" label variant="outlined" size="small">
            {{ item.color }}
          </v-chip>
        </div>
      </template>
      <!-- Built-in tags column -->
      <template #item.tags="{ item }" v-if="!customSlots.includes('item.tags')">
        <div class="tw:flex tw:gap-2" v-if="item.tags?.length">
          <v-chip
            v-for="tag in item.tags"
            :key="tag._id || tag.id || tag"
            :color="tag.color"
            label
            variant="outlined"
            size="small"
          >
            {{ tag.title || tag.name || tag }}
          </v-chip>
        </div>
      </template>

      <!-- Dynamic slots for custom column rendering -->
      <template v-for="slot in customSlots" :key="slot" #[slot]="slotProps">
        <slot :name="slot" v-bind="slotProps" />
      </template>

      <!-- Actions column template -->
      <template #item.actions="{ item }" v-if="showActionsColumn">
        <slot name="prepend-row-actions" :item="item"> </slot>
        <slot name="row-actions" :item="item" />
        <slot name="append-row-actions" :item="item"> </slot>
      </template>

      <!-- Loading template -->
      <template #loader>
        <v-skeleton-loader
          class="mx-auto border"
          :height="500"
          width="100%"
          type="list-item"
          :loading="true"
        />
      </template>

      <!-- Always bridge tbody summary slots so v-data-table receives a stable slot (dynamic v-for is not reliable here). -->
      <template #body.prepend="slotProps">
        <slot name="body.prepend" v-bind="slotProps ?? {}" />
      </template>
      <template #body.append="slotProps">
        <slot name="body.append" v-bind="slotProps ?? {}" />
      </template>
      <template #tfoot="slotProps">
        <slot name="tfoot" v-bind="slotProps ?? {}" />
      </template>

      <!-- Forward remaining v-data-table slots (e.g. bottom, thead). -->
      <template
        v-for="slotName in vDataTablePassthroughSlots"
        :key="slotName"
        #[slotName]="slotProps"
      >
        <slot :name="slotName" v-bind="slotProps ?? {}" />
      </template>
    </v-data-table>
    <!-- Additional dialogs slot -->
    <slot name="dialogs" />

    <v-navigation-drawer
      v-model="isFilterDrawerOpen"
      location="right"
      temporary
      width="360"
      :aria-label="$t('core.baseDataTable.filters')"
    >
      <div class="tw:flex tw:flex-col tw:h-full">
        <slot name="filters.summary" v-bind="filterSummarySlotProps" />
        <slot name="filters.drawer.header" v-bind="filterDrawerHeaderSlotProps">
          <div
            class="tw:flex tw:items-center tw:justify-between tw:px-4 tw:py-3"
          >
            <span class="tw:font-semibold">{{
              $t("core.baseDataTable.filters")
            }}</span>
            <v-btn
              icon="mdi-close"
              size="small"
              variant="text"
              :aria-label="$t('core.baseDataTable.closeFilters')"
              @click="isFilterDrawerOpen = false"
            />
          </div>
        </slot>

        <slot name="filters.drawer.body" v-bind="filterDrawerBodySlotProps">
          <div class="tw:px-4 tw:pb-4 tw:flex tw:flex-col tw:gap-3">
            <template v-for="header in filterableHeaders" :key="String(header.key)">
              <v-autocomplete
                v-if="columnUsesComboboxFilter(String(header.key))"
                v-model="columnFilters[String(header.key)]"
                @update:model-value="notifyFilterStateChange"
                :items="filterOptionsByKey[String(header.key)] ?? []"
                :label="header.title"
                clearable
                hide-details
                density="compact"
                variant="outlined"
              />
              <v-text-field
                v-else
                v-model="columnFilters[String(header.key)]"
                @update:model-value="notifyFilterStateChange"
                :label="header.title"
                clearable
                hide-details
                density="compact"
                variant="outlined"
              />
            </template>
          </div>
        </slot>

        <div class="tw:mt-auto tw:px-4 tw:pb-4 tw:pt-2 tw:flex tw:gap-2">
          <slot
            name="filters.drawer.footer"
            v-bind="filterDrawerFooterSlotProps"
          >
            <v-btn
              variant="outlined"
              prepend-icon="mdi-filter-remove"
              :text="$t('core.baseDataTable.resetFilters')"
              @click="resetFilters"
            />
            <v-btn
              color="primary"
              variant="flat"
              prepend-icon="mdi-check"
              :text="$t('core.baseDataTable.applyFilters')"
              @click="applyFilters"
            />
          </slot>
        </div>
      </div>
    </v-navigation-drawer>
  </div>
</template>

<script setup lang="ts" generic="T extends Record<string, any>">
import { Comment, computed, ref, useSlots, useTemplateRef, watch } from "vue"
import DataExporter from "@racletteOrchestrator/components/dataExport/DataExporter.vue"
import FileUpload from "@racletteOrchestrator/components/input/FileUpload.vue"
import type {
  ExportPayload,
  FormatEntry,
} from "@racletteOrchestrator/composables/useExport/types"
import type { DataTableItem } from "vuetify/lib/components/VDataTable/types"

export interface BaseDataTableActionContext<T> {
  item?: T
  filters: Record<string, string>
  selectedIds: Array<string | number>
}

export interface BaseDataTableProps<T> {
  uuid?: string
  // Data props
  items?: T[]
  loading?: boolean
  itemsDeleted?: boolean
  // Table configuration
  headers: Array<{
    title: string
    key: keyof T | string
    align?: "start" | "center" | "end"
    sortable?: boolean
    width?: string | number
  }>
  itemsPerPage?: number
  /** Initial / default column sort (Vuetify `{ key, order }[]`). */
  sortBy?: ReadonlyArray<{ key: string; order: "asc" | "desc" }>

  // Row actions
  showActionsColumn?: boolean

  // Item processing
  rowClickHandler?: (item: T, context?: BaseDataTableActionContext<T>) => void
  /** When set with `rowClickHandler`, only matching rows are clickable and show a pointer cursor. */
  isRowClickable?: (item: T) => boolean

  dataName: string

  showExport?: boolean
  /** Checkbox column for row selection (independent of export mode). */
  showSelect?: boolean
  showFilters?: boolean
  /** Show the search field above the table (default true). */
  showSearch?: boolean
  /** Pass loading state through to v-data-table (default true). */
  showLoading?: boolean
  /** Max distinct values per column before filter falls back to a text field. */
  filterMaxSelectOptions?: number

  itemValue?: string
  /** Vuetify group-by key(s), e.g. `"project"` for project-grouped rows. */
  groupBy?: string | readonly string[]
  actionsHeaderTitle?: string

  /**
   * When set, exported rows come from here (matched to table selection via `itemValue`, default `_id`).
   * Use this to omit display-only fields (e.g. relative dates) from JSON export while keeping table `items`.
   */
  exportableItems?: Record<string, unknown>[]
  exportMeta?: Record<string, unknown>

  /** Formats offered in the export menu (workbench list tables default to JSON only). */
  exportFormats?: FormatEntry[]
  exportDefaultFormat?: string

  /** When set, an import control is shown and the handler receives file contents (same contract as FileUpload). */
  onFileLoaded?: (content: string | ArrayBuffer, file: File) => void
  importAccept?: string
  importReadAs?: "text" | "arrayBuffer"

  onFilterStateChange?: (
    payload: Record<string, string>,
    context: BaseDataTableActionContext<T>,
  ) => void
  onFilterApply?: (
    payload: Record<string, string>,
    context: BaseDataTableActionContext<T>,
  ) => void
  onFilterReset?: (
    payload: Record<string, string>,
    context: BaseDataTableActionContext<T>,
  ) => void
}

const props = withDefaults(defineProps<BaseDataTableProps<T>>(), {
  uuid: undefined,
  items: () => [],
  itemsDeleted: false,
  itemsPerPage: 10,
  sortBy: undefined,
  showActionsColumn: true,
  showExport: true,
  showSelect: false,
  showFilters: false,
  showSearch: true,
  showLoading: true,
  filterMaxSelectOptions: 300,
  itemValue: "_id",
  groupBy: undefined,
  actionsHeaderTitle: "",
  exportableItems: undefined,
  exportMeta: undefined,
  exportFormats: () => ["json"] as FormatEntry[],
  exportDefaultFormat: "json",
  onFileLoaded: undefined,
  importAccept: ".json",
  importReadAs: "text",
  onFilterStateChange: undefined,
  onFilterApply: undefined,
  onFilterReset: undefined,
})

const emit = defineEmits<{
  rowClick: [item: T]
  "update:currentItems": [items: readonly DataTableItem<T>[]]
}>()

type VuetifyGroupByItem = { key: string; order?: "asc" | "desc" }

/** Vuetify expects `{ key }[]`; accept plain column key(s) from callers. */
const resolvedGroupBy = computed((): readonly VuetifyGroupByItem[] | undefined => {
  const groupBy = props.groupBy
  if (!groupBy) return undefined
  if (typeof groupBy === "string") {
    return [{ key: groupBy, order: "asc" }]
  }
  if (groupBy.length === 0) return undefined
  if (typeof groupBy[0] === "string") {
    return groupBy.map((key) => ({ key, order: "asc" as const }))
  }
  return groupBy as readonly VuetifyGroupByItem[]
})

const slots = useSlots()

/** Slots owned by BaseDataTable chrome (toolbar, filters drawer, row actions, export). */
const BASE_DATA_TABLE_RESERVED_SLOTS = new Set([
  "toolbar-start",
  "toolbar-leading",
  "toolbar-end",
  "toolbar-filters-trigger",
  "toolbar-actions-trigger",
  "action.export-mode",
  "action.import",
  "toolbar-actions-menu",
  "toolbar-search",
  "importer",
  "exporter",
  "prepend-row-actions",
  "row-actions",
  "append-row-actions",
  "dialogs",
  "filters.summary",
  "filters.drawer.header",
  "filters.drawer.body",
  "filters.drawer.footer",
])

const V_DATA_TABLE_BRIDGED_SLOTS = new Set([
  "body.prepend",
  "body.append",
  "tfoot",
])

/** v-data-table slots supplied by the parent (e.g. bottom), excluding built-in handling. */
const vDataTablePassthroughSlots = computed(() =>
  Object.keys(slots).filter(
    (name) =>
      !BASE_DATA_TABLE_RESERVED_SLOTS.has(name) &&
      !V_DATA_TABLE_BRIDGED_SLOTS.has(name) &&
      !name.startsWith("item.") &&
      name !== "loader" &&
      name !== "top",
  ),
)

/** Hide ⋮ when the menu would be empty (no export toggle, import, or slot content). */
const showActionsOverflowMenu = computed(
  () =>
    Boolean(props.showExport) ||
    Boolean(props.onFileLoaded) ||
    Boolean(slots["toolbar-actions-menu"]) ||
    Boolean(slots["action.export-mode"]) ||
    Boolean(slots["action.import"]),
)

const hasSlotContent = (name: string) => {
  const slot = slots[name]
  if (!slot) return false
  return slot().some((vnode) => {
    if (vnode.type === Comment) return false
    if (typeof vnode.children === "string") return vnode.children.trim().length > 0
    if (Array.isArray(vnode.children)) return vnode.children.length > 0
    return true
  })
}

/** Toolbar hidden when no title, filters, export menu, import, or custom slot content. */
const showTopToolbar = computed(
  () =>
    Boolean(props.dataName?.length) ||
    Boolean(props.showFilters) ||
    showActionsOverflowMenu.value ||
    Boolean(props.onFileLoaded) ||
    hasSlotContent("toolbar-start") ||
    hasSlotContent("toolbar-leading") ||
    hasSlotContent("toolbar-end"),
)

const importFileUploadRef = useTemplateRef<{
  openPicker: () => void
}>("importFileUploadRef")

// Reactive data
const search = ref("")
const selected = defineModel<Array<string | number>>("selected", {
  default: () => [],
})
const isExportMode = ref(false)
const showSelectColumn = computed(
  () => props.showSelect || (props.showExport && isExportMode.value),
)
const isActionsMenuOpen = ref(false)
const isFilterDrawerOpen = ref(false)
/** Draft values edited in the filter drawer (applied only on Apply). */
const columnFilters = ref<Record<string, string>>({})
/** Values currently filtering the table. */
const appliedColumnFilters = ref<Record<string, string>>({})

const syncDraftFiltersFromApplied = () => {
  columnFilters.value = { ...appliedColumnFilters.value }
}

watch(isFilterDrawerOpen, (open) => {
  if (open) {
    syncDraftFiltersFromApplied()
  }
})

const computedHeaders = computed(() => {
  if (!props.headers || !props.headers?.length) {
    return null
  }

  const headers = [...props.headers].map((header) => {
    const key = String(header.key)
    const hasFilter =
      props.showFilters &&
      appliedColumnFilters.value[key]?.trim()?.length > 0
    return {
      ...header,
      title: header.title,
      ...(hasFilter
        ? {
            headerProps: {
              class: "filtered-column-header",
            },
            cellProps: {
              class: "filtered-column-cell",
            },
          }
        : {}),
    }
  })

  // Add actions column if needed
  if (props.showActionsColumn) {
    headers.push({
      title: props.actionsHeaderTitle || "Actions",
      key: "actions",
      align: "end" as const,
      sortable: false,
    })
  }

  return headers
})

const customSlots = computed(() => {
  return Object.keys(slots).filter(
    (slot) => slot.startsWith("item.") && slot !== "item.actions",
  )
})

const exportColumnKeys = computed(() => {
  return (props.headers ?? [])
    .map((header) => String(header.key).trim())
    .filter((key) => key.length > 0 && key !== "actions")
})

const filterableHeaders = computed(() =>
  (props.headers ?? []).filter((header) => {
    const key = String(header.key)
    return key.length > 0 && key !== "actions"
  }),
)

const filterOptionsByKey = computed(() => {
  const rows = props.items ?? []
  const options: Record<string, string[]> = {}

  for (const header of filterableHeaders.value) {
    const key = String(header.key)
    const values = new Set<string>()
    for (const row of rows) {
      const raw = row[key as keyof T]
      if (raw === undefined || raw === null) {
        continue
      }
      const text = String(raw)
      if (text.length === 0) {
        continue
      }
      values.add(text)
    }
    options[key] = [...values].sort((a, b) => a.localeCompare(b))
  }

  return options
})

const columnUsesComboboxFilter = (key: string) => {
  const count = filterOptionsByKey.value[key]?.length ?? 0
  return count <= props.filterMaxSelectOptions
}

const filteredItems = computed(() => {
  const rows = props.items ?? []
  if (!props.showFilters) {
    return rows
  }

  const activeFilters = Object.entries(appliedColumnFilters.value).filter(
    ([, value]) => value?.trim().length > 0,
  )
  if (!activeFilters.length) {
    return rows
  }

  return rows.filter((row) =>
    activeFilters.every(([key, filterValue]) => {
      const raw = row[key as keyof T]
      if (raw === undefined || raw === null) {
        return false
      }
      const cell = String(raw)
      if (columnUsesComboboxFilter(key)) {
        return cell.toLowerCase() === filterValue.toLowerCase()
      }
      return cell.toLowerCase().includes(filterValue.toLowerCase())
    }),
  )
})

const projectRowsToVisibleColumns = <TRow extends Record<string, unknown>>(
  rows: TRow[],
) => {
  if (!exportColumnKeys.value.length) {
    return rows
  }

  return rows.map((row) => {
    const projectedRow: Record<string, unknown> = {}
    for (const key of exportColumnKeys.value) {
      if (key in row) {
        projectedRow[key] = row[key]
      }
    }
    return projectedRow
  })
}

/** Rows passed to DataExporter when something is selected. */
const exportPayloadData = computed<ExportPayload>(() => {
  const idKey = props.itemValue as keyof T
  const selectedIds = selected.value
  const toKey = (value: unknown) => String(value)

  if (props.exportableItems != null) {
    const idToRow = new Map<string, Record<string, unknown>>()

    for (const row of props.exportableItems) {
      const rawKey = row[idKey as string]
      if (rawKey === undefined || rawKey === null) {
        console.warn(
          `[BaseDataTable] Export row is missing "${String(idKey)}" key and will be ignored.`,
          row,
        )
        continue
      }

      const rowKey = toKey(rawKey)
      if (idToRow.has(rowKey)) {
        console.warn(
          `[BaseDataTable] Duplicate export key "${rowKey}" for itemValue "${String(idKey)}".`,
        )
      }
      idToRow.set(rowKey, row)
    }

    const selectedExportRows = selectedIds
      .map((id) => idToRow.get(toKey(id)))
      .filter((row): row is Record<string, unknown> => row !== undefined)

    return {
      items: projectRowsToVisibleColumns(selectedExportRows),
      ...(props.exportMeta ? { meta: props.exportMeta } : {}),
    }
  }

  const selectedKeySet = new Set(selectedIds.map((id) => toKey(id)))
  const selectedRows = (props.items ?? []).filter((item) => {
    const rowId = item[idKey]
    if (rowId === undefined || rowId === null) {
      console.warn(
        `[BaseDataTable] Table item is missing "${String(idKey)}" key and will not be exportable.`,
        item,
      )
      return false
    }
    return selectedKeySet.has(toKey(rowId))
  }) as Record<string, unknown>[]

  return {
    items: projectRowsToVisibleColumns(selectedRows),
    ...(props.exportMeta ? { meta: props.exportMeta } : {}),
  }
})

// Methods
const toggleExportMode = () => {
  isExportMode.value = !isExportMode.value
  if (!isExportMode.value && !props.showSelect) {
    selected.value = []
  }
}

const toggleFilterDrawer = () => {
  if (!isFilterDrawerOpen.value) {
    syncDraftFiltersFromApplied()
  }
  isFilterDrawerOpen.value = !isFilterDrawerOpen.value
}

const openFilters = () => {
  syncDraftFiltersFromApplied()
  isFilterDrawerOpen.value = true
}

const isItemRowClickable = (item: T) =>
  props.isRowClickable ? props.isRowClickable(item) : true

const resolveRowProps = (data: { item: T }) => {
  if (!props.rowClickHandler) {
    return {}
  }

  return {
    class: isItemRowClickable(data.item)
      ? "tw:cursor-pointer"
      : "tw:cursor-default",
  }
}

const isSelectionControlClick = (event: PointerEvent) => {
  const target = event.target
  if (!(target instanceof Element)) return false
  return Boolean(
    target.closest(
      ".v-selection-control, .v-data-table__checkbox, .v-data-table-column--select",
    ),
  )
}

const handleRowClick = (event: PointerEvent, row: { item: T }) => {
  if (showSelectColumn.value && isSelectionControlClick(event)) {
    return
  }

  if (props.rowClickHandler && !isItemRowClickable(row.item)) {
    return
  }

  const context = actionContext(row.item)
  if (props.rowClickHandler) {
    props.rowClickHandler(row.item, context)
  }

  emit("rowClick", row.item)
}

const draftFilterPayload = computed(() => ({ ...columnFilters.value }))
const appliedFilterPayload = computed(() => ({
  ...appliedColumnFilters.value,
}))
const actionContext = (item?: T): BaseDataTableActionContext<T> => ({
  item,
  filters: appliedFilterPayload.value,
  selectedIds: [...selected.value],
})

const notifyFilterStateChange = () => {
  if (props.onFilterStateChange) {
    props.onFilterStateChange(draftFilterPayload.value, {
      ...actionContext(),
      filters: draftFilterPayload.value,
    })
  } else {
    console.log("[BaseDataTable] filter-state-change", draftFilterPayload.value)
  }
}

const applyFilters = () => {
  appliedColumnFilters.value = { ...columnFilters.value }
  if (props.onFilterApply) {
    props.onFilterApply(appliedFilterPayload.value, actionContext())
  } else {
    console.log("[BaseDataTable] filter-apply", appliedFilterPayload.value)
  }
}

const resetFilters = () => {
  columnFilters.value = {}
  appliedColumnFilters.value = {}
  if (props.onFilterReset) {
    props.onFilterReset({}, actionContext())
  } else {
    console.log("[BaseDataTable] filter-reset", {})
  }
}

const actionsTriggerSlotProps = computed(() => ({
  isOpen: isActionsMenuOpen.value,
  toggle: () => {
    isActionsMenuOpen.value = !isActionsMenuOpen.value
  },
}))

const menuActionSlotProps = computed(() => ({
  isExportMode: isExportMode.value,
  toggleExportMode,
  isFilterDrawerOpen: isFilterDrawerOpen.value,
  toggleFilterDrawer,
  openFilters,
  canExport: props.showExport,
  canImport: Boolean(props.onFileLoaded),
  openImportPicker: () => importFileUploadRef.value?.openPicker(),
  context: actionContext(),
}))

const exportModeActionSlotProps = computed(() => ({
  active: isExportMode.value,
  enabled: props.showExport,
  toggle: toggleExportMode,
}))

const importActionSlotProps = computed(() => ({
  enabled: Boolean(props.onFileLoaded),
  openPicker: () => importFileUploadRef.value?.openPicker(),
}))

const filterTriggerSlotProps = computed(() => ({
  isFilterDrawerOpen: isFilterDrawerOpen.value,
  openFilters,
  toggleFilterDrawer,
}))

const exporterSlotProps = computed(() => ({
  data: exportPayloadData.value,
  disabled: !selected.value.length,
  formats: props.exportFormats,
  defaultFormat: props.exportDefaultFormat,
  isExportMode: isExportMode.value,
  selectedCount: selected.value.length,
}))

const importerSlotProps = computed(() => ({
  openPicker: () => importFileUploadRef.value?.openPicker(),
  accept: props.importAccept,
  readAs: props.importReadAs,
  enabled: Boolean(props.onFileLoaded),
}))

const filterSummarySlotProps = computed(() => ({
  filters: appliedFilterPayload.value,
  activeCount: Object.values(appliedColumnFilters.value).filter(
    (value) => value?.trim().length > 0,
  ).length,
}))

const filterDrawerHeaderSlotProps = computed(() => ({
  close: () => {
    isFilterDrawerOpen.value = false
  },
  filters: draftFilterPayload.value,
}))

const filterDrawerBodySlotProps = computed(() => ({
  filters: columnFilters.value,
  headers: filterableHeaders.value,
  filterOptionsByKey: filterOptionsByKey.value,
  columnUsesComboboxFilter,
  setFilter: (key: string, value: string) => {
    columnFilters.value[key] = value
    notifyFilterStateChange()
  },
}))

const filterDrawerFooterSlotProps = computed(() => ({
  applyFilters,
  resetFilters,
  filters: draftFilterPayload.value,
  context: actionContext(),
}))
</script>

<style lang="css">
/*
 * VToolbar pins .v-toolbar__content to a fixed height (density-compact ≈ 48px)
 * and sets overflow: hidden, which clips wrapped toolbar controls when the
 * table lives in a narrow container (not just small viewports).
 */
.base-data-table-toolbar .v-toolbar__content {
  height: auto !important;
  min-height: 48px;
  overflow: visible;
  flex-wrap: wrap;
  row-gap: 8px;
  column-gap: 8px;
  justify-content: space-between;
  align-items: center;
}

/*
 * Vuetify VToolbar.sass: .v-toolbar__content > .v-toolbar-title {
 *   margin-inline-start: $toolbar-title-margin  // _variables.scss → 20px
 * }
 */
.base-data-table-toolbar .v-toolbar__content > .v-toolbar-title {
  margin-inline-start: 0;
}

.base-data-table-toolbar .v-toolbar__append {
  flex: 1 1 auto;
  min-width: 0;
}

/*
 * Default VToolbar gives the title flex: 1 1 so it steals shrink and ellipsizes
 * before the append block wraps. Prefer capping the title width so actions keep
 * horizontal room; headline text aligns to the end of the title cell.
 */
.base-data-table-toolbar .v-toolbar-title {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: flex-end;
  flex: 0 1 auto;
  min-width: 0;
  max-width: min(45%, 448px);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.itemsDeleted .v-data-table__tr {
  background-color: rgb(233, 190, 190);
  color: #000;
}

.filtered-column-header {
  background-color: rgba(var(--v-theme-info), 0.18);
  box-shadow: inset 0 -2px 0 rgba(var(--v-theme-info), 0.9);
}

.filtered-column-cell {
  background-color: rgba(var(--v-theme-info), 0.1);
}
</style>
