<script lang="ts" setup>
import { computed, ref, watch } from 'vue';

import { getDataColScreenX } from '../composables/useAutoMeasure';
import {
  collectColumnUniqueValues,
  isColumnFilterActive,
} from '../composables/filterSort';
import type {
  SortDirection,
  SpreadsheetColumnConfig,
  SpreadsheetHeaderConfig,
  SpreadsheetViewState,
} from '../types';
import { COLUMN_LETTER_HEIGHT, HEADER_TITLE_HEIGHT } from '../types';

import HeaderColumnFilter from './HeaderColumnFilter.vue';

const props = defineProps<{
  headers: SpreadsheetHeaderConfig[];
  columns: SpreadsheetColumnConfig[];
  rows: Record<string, unknown>[];
  colWidths: number[];
  scrollLeft: number;
  fixedCols: number[];
  fixedWidth: number;
  viewportWidth: number;
  viewState: SpreadsheetViewState;
  enableFilter?: boolean;
  isDark?: boolean;
  resolveDisplayValue: (
    viewRow: number,
    colCfg: SpreadsheetColumnConfig,
    row?: Record<string, unknown>,
  ) => unknown;
}>();

const emit = defineEmits<{
  sort: [col: number, direction: SortDirection];
  clearSort: [];
  clearFilter: [col: number];
  'update:columnFilter': [col: number, checkedValues: string[] | undefined];
}>();

const uniqueCache = ref<Record<number, string[]>>({});
const loadingCols = ref<Set<number>>(new Set());
const rowsVersion = ref(0);

const filterableHeaders = computed(() =>
  props.headers.filter((h) => h.filterable !== false),
);

function invalidateCache() {
  uniqueCache.value = {};
  rowsVersion.value += 1;
}

watch(
  () => props.rows.length,
  invalidateCache,
);

function ensureUniqueValues(col: number) {
  if (uniqueCache.value[col]?.length !== undefined) return;
  loadingCols.value = new Set([...loadingCols.value, col]);
  uniqueCache.value[col] = collectColumnUniqueValues(
    props.rows,
    col,
    props.columns,
    props.resolveDisplayValue,
  );
  const next = new Set(loadingCols.value);
  next.delete(col);
  loadingCols.value = next;
}

function onFilterOpen(col: number) {
  ensureUniqueValues(col);
}

function getUniqueValues(col: number): string[] {
  return uniqueCache.value[col] ?? [];
}

function isLoadingCol(col: number): boolean {
  return loadingCols.value.has(col);
}

function headerLeft(col: number): number {
  return getDataColScreenX(
    col,
    props.colWidths,
    props.scrollLeft,
    props.fixedCols,
    props.fixedWidth,
  );
}

function isFilterActive(col: number): boolean {
  return isColumnFilterActive(props.viewState.filters[col]);
}

function sortDirection(col: number): SortDirection | null {
  if (props.viewState.sort?.col === col) {
    return props.viewState.sort.direction;
  }
  return null;
}
</script>

<template>
  <div
    v-if="enableFilter !== false"
    class="header-filter-layer"
    :style="{ width: `${viewportWidth}px`, height: `${COLUMN_LETTER_HEIGHT + HEADER_TITLE_HEIGHT}px` }"
  >
    <HeaderColumnFilter
      v-for="header in filterableHeaders"
      :key="header.col"
      :active="isFilterActive(header.col)"
      :checked-values="viewState.filters[header.col]?.checkedValues"
      :col="header.col"
      :is-dark="isDark"
      :left="headerLeft(header.col)"
      :loading-values="isLoadingCol(header.col)"
      :sort-direction="sortDirection(header.col)"
      :title="header.title"
      :top="COLUMN_LETTER_HEIGHT"
      :unique-values="getUniqueValues(header.col)"
      :width="colWidths[header.col] ?? 100"
      @clear-filter="emit('clearFilter', header.col)"
      @clear-sort="emit('clearSort')"
      @open="onFilterOpen(header.col)"
      @sort="(dir) => emit('sort', header.col, dir)"
      @update:checked-values="
        (v) => emit('update:columnFilter', header.col, v)
      "
    />
  </div>
</template>

<style scoped lang="scss">
.header-filter-layer {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
  z-index: 3;
}
</style>
