<template>
  <div class="list-table-v2" :style="tableOuterContainerStyle">
    <!-- Debug info -->
    <div v-if="debug" class="debug-info">
      <json-view
        :data="debugInfo"
        :deep="1"
        :virtual="true"
        :show-icon="true"
        :show-double-quotes="false"
        :height="200"
        inline
        collapse-fully-inline-node
      />
    </div>

    <!-- Main table container with ResizeObserver for width tracking -->
    <div ref="tableContainerRef" class="table-container" :style="tableContainerStyle">
      <!-- Header row (fixed) -->
      <div class="table-header" :style="{ height: `${resolvedHeaderHeight}px` }">
        <div v-for="col in visibleColumns" :key="col.key" class="header-cell" :style="getColumnStyle(col)">
          <div class="header-cell-content">
            <slot :name="`header-${col.key}`">
              <span class="header-cell-text" :style="getTextStyle(col)">{{ getHeaderText(col) }}</span>
            </slot>
          </div>
        </div>
      </div>

      <!-- Scrollable body -->
      <div ref="scrollBodyRef" class="table-body" :style="{ height: `${viewportHeight}px` }" @scroll="handleScroll">
        <!-- Total height spacer -->
        <div v-if="pageData.length" class="table-spacer" :style="{ height: `${virtualTotalHeight}px` }">
          <!-- Visible rows -->
          <div
            v-for="virt in visibleRows"
            :key="lodash.get(getRow(virt.index), rowKey) ?? virt.index"
            class="table-row"
            :style="{
              transform: `translateY(${virt.offsetY}px)`,
              height: `${virt.height}px`,
            }"
            @click="handleRowClick(virt.index)"
          >
            <div
              v-for="col in visibleColumns"
              :key="col.key"
              class="table-cell"
              :style="getColumnStyle(col)"
              @click="handleCellClick(virt.index, col)"
            >
              <div class="table-cell-content">
                <slot :name="col.key" :row="getRow(virt.index)">
                  <span class="cell-text" :style="getTextStyle(col)">{{
                    getRow(virt.index) ? formatCellValue(getRow(virt.index)!, col) : "--"
                  }}</span>
                </slot>
              </div>
            </div>
          </div>
        </div>

        <div v-else class="table-spacer-empty">
          <NoData />
        </div>
      </div>
    </div>

    <!-- Pagination -->
    <div v-if="page" class="my-pagination">
      <el-pagination
        :small="state.size.size == 'small'"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
        :current-page="example.page"
        :page-sizes="[10, 20, 50, 100, 200]"
        :page-size="example.size"
        layout="total, sizes, prev, pager, next, jumper"
        :total="totalRows"
      />
    </div>
  </div>
</template>

<script lang="tsx" setup generic="T">
import { ref, computed, onMounted, onUnmounted, toRef, CSSProperties } from "vue";
import { useStore } from "vuex";
import { useI18n } from "vue3-i18n";
import * as lodash from "lodash-es";
import type { ListTableColumn } from "./types";
import { usePretextColumnWidths } from "./usePretextColumnWidths";
import { resolveRowHeights } from "./usePretextRowHeights";
import { useVirtualRows } from "./useVirtualRows";
import { formatTimestampFromValue } from "../../../../plugs/composables";
import { match, P } from "ts-pattern";
import { JsonView } from "noob-mengyxu";
import NoData from "./no-data.vue";
import { ListTableProps } from "./types";

const DEFAULT_FONT = "14px Microsoft YaHei, sans-serif";
const DEFAULT_HEADER_FONT = "bold 16px Microsoft YaHei, sans-serif";
const DEFAULT_TEXT_MAX_WIDTH = 400;
const DEFAULT_LINE_HEIGHT = 20;
const DEFAULT_TOP_BOTTOM_PADDING = 8;
const DEFAULT_LEFT_RIGHT_PADDING = 4;

const { t } = useI18n();
const { state } = useStore();

// =============================================================================
// Container refs and dimensions
// =============================================================================
const tableContainerRef = ref<HTMLElement | null>(null);
const scrollBodyRef = ref<HTMLElement | null>(null);
const containerWidth = ref(800);
const viewportHeight = ref(400);
let resizeObserver: ResizeObserver | null = null;

// =============================================================================
// Props - same interface as original list-table-v2.vue
// =============================================================================

type Props = ListTableProps<T>;

const props = withDefaults(defineProps<Props>(), {
  columns: () => [],
  height: undefined,
  minHeight: 300,
  maxHeight: undefined,
  example: () => ({}),
  rowKey: "id",
  selectKey: undefined,
  treeProps: undefined,
  lazy: false,
  border: false,
  rowHeight: undefined,
  headerHeight: 44,
  font: DEFAULT_FONT,
  debug: false,
});

