<template>
  <div
    class="list"
    :class="[`list--${presentation}`]"
    :style="{
      '--hover-color': 'rgba(from var(--border-color) r g b / 0.05)',
      '--list-columns-grid': columns.map((col) => (col.width ? col.width : '1fr')).join(' '),
      '--list-columns-grid-min': columns
        .map((col) => (col.minWidth ? col.minWidth : 'auto'))
        .join(' '),
      '--list-columns-grid-max': columns
        .map((col) => (col.maxWidth ? col.maxWidth : 'auto'))
        .join(' '),
      '--width': props.width,
    }"
  >
    <!-- List Header -->
    <div class="list__header">
      <!-- Filter -->
      <div v-if="filter" class="list__filter">
        <TextInput
          v-model="filterValue"
          :placeholder="filter.placeholder || 'Filter...'"
          icon="search"
          @update:model-value="handleFilter"
          :presentation="presentation"
        />
      </div>

      <!-- Header Extras -->
      <div class="list__header__extras">
        <slot name="header_extras"></slot>
      </div>

      <!-- Actions -->
      <div
        v-if="
          actions?.length || props.CSVDownload !== undefined || props.XLSXDownload !== undefined
        "
        class="list__actions"
      >
        <Action
          v-for="(action, actionIndex) in [
            ...(props.CSVDownload !== undefined
              ? [
                  {
                    id: 'csv-download',
                    label: 'Download CSV',
                    icon: 'file-csv',
                    color: 'var(--primary-color)',
                    onActionClick: handleCSVDownload,
                  },
                ]
              : []),
            ...(props.XLSXDownload !== undefined
              ? [
                  {
                    id: 'xlsx-download',
                    label: 'Download XLSX',
                    icon: 'file-excel',
                    color: 'var(--primary-color)',
                    onActionClick: handleXLSXDownload,
                  },
                ]
              : []),
            ...(actions || []),
          ]"
          :key="actionIndex"
          v-bind="action"
          :href="
            presentation === 'minimal' && !action.href
              ? `#/${action.id ? action.id : action.label?.toLowerCase()}`
              : action.href
          "
          @click.prevent="action.onActionClick ? action.onActionClick(undefined, action) : null"
          :size="presentation === 'minimal' ? 'small' : 'regular'"
        />
      </div>
    </div>

    <!-- List Content -->
    <div class="list__content">
      <!-- Loading State -->
      <div v-if="loading" class="list__loading">
        <div class="list__loading-spinner">
          <font-awesome-icon :icon="['fas', 'spinner']" spin />
          <span>Loading...</span>
        </div>
      </div>

      <!-- Empty State -->
      <div v-else-if="!sortedAndFilteredData.length" class="list__empty">
        {{ emptyMessage || 'No data available' }}
      </div>

      <!-- Table -->
      <div v-else class="list__grid">
        <!-- Header -->
        <div class="list__header-row">
          <div
            v-for="column in columns"
            :key="column.key"
            class="list__header-cell"
            :class="[
              `list__column--${column.align || 'left'}`,
              { 'list__column--sortable': column.sortable },
            ]"
            :style="{
              width: column.width,
              minWidth: column.minWidth,
              maxWidth: column.maxWidth,
            }"
            @click="handleSort(column)"
          >
            <div class="list__column-header" :title="column.headerTooltip">
              <span>{{ column.label }}</span>
              <span
                v-if="column.sortable"
                class="list__sort-icon"
                :class="{
                  'list__sort-icon--active': sortColumn?.key === column.key,
                  'list__sort-icon--asc': sortColumn?.key === column.key && sortDirection === 'asc',
                  'list__sort-icon--desc':
                    sortColumn?.key === column.key && sortDirection === 'desc',
                }"
              >
                <font-awesome-icon
                  :icon="[
                    'fas',
                    sortColumn?.key === column.key
                      ? sortDirection === 'asc'
                        ? 'arrow-up-wide-short'
                        : 'arrow-down-wide-short'
                      : 'sort',
                  ]"
                />
              </span>
            </div>
          </div>
        </div>

        <!-- Rows -->
        <template v-for="(row, index) in sortedAndFilteredData" :key="index">
          <div
            class="list__row"
            :class="rowClassList(row)"
            @click.prevent="emit('row-click', row, index)"
            @dblclick.prevent="emit('row-dblclick', row, index)"
          >
            <div
              v-for="column in columns"
              :key="column.key"
              class="list__cell"
              :class="`${getCellClasses(row, column)} list__cell--${column.align || 'left'}`"
              :style="{
                width: column.width,
                minWidth: column.minWidth,
                maxWidth: column.maxWidth,
              }"
              v-tooltip="getCellTooltip(row, column)"
            >
              <template v-if="column.type === 'text'">
                {{ row[column.key] }}
              </template>
              <template v-else-if="column.type === 'email'">
                <a
                  v-if="row['name']"
                  :href="`mailto:${row['name']} <${row[column.key]}>`"
                  @click.stop
                >
                  {{ row[column.key] }}
                </a>
                <a v-else :href="`mailto:${row[column.key]}`" @click.stop>{{ row[column.key] }}</a>
              </template>
              <template v-else-if="column.type === 'number'">
                {{ row[column.key] }}
              </template>
              <template v-else-if="column.type === 'date'">
                {{ formatDate(row[column.key]) }}
              </template>
              <template v-else-if="column.type === 'datetime'">
                {{ formatDatetime(row[column.key]) }}
              </template>
              <template v-else-if="column.type === 'time'">
                {{ formatTime(row[column.key]) }}
              </template>
              <template v-else-if="column.type === 'action'">
                <div class="list__row-actions" v-if="row[column.key]">
                  <Action
                    v-for="(action, actionIndex) in row[column.key] as ListActionProps[]"
                    :key="actionIndex"
                    v-bind="action"
                    v-tooltip="getActionTooltip(action)"
                    :href="
                      presentation === 'minimal' && !action.href
                        ? `#/${action.id ? action.id : action.label?.toLowerCase()}`
                        : action.href
                    "
                    @click.stop="action.onActionClick ? action.onActionClick(row, action) : null"
                    :presentation="presentation"
                    size="small"
                    :color="presentation === 'minimal' ? 'inherit' : ''"
                  />
                </div>
              </template>
              <template v-else-if="column.type === 'checkbox'">
                <Checkbox
                  v-if="row[column.key]"
                  v-bind="row[column.key]"
                  v-model="row[column.key].modelValue"
                  @click.stop="
                    row[column.key].onCheckboxClick
                      ? row[column.key].onCheckboxClick(row, column.key)
                      : null
                  "
                  :presentation="presentation"
                />
              </template>
              <template v-else-if="column.type === 'icon'">
                <div class="list__cell-icon" v-if="row[column.key]">
                  <font-awesome-icon
                    :icon="row[column.key].icon"
                    :style="{ color: row[column.key].color }"
                  />
                </div>
              </template>
            </div>
          </div>
        </template>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import TextInput from './TextInput.vue'