const sizeRegex = /(\d+(?:\.\d+)?(?:px|em|rem|pt|%|vh|vw))/i;
const makeBold = (font: string) => {
  // 1. Check if 'bold' or '700' already exists to avoid duplicates
  if (/\bbold\b|\b[7-9]00\b/.test(font)) {
    return font;
  }

  // 2. Regex to find the font size (the first number followed by a CSS unit)
  // This looks for the mandatory size component

  if (sizeRegex.test(font)) {
    // Insert 'bold ' immediately before the size
    return font.replace(sizeRegex, "bold $1");
  }

  // Fallback: If the string is malformed, appending to front is the safest bet
  return `bold ${font}`;
};

const headerFont = computed(() => props.headerFont ?? makeBold(props.font) ?? DEFAULT_HEADER_FONT);

const emit = defineEmits<{
  (e: "query"): void;
  (e: "selection-change", selection: any[]): void;
  (e: "row-click", row: T): void;
  (e: "cell-click", row: T, column: ListTableColumn<T>, ...args: any[]): void;
}>();

// =============================================================================
// Data and columns as computed refs for hooks
// =============================================================================

const pageData = computed<T[]>(() => {
  if (props.page) {
    return props.data.data;
  }
  return props.data ?? [];
});

const totalRows = computed(() => {
  if (!props.page) return props.data?.length ?? 0;
  return props.data.total ?? 0;
});

// Helper function for template to safely access row data
function getRow(index: number): T | undefined {
  return pageData.value[index];
}

const columnsRef = toRef(props, "columns");

// Pass computed pageData to hooks
const pageDataRef = toRef(pageData);

// =============================================================================
// Cell formatting
// =============================================================================

const isNullish = (value: any) => value === undefined || value === null || value === "";
const getHeaderText = (col: ListTableColumn<T>): string => {
  if (col.name) return col.name;
  if (col.i18n) return t(col.i18n);
  return col.key;
};

const formatCellValue = (row: T, col: ListTableColumn<T>): string => {
  const value = (row as any)[col.dataKey || col.key];

  if (col.dict) {
    return formatByDict(col.dict, value);
  }
  if (col.timestamp) {
    if ((typeof value !== "string" && typeof value !== "number") || value === "") {
      return "--";
    }
    return formatTimestampFromValue(value, col.timestamp);
  }
  if (col.filesize) {
    return formatFileSize(value);
  }

  if (value === undefined || value === null || value === "") {
    return "--";
  }
  return String(value);
};

const display = (value: any): string => (isNullish(value) ? "--" : String(value));

const formatFileSize = (value: any): string => {
  const k = value / 1024;
  if (k < 1) return value + "B";
  const m = k / 1024;
  if (m < 1) return k.toFixed(2) + "K";
  const g = m / 1024;
  if (g < 1) return m.toFixed(2) + "M";
  return g.toFixed(2) + "G";
};

const formatByDict = (dictKey: string, cellData: any): string => {
  if (!dictKey) return `UNKNOWN_DICT: ${dictKey}`;

  const mapping = state.dict[dictKey];
  if (mapping == null) return `UNKNOWN_DICT: ${dictKey}`;
  return display(mapping[cellData]);
};

// =============================================================================
// Wire hooks
// =============================================================================

// Column width computation via pretext
const { computedConfigs, totalFlexBasis, columnWidths } = usePretextColumnWidths(
  pageDataRef,
  columnsRef,
  containerWidth,
  formatCellValue,
  t,
  { font: props.font, headerFont: headerFont.value }
);

// Row heights via pretext - use actual column widths from flexbox algorithm
const { rowHeights, cellHeights } = resolveRowHeights(
  pageDataRef,
  columnsRef,
  columnWidths,
  DEFAULT_TEXT_MAX_WIDTH,
  formatCellValue,
  {
    font: props.font,
    lineHeight: DEFAULT_LINE_HEIGHT,
    padding: {
      top: DEFAULT_TOP_BOTTOM_PADDING,
      bottom: DEFAULT_TOP_BOTTOM_PADDING,
      left: DEFAULT_LEFT_RIGHT_PADDING + 1,
      right: DEFAULT_LEFT_RIGHT_PADDING,
    },
  },
  {
    debug: props.debug,
    fixedRowHeight: props.rowHeight,
  }
);

// Virtualizer (needs rowHeights as number[] and viewportHeight)
const virtualizer = useVirtualRows(rowHeights, viewportHeight, { overscan: 5 });

// Destructure for template auto-unwrap (Vue 3 auto-unwraps top-level refs)
const { visibleRows, totalHeight: virtualTotalHeight, onScroll } = virtualizer;

// =============================================================================
// Visible columns (non-fixed columns only for now - fixed columns in PR4)
// =============================================================================
const visibleColumns = computed(() => {
  return props.columns.filter((col) => !col.fixed);
});

// =============================================================================
// Computed styles
// =============================================================================