import Action from './Action.vue'
import Checkbox from './Checkbox.vue'
import type {
  ListProps,
  ListEmits,
  ListColumn,
  ListRowData,
  ListDownloadPayload,
} from '../types/list'
import type { ListActionProps } from '../types/action'
import { config } from '../config'
import { utils, writeFileXLSX } from 'xlsx'

const props = withDefaults(defineProps<ListProps>(), {
  presentation: 'default',
  loading: false,
  width: '100%',
})

const emit = defineEmits<ListEmits>()

const filterValue = ref('')
const sortColumn = ref<ListColumn | null>(null)
const sortDirection = ref<'asc' | 'desc'>('asc')
const effectiveFilterValue = ref('')

// Debounce the filter value changes
watch(filterValue, (newValue, _old, onCleanup) => {
  const debouncedValue = newValue.toLowerCase()
  if (debouncedValue.length < 3) {
    effectiveFilterValue.value = ''
    return
  }

  const timeout = setTimeout(() => {
    effectiveFilterValue.value = debouncedValue
  }, 250)
  
  onCleanup(() => clearTimeout(timeout))
})

const handleCSVDownload = () => {
  const csv = []
  const headerRow = []
  const headerKeys = []
  for (const column of props.columns) {
    if (['checkbox', 'icon', 'action'].indexOf(column.type as string) === -1) {
      const columnName = column.label || column.key
      headerRow.push(columnName)
      headerKeys.push(column.key)
    }
  }
  csv.push(headerRow.join(','))

  for (const row of props.data) {
    const rowData: string[] = []
    headerKeys.forEach((key) => {
      const value = row[key]
      if (value == null) {
        rowData.push('')
      } else {
        rowData.push(value)
      }
    })
    csv.push('"' + rowData.join('","') + '"')
  }

  const blob = new Blob([csv.join('\n')], { type: 'text/csv;charset=utf-8;' })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  const downloadName = props.CSVDownload || 'data'
  a.download = downloadName.toLowerCase().endsWith('.csv') ? downloadName : downloadName + '.csv'
  a.click()
  URL.revokeObjectURL(url)
  emit('download', { type: 'csv', fileName: a.download } as ListDownloadPayload)
}

const handleXLSXDownload = () => {
  if (!props.XLSXDownload) return

  const downloadName = props.XLSXDownload || 'data'
  const fileName = downloadName.toLowerCase().endsWith('.xlsx')
    ? downloadName
    : downloadName + '.xlsx'

  const workbook = utils.book_new()

  const columns = []
  for (const column of props.columns) {
    if (['checkbox', 'icon', 'action'].indexOf(column.type as string) === -1) {
      columns.push({
        field: column.key,
        label: column.label || column.key,
      })
    }
  }

  const rows = []
  for (const row of props.data) {
    const rowData: any = {}
    columns.forEach((column) => {
      const value = row[column.field]
      if (value == null) {
        rowData[column.field] = ''
      } else {
        rowData[column.field] = value
      }
    })
    rows.push(rowData)
  }

  const sheetName =
    downloadName.lastIndexOf('.') !== -1
      ? downloadName.substring(0, downloadName.lastIndexOf('.')) || 'Sheet1'
      : downloadName
  const worksheet = utils.json_to_sheet(rows)
  utils.book_append_sheet(
    workbook,
    worksheet,
    sheetName.length > 31 ? sheetName.substring(0, 31) : sheetName,
  )
  writeFileXLSX(workbook, fileName)
  emit('download', { type: 'xlsx', fileName: fileName } as ListDownloadPayload)
}

const handleFilter = () => {
  // No need to do anything here as the watch handles the filtering
}

const handleSort = (column: ListColumn) => {
  if (!column.sortable) return

  if (sortColumn.value?.key === column.key) {
    sortDirection.value = sortDirection.value === 'asc' ? 'desc' : 'asc'
  } else {
    sortColumn.value = column
    sortDirection.value = 'asc'
  }
}

const formatDate = (date: string | Date) => {
  try {
    const dateFormatter = new Intl.DateTimeFormat(
      config.dateFormat.locale,
      config.dateFormat.options,
    )
    if (typeof date === 'string' && !isNaN(Date.parse(date))) {
      return dateFormatter.format(new Date(date))
    } else if (date instanceof Date && !isNaN(date.getTime())) {
      return dateFormatter.format(date)
    }
  } catch (e) {
    console.error('Error formatting date:', e)
  }
  return '-'
}

const formatDatetime = (date: string | Date) => {
  try {
    const dateFormatter = new Intl.DateTimeFormat(
      config.datetimeFormat.locale,
      config.datetimeFormat.options,
    )
    if (typeof date === 'string' && !isNaN(Date.parse(date))) {
      return dateFormatter.format(new Date(date))
    } else if (date instanceof Date && !isNaN(date.getTime())) {
      return dateFormatter.format(date)
    }
  } catch (e) {
    console.error('Error formatting datetime:', e)
  }
  return '-'
}

const formatTime = (date: string | Date) => {
  try {
    const dateFormatter = new Intl.DateTimeFormat(
      config.timeFormat.locale,
      config.timeFormat.options,
    )
    if (typeof date === 'string' && !isNaN(Date.parse(date))) {
      return dateFormatter.format(new Date(date))
    } else if (date instanceof Date && !isNaN(date.getTime())) {
      return dateFormatter.format(date)
    }
  } catch (e) {
    console.error('Error formatting time:', e)
  }
  return '-'
}