const parseCssSize = (size: number | string | undefined, nonZero: boolean = true) => {
  const num = typeof size === "string" ? parseFloat(size) : size;
  return match(size)
    .with(P.string.regex(sizeRegex), (str) => str)
    .with(
      P.string,
      (_) => num != null && isFinite(num) && (nonZero ? num > 0 : num >= 0),
      (_) => `${num}px`
    )
    .with(
      P.number,
      (num) => isFinite(num) && (nonZero ? num > 0 : num >= 0),
      (num) => num
    )
    .otherwise(() => undefined);
};

const tableContainerStyle = computed(() => {
  const style: CSSProperties = {
    display: "flex",
    flexDirection: "column" as const,
    width: "100%",
    overflow: "hidden" as const,
  };

  style.maxHeight = parseCssSize(props.maxHeight);
  if ((style.height = parseCssSize(props.height))) {
    style.flex = "unset";
  } else {
    style.minHeight = parseCssSize(props.minHeight);
  }

  return style;
});

const tableOuterContainerStyle = computed(() => {
  const style: CSSProperties = {};
  if (props.height !== undefined) {
    style.flex = "unset";
  }

  return style;
});

// =============================================================================
// Column styling
// =============================================================================
function getColumnStyle(col: ListTableColumn<T>): CSSProperties {
  const config = computedConfigs.value.find((c) => c.key === col.key);
  const explicitWidth = col.width !== undefined && col.width !== "auto" ? Number(col.width) : undefined;
  const explicitMaxWidth =
    col.maxWidth !== undefined && Number.isFinite(Number(col.maxWidth)) ? Number(col.maxWidth) : undefined;
  const align = match(col.align)
    .returnType<CSSProperties>()
    .with("left", () => ({ alignSelf: "flex-start", textAlign: "left" }))
    .with("right", () => ({ alignSelf: "flex-end", textAlign: "right" }))
    .otherwise(() => ({ textAlign: "center" }));

  if (explicitWidth !== undefined && Number.isFinite(explicitWidth) && explicitWidth > 0) {
    return {
      flex: `0 0 ${explicitWidth}px`,
      width: `${explicitWidth}px`,
      minWidth: `${explicitWidth}px`,
      ...align,
      ...(explicitMaxWidth !== undefined ? { maxWidth: `${explicitMaxWidth}px` } : {}),
    };
  }

  // Find the column index to get the computed width
  const colIndex = columnsRef.value.findIndex((c) => c.key === col.key);
  const computedWidth = colIndex >= 0 ? columnWidths.value[colIndex] : 0;
  const fallbackBasis = Number(col.minWidth ?? 120);

  if (!config) {
    return {
      flexGrow: "1.1",
      flexShrink: "1.1",
      flexBasis: `${fallbackBasis}px`,
      minWidth: `${Math.max(fallbackBasis, 50)}px`,
      ...align,
      ...(explicitMaxWidth !== undefined ? { maxWidth: `${explicitMaxWidth}px` } : {}),
    };
  }

  // Apply the computed flex factors to the real cells so the row can consume
  // any remaining space after fixed-width columns and scrollbar width.
  const flexBasis = computedWidth > 0 ? computedWidth : config.flexBasis;
  return {
    flexGrow: `${config.flexGrow}`,
    flexShrink: `${config.flexShrink}`,
    flexBasis: `${flexBasis}px`,
    minWidth: `${config.minWidth}px`,
    ...align,
    ...(explicitMaxWidth !== undefined ? { maxWidth: `${explicitMaxWidth}px` } : {}),
  };
}

function getTextStyle(col: ListTableColumn<T>): CSSProperties {
  return {
    maxWidth: `${col.maxTextWidth ?? DEFAULT_TEXT_MAX_WIDTH}px`,
  };
}

// =============================================================================
// Header height resolution
// =============================================================================
const resolvedHeaderHeight = computed(() => {
  return props.headerHeight ?? 44;
});

// =============================================================================
// Scroll handling
// =============================================================================
function handleScroll(event: Event) {
  const target = event.target as HTMLElement;
  onScroll(target.scrollTop);
}

// =============================================================================
// Event handlers
// =============================================================================
function handleRowClick(index: number) {
  const row = pageData.value[index];
  if (row) {
    emit("row-click", row);
  }
}

function handleCellClick(index: number, col: ListTableColumn<T>) {
  const row = pageData.value[index];
  if (row) {
    emit("cell-click", row, col, null, null);
  }
}

const handleSizeChange = (val: number) => {
  props.example.size = val;
  emit("query");
};

const handleCurrentChange = (val: number) => {
  props.example.page = val;
  emit("query");
};