const rowClassList = (row: any): Record<string, boolean> => {
  const classList: Record<string, boolean> = {
    'list__row--selected': row.selected,
    'list__row--minimal': props.presentation === 'minimal',
  }

  if (row.class) {
    const rowClassName = row.class as string
    classList[`list__row--${rowClassName}`] = true
  }

  return classList
}

const filteredData = computed(() => {
  if (!effectiveFilterValue.value) return props.data

  const filterableColumns = props.columns.filter((col) => col.filterable)
  if (filterableColumns.length === 0) return props.data

  return props.data.filter((row) => {
    // Always include rows excluded from filtering
    if (row.excludeFromFilter) return true

    return filterableColumns.some((column) => {
      const value = row[column.key]
      if (value == null) return false

      const checkboxValue = value?.modelValue ? 'yes' : 'no'

      switch (column.type) {
        case 'text':
        case 'email':
          return String(value).toLowerCase().includes(effectiveFilterValue.value)
        case 'number':
          return String(value).includes(effectiveFilterValue.value)
        case 'date':
          return formatDate(value).toLowerCase().includes(effectiveFilterValue.value)
        case 'datetime':
          return formatDatetime(value).toLowerCase().includes(effectiveFilterValue.value)
        case 'checkbox':
          return checkboxValue.includes(effectiveFilterValue.value)
        default:
          return false
      }
    })
  })
})

const sortedAndFilteredData = computed(() => {
  // Separate fixed rows (excluded from sorting) from sortable rows
  const fixedRows: ListRowData[] = []
  const sortableRows: ListRowData[] = []

  filteredData.value.forEach((row) => {
    if (row.excludeFromSort) {
      fixedRows.push(row)
    } else {
      sortableRows.push(row)
    }
  })

  // Sort only the sortable rows if sorting is active
  let sortedRows = sortableRows
  if (sortColumn.value) {
    sortedRows = [...sortableRows].sort((a, b) => {
      const column = sortColumn.value!
      const aValue = a[column.key]
      const bValue = b[column.key]
      const sortOrder = sortDirection.value === 'asc' ? 1 : -1

      // Handle different data types
      if (column.type === 'date' || column.type === 'datetime' || column.type === 'time') {
        if (
          typeof aValue === 'string' &&
          !isNaN(Date.parse(aValue)) &&
          typeof bValue === 'string' &&
          !isNaN(Date.parse(bValue))
        ) {
          const dateA = new Date(aValue).getTime()
          const dateB = new Date(bValue).getTime()
          return (dateA - dateB) * sortOrder
        }
        if (typeof aValue === 'string' && !isNaN(Date.parse(aValue))) {
          return sortOrder
        }
        if (typeof bValue === 'string' && !isNaN(Date.parse(bValue))) {
          return sortOrder * -1
        }
        return 0
      }

      if (column.type === 'number') {
        return (aValue - bValue) * sortOrder
      }

      if (column.type === 'checkbox') {
        const aChecked = aValue?.modelValue || false
        const bChecked = bValue?.modelValue || false
        return (aChecked - bChecked) * sortOrder
      }

      // Default string comparison for text and other types
      const stringA = String(aValue || '').toLowerCase()
      const stringB = String(bValue || '').toLowerCase()
      return stringA.localeCompare(stringB) * sortOrder
    })
  }

  // Separate fixed rows by their fixed position
  const topFixedRows = fixedRows.filter((row) => row.fixed === 'top')
  const bottomFixedRows = fixedRows.filter((row) => row.fixed === 'bottom')

  // Return rows in order: top fixed, sorted, bottom fixed
  return [...topFixedRows, ...sortedRows, ...bottomFixedRows]
})

defineExpose({
  focus: () => {
    const filterInput = document.querySelector('.list__filter input')
    if (filterInput) {
      ;(filterInput as HTMLInputElement).focus()
    }
  },
  blur: () => {
    const filterInput = document.querySelector('.list__filter input')
    if (filterInput) {
      ;(filterInput as HTMLInputElement).blur()
    }
  },
  clearFilter: () => {
    filterValue.value = ''
  },
})

const getCellClasses = (row: any, column: ListColumn) => {
  if (!column.cellClasses) return ''

  return Object.entries(column.cellClasses)
    .map(([key, value]) => {
      return value(row[column.key]) ? key : ''
    })
    .join(' ')
}

function getCellTooltip(row: any, column: ListColumn) {
  if (!column.cellTooltip) return false

  const value = row[column.key]
  if (value == null) return false

  switch (column.type) {
    case 'text':
    case 'number':
    case 'email':
      return { content: String(value) }
    case 'date':
      return { content: formatDate(value) }
    case 'datetime':
      return { content: formatDatetime(value) }
    case 'time':
      return { content: formatTime(value) }
    default:
      return false
  }
}

function getActionTooltip(action: ListActionProps) {
  const tooltip = action?.tooltip
  if (!tooltip) return false
  return { content: tooltip, html: true }
}
</script>

<style scoped>
.list {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  width: v-bind('props.width');

  &.list--minimal {
    line-height: 1.2rem;

    .list__header-row {
      .list__header-cell {
        padding: 0.25rem 0.5rem;
      }
    }
  }
}

.list__header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
}

.list__filter {
  flex: 1;
  max-width: 300px;
}

.list__actions {
  display: flex;
  gap: 0.5rem;
  align-self: flex-end;
}
:deep(.list__actions > .action--small) {
  padding-bottom: 0.25rem;
}

.list__content {
  position: relative;
  min-height: 200px;
}

.list__loading {
  position: absolute;
  inset: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  background: rgba(255, 255, 255, 0.8);
}

.list__loading-spinner {
  font-size: 2rem;
  border-top-color: transparent;

  .fa-spin {
    --fa-animation-duration: 5s;
    margin-right: 0.5rem;
  }
}

.list__empty {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 2rem;
  color: var(--text-secondary);
}

.list__grid {
  display: grid;
  grid-template-columns: repeat(var(--columns-count), 1fr);
  width: 100%;
}

.list__header-row {
  display: grid;
  grid-template-columns: var(--list-columns-grid, 1fr);
  border-bottom: 2px solid rgba(from var(--border-color) r g b / 0.8);
  background-color: rgba(from var(--text-color) r g b / 0.8);
  color: rgba(255, 255, 255, 0.9);

  .list__header-cell {
    padding: 0.75rem;
    text-align: left;
    font-weight: 600;
  }
}

.list__row {
  display: grid;
  grid-template-columns: var(--list-columns-grid, 1fr);
  cursor: pointer;
  transition: all 0.2s;
  border-bottom: 1px solid rgba(from var(--border-color) r g b / 0.2);
}

.list__row:hover {
  background-color: var(--hover-color);
}

.list__row--selected {
  background-color: var(--selected-bg-color-light);
  color: var(--selected-color);
}

.list__row--selected:hover {
  background-color: var(--selected-bg-color-dark);
}

.list__cell {
  padding: 0.75rem;
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
}

.list__column--sortable {
  cursor: pointer;
}

.list__column--sortable:hover {
  background-color: rgba(from var(--text-color) r g b / 0.8);
}

.list__column--right,
.list__cell--right,
.list__column--right > div,
.list__cell--right > div {
  white-space: nowrap;
  justify-content: flex-end;
  text-align: right;
}

.list__column--center,
.list__cell--center,
.list__column--center > div,
.list__cell--center > div {
  white-space: nowrap;
  justify-content: center;
  text-align: center;
}

.list__row-actions {
  display: flex;
  gap: 0.5rem;
}

.list__cell-icon {
  padding: 0.125rem 0;
}

.list__column-header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.list__sort-icon {
  color: rgba(255, 255, 255, 0.5);
  transition: color 0.2s;
}

.list__sort-icon--active {
  color: rgba(255, 255, 255, 0.8);
}

.list__column--sortable:hover .list__sort-icon {
  color: rgba(255, 255, 255, 0.8);
}

.list__sort-icon--asc,
.list__sort-icon--desc {
  transform: none;
}

/* Minimal Presentation */
.list--minimal .list__header-cell {
  padding: 0.5rem;
  font-size: 0.875rem;
}

.list--minimal .list__cell {
  padding: 0 0.5rem;
  font-size: 0.875rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}
</style>