// ============================================================================
// Debug Info
// ============================================================================
const debugInfo = computed(() => ({
  containerWidth,
  virtualTotalHeight,
  visibleRows: visibleRows.value.map((entry) => entry.height),
  cellHeights: cellHeights.value?.map((rowCellHeights) =>
    rowCellHeights.map((entry) => (entry ? `${entry.isCustomRenderer ? "*" : ""}${entry.height}` : "null"))
  ),
  totalRows,
  columnWidths: columnWidths.value.map((w) => Math.round(w)),
  columnConfigs: computedConfigs,
}));

// =============================================================================
// ResizeObserver for container dimensions
// =============================================================================
onMounted(() => {
  // Initial measurement
  if (tableContainerRef.value) {
    const rect = tableContainerRef.value.getBoundingClientRect();
    containerWidth.value = rect.width;
    viewportHeight.value = rect.height - resolvedHeaderHeight.value;
  }

  // ResizeObserver for container width changes
  resizeObserver = new ResizeObserver(
    lodash.debounce((entries) => {
      for (const entry of entries) {
        const { width, height } = entry.contentRect;
        containerWidth.value = width;
        viewportHeight.value = height - resolvedHeaderHeight.value;
      }
    }, 50)
  );

  if (tableContainerRef.value) {
    resizeObserver.observe(tableContainerRef.value);
  }
});

onUnmounted(() => {
  if (resizeObserver) {
    resizeObserver.disconnect();
    resizeObserver = null;
  }
});
</script>

<style lang="scss" scoped>
.debug-info {
  font-size: 9px;
  padding: 4px 8px;
  background: #f5f5f5;
  border-bottom: 1px solid #ddd;
  font-family: monospace;
  white-space: pre;
}

.list-table-v2 {
  display: flex;
  flex-direction: column;
  flex: 1;
  overflow: hidden;
  position: relative;
}

.table-container {
  display: flex;
  flex: 1;
  min-height: 0;
  flex-direction: column;
  overflow: hidden;
}

.table-header {
  display: flex;
  flex-shrink: 0;
  background: v-bind("state.style.tableHeadBg");
  border-bottom: 1px solid v-bind("state.style.tableBorderColor");
  box-sizing: border-box;
}

.header-cell {
  display: flex;
  flex: 1 1 120px;
  align-items: center;
  justify-content: center;
  padding: v-bind("`${DEFAULT_TOP_BOTTOM_PADDING}px ${DEFAULT_LEFT_RIGHT_PADDING}px`");
  box-sizing: border-box;
  border-right: 1px solid v-bind("state.style.tableBorderColor");
  overflow: hidden;
  word-break: break-word;
  min-width: 0;

  &:last-child {
    border-right: none;
  }
}

.header-cell-content {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
  max-width: 100%;
  overflow: hidden;
  box-sizing: border-box;
}

.header-cell-text {
  display: block;
  width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: v-bind("state.style.tableColor");
  font: v-bind("headerFont");
}

.table-body {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  background: v-bind("state.style.tableBg");
  position: relative;
}

.table-spacer {
  position: relative;
  width: 100%;
}

.table-spacer-empty {
  position: relative;
  width: 100%;
  height: 100%;
}

.table-row {
  position: absolute;
  left: 0;
  right: 0;
  display: flex;
  border-bottom: 1px solid v-bind("state.style.tableBorderColor");
  box-sizing: border-box;
  background: v-bind("state.style.tableBg");
  transition: background-color 0.15s;

  &:hover {
    background: v-bind("state.style.tableCurBg");
  }
}

.table-cell {
  display: flex;
  flex: 1 1 120px;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 8px 4px;
  box-sizing: border-box;
  border-right: 1px solid v-bind("state.style.tableBorderColor");
  overflow: hidden;
  word-break: break-word;
  min-height: 100%;
  min-width: 0;

  &:last-child {
    border-right: none;
  }
}

.table-cell-content {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
  max-width: 100%;
  overflow: hidden;
  box-sizing: border-box;
}

.header-cell-content > *,
.table-cell-content > * {
  min-width: 0;
  max-width: 100%;
}

.cell-text {
  display: block;
  width: 100%;
  overflow: visible;
  text-overflow: clip;
  white-space: normal;
  word-break: break-word;
  color: v-bind("state.style.tableColor");
  font: v-bind("props.font");
  line-height: v-bind("`${DEFAULT_LINE_HEIGHT}px`");
}

.my-pagination {
  display: flex;
  justify-content: center;
  padding-top: 5px;
  flex-shrink: 0;
}

.my-pagination * {
  --el-pagination-bg-color: v-bind("state.style.bodyBg") !important;
  --el-pagination-disabled-bg-color: v-bind("state.style.bodyBg") !important;
  --el-pagination-text-color: v-bind("state.style.color") !important;
  --el-pagination-button-color: v-bind("state.style.color") !important;
  --el-pagination-button-disabled-bg-color: v-bind("state.style.bodyBg") !important;
}
</style>
