// PivotDataView.vue - FULL CODE (stable mapping, no aggregation, data will show correctly)
<template>
  <div>
    <!-- TimeOption Component -->
    <TimeOption
      v-bind="{
        pagename: vpagename,
        api: props.api,
        timeopt: props.timeopt,
        filter: optfilter,
        importdata: props.importdata,
        newDataAvailable: newDataAvailable,
        params: vparams,
      }"
      ref="timeopt"
      @option="timeOption"
      @excel="exportExcel"
      @add="insert"
      @manual-refresh="manualRefresh"
      @refresh-data="refreshData"
      @import="openImportModal"
      class="mb-3"
      v-if="props.timeopt"
    />

    <!-- Filters Display -->
    <div
      class="field is-grouped is-grouped-multiline pl-2 mb-3"
      v-if="filters && filters.length > 0"
    >
      <div class="control mr-5">
        <a
          class="button is-primary is-small has-text-white has-text-weight-bold"
          @click="clearFilters()"
        >
          <span class="fs-14">Xóa lọc</span>
        </a>
      </div>
      <div class="control pr-2 mr-5">
        <span class="icon-text">
          <span class="icon">
            <Icon
              name="mdi:sigma"
              :size="20"
              class="has-text-primary"
            />
          </span>
          <span class="fsb-18 has-text-primary">{{ pivotData.length }}</span>
        </span>
      </div>
      <div
        class="control"
        v-for="(v, i) in filters"
        :key="i"
      >
        <div class="tags has-addons is-marginless">
          <a
            class="tag is-primary has-text-white is-marginless"
            @click="showCondition(v)"
          >
            {{ v.label.indexOf(">") >= 0 ? $stripHtml(v.label, 30) : v.label }}
          </a>
          <a
            class="tag is-delete is-marginless has-text-black-5"
            @click="removeFilter(i)"
          ></a>
        </div>
        <span class="help has-text-black-5">
          {{
            v.sort
              ? v.sort
              : v.select
                ? "[" + (v.select.length > 0 ? $stripHtml(v.select[0], 20) : "") + "...∑" + v.select.length + "]"
                : v.condition
          }}
        </span>
      </div>
    </div>

    <!-- Pivot Table -->
    <div
      class="table-container mb-0"
      ref="container"
      v-if="pivotData.length > 0"
    >
      <table
        class="table is-fullwidth is-bordered is-narrow is-hoverable"
        :style="tableStyle"
      >
        <thead>
          <tr
            v-for="(level, levelIndex) in maxHeaderDepth"
            :key="`header-level-${levelIndex}`"
          >
            <template v-if="levelIndex === 0">
              <th
                v-for="(rowField, idx) in rowFields"
                :key="`row-header-group-${idx}`"
                :style="headerStyle"
                :rowspan="maxHeaderDepth"
                class="is-sticky-left"
                @click="showFieldMenu(rowField)"
              >
                <div :style="rowField.dropStyle">
                  <a v-if="rowField.label.indexOf('<') < 0">{{ rowField.label }}</a>
                  <a v-else>
                    <component
                      :is="dynamicComponent(rowField.label)"
                      :row="{}"
                      @clickevent="clickEvent($event, {}, rowField)"
                    />
                  </a>
                </div>
              </th>
            </template>

            <th
              v-for="(node, nodeIdx) in getNodesAtLevel(levelIndex)"
              :key="`header-node-${levelIndex}-${nodeIdx}`"
              :colspan="getColspan(node)"
              :rowspan="getRowspan(node, levelIndex)"
              :style="getHeaderStyle(node, levelIndex)"
              style="z-index: 1"
              class="has-text-centered"
            >
              {{ node.label }}
            </th>
          </tr>
        </thead>

        <tbody>
          <tr
            v-for="(row, rowIndex) in pivotData"
            :key="`row-${rowIndex}`"
          >
            <td
              v-for="(rowField, idx) in rowFields"
              :key="`row-cell-${rowIndex}-${idx}`"
              :style="getRowCellStyle(rowField, row)"
              class="is-sticky-left has-text-weight-bold"
              @dblclick="doubleClick(rowField, row[rowField.name])"
            >
              <component
                :is="dynamicComponent(rowField.template)"
                :row="{ [rowField.name]: row[rowField.name] }"
                v-if="rowField.template"
                @clickevent="clickEvent($event, row, rowField)"
              />
              <span v-else>{{ formatCellValue(row[rowField.name], rowField) }}</span>
            </td>

            <td
              v-for="(leaf, leafIdx) in allLeafColumns"
              :key="`data-cell-${rowIndex}-${leafIdx}`"
              :style="getDataCellStyle(row, leaf)"
              @dblclick="handleCellClick(row, leaf)"
              :class="leaf.align || 'has-text-right'"
            >
              <template v-if="row.data[leaf.colKey]">
                <component
                  :is="dynamicComponent(leaf.template)"
                  :row="row.data[leaf.colKey]"
                  v-if="leaf.template"
                  @clickevent="clickEvent($event, row.data[leaf.colKey], leaf)"
                />
                <span v-else>
                  {{ formatCellValue(row.data[leaf.colKey][leaf.field], leaf) }}
                </span>
              </template>
              <span
                v-else
                class="has-text-grey-70"
                >-</span
              >
            </td>
          </tr>
        </tbody>
      </table>
    </div>

    <div
      v-else
      class="has-text-centered has-text-grey py-6"
    >
      <p>Không có dữ liệu để hiển thị</p>
    </div>

    <Modal
      @close="closeModal"
      @selected="doSelect"
      @confirm="confirmRemove"
      v-bind="showmodal"
      v-if="showmodal"
    />
  </div>
</template>

<script setup>
import TimeOption from "~/components/datatable/TimeOption";
import { ref, computed, watch, onMounted, onBeforeUnmount, defineComponent } from "vue";
import { useStore } from "~/stores/index";
import { cloneDeep } from "es-toolkit";

const emit = defineEmits(["modalevent", "dataevent", "dataUpdated", "edit", "insert"]);
const store = useStore();

const props = defineProps({
  api: String,
  pagename: String,
  setting: String,
  params: Object,
  filter: Object,
  pivotConfig: Object,
  timeopt: Object,
  modal: Object,
  realtime: Object,
  importdata: Object,
  data: Object,
});

const {
  $copy,
  $find,
  $findapi,
  $getapi,
  $setpage,
  $stripHtml,
  $snackbar,
  $dayjs,
  $parseNum,
  $formatNum,
  $empty,
  $unique,
  $multiSort,
  $remove,
  $calculate,
  $deleterow,
} = useNuxtApp();

// Dynamic component helper
function dynamicComponent(htmlString) {
  if (!htmlString) return null;
  return defineComponent({
    template: htmlString,
    props: {
      row: {
        type: Object,
        default: () => ({}),
      },
    },
  });
}

const showmodal = ref();
const pagedata = ref();
const newDataAvailable = ref(false);
const pivotData = ref([]);
const columnHeaderTree = ref([]);
const filters = ref([]);
const currentField = ref();
const currentRow = ref();
const filterData = ref([]);
const pollingInterval = ref(null);
const lastDataHash = ref(null);

// Computed properties
const vpagename = props.pagename || "pagedata_pivot";

const vparams = computed(() => {
  if (!props.params) return undefined;
  const params = $copy(props.params);
  if (params.filter) {
    for (const [key, value] of Object.entries(params.filter)) {
      if (typeof value === "string" && value.indexOf("$") >= 0) {
        const storeKey = value.replace("$", "");
        if (store[storeKey]) {
          params.filter[key] = store[storeKey].id || store[storeKey];
        }
      }
    }
  }
  return params;
});

let vfilter = props.filter ? $copy(props.filter) : undefined;
let optfilter = props.filter || (props.params ? props.params.filter : undefined);
let connection = undefined;
const realtimeConfig = ref({ time: 0, update: "true" });

if (props.realtime) {
  realtimeConfig.value = {
    time: props.realtime.time || 0,
    update: props.realtime.update,
  };
}

// Extract pivot config
const rowFields = computed(() => {
  if (!props.pivotConfig?.rowFields) return [];
  const fieldNames = props.pivotConfig.rowFields;
  return fieldNames.map((name) => {
    const field = pagedata.value?.fields?.find((f) => f.name === name);
    const result = field || { name, label: name, format: "string" };
    result.headerStyle = getSettingStyle("header", result);
    result.dropStyle = getSettingStyle("dropdown", result);
    return result;
  });
});

const columnTreeConfig = computed(() => {
  const cfg = props.pivotConfig?.columnFields || [];
  if (cfg.every((item) => typeof item === "string")) {
    return cfg.map((field) => ({ field, label: field }));
  }
  return cfg;
});

const subColumns = computed(() => {
  if (!props.pivotConfig?.subColumns || props.pivotConfig.subColumns.length === 0) {
    return [
      {
        field: "amount",
        label: "Số tiền",
        format: "number",
        decimal: 0,
        align: "has-text-right",
      },
    ];
  }
  return props.pivotConfig.subColumns.map((subCol) => {
    const field = pagedata.value?.fields?.find((f) => f.name === subCol.field);
    return {
      ...subCol,
      format: subCol.format || field?.format || "string",
      decimal: subCol.decimal !== undefined ? subCol.decimal : field?.decimal,
      unit: subCol.unit || field?.unit,
      template: subCol.template || field?.template,
    };
  });
});

// Build nested column tree
const buildColumnTree = (rawData) => {
  if (!rawData?.length || !columnTreeConfig.value.length) return [];

  const rootMap = new Map();

  rawData.forEach((item) => {
    let currentMap = rootMap;
    let currentPath = "";

    columnTreeConfig.value.forEach((cfg, level) => {
      const field = cfg.field;
      const val = item[field] != null ? String(item[field]).trim() : "N/A";
      const key = val;
      const prefix = cfg.prefix || "";
      const nodeLabel = cfg.label ? `${cfg.label} ${val}` : prefix + val;

      if (!currentMap.has(key)) {
        const node = {
          label: nodeLabel,
          value: val,
          path: currentPath ? currentPath + "|||" + val : val,
          sortType: cfg.sortType || "string",
          sortOrder: cfg.sortOrder,
        };
        if (level < columnTreeConfig.value.length - 1) {
          node.childrenMap = new Map();
        }
        currentMap.set(key, node);
      }

      currentMap = currentMap.get(key);
      currentPath = currentMap.path;
      if (level < columnTreeConfig.value.length - 1) {
        currentMap = currentMap.childrenMap;
      }
    });

    if (!currentMap.children) {
      currentMap.children = subColumns.value.map((sc) => ({
        ...sc,
        colKey: currentPath,
        isLeaf: true,
      }));
    }
  });

  const mapToSortedArray = (map) => {
    if (!map || !map.size) return [];

    const firstNode = map.values().next().value;
    const sortType = firstNode?.sortType || "string";
    const sortOrder = firstNode?.sortOrder;

    return Array.from(map.values()).sort((a, b) => {
      const valA = String(a?.value ?? "");
      const valB = String(b?.value ?? "");

      if (sortType === "number") {
        const numA = Number(valA);
        const numB = Number(valB);
        if (!isNaN(numA) && !isNaN(numB)) return numA - numB;
      }

      if (sortType === "custom" && Array.isArray(sortOrder)) {
        const order = sortOrder;
        const getIndex = (val) => {
          for (let i = 0; i < order.length; i++) {
            if (Array.isArray(order[i]) && order[i].includes(val)) return i;
            if (order[i] === val) return i;
          }
          return -1;
        };
        const indexA = getIndex(valA);
        const indexB = getIndex(valB);

        if (indexA !== -1 || indexB !== -1) {
          if (indexA === -1) return 1;
          if (indexB === -1) return -1;
          if (indexA !== indexB) return indexA - indexB;
        }
      }

      return valA.localeCompare(valB);
    });
  };

  const recurseSort = (node) => {
    if (node.childrenMap) {
      node.children = mapToSortedArray(node.childrenMap);
      delete node.childrenMap;
    }
    if (node.children && !node.children[0]?.isLeaf) {
      node.children.forEach(recurseSort);
    }
  };

  const roots = mapToSortedArray(rootMap);
  roots.forEach(recurseSort);
  return roots;
};

// Simplified transformToPivot - stable colKey, last wins for duplicates
const transformToPivot = (rawData) => {
  if (!rawData?.length) {
    pivotData.value = [];
    columnHeaderTree.value = [];
    return;
  }

  columnHeaderTree.value = buildColumnTree(rawData);

  const localRowFields = rowFields.value;
  const grouped = {};

  rawData.forEach((item) => {
    const rowKey = localRowFields
      .map((f) => {
        const val = item[f.name];
        return val != null ? String(val).trim() : "N/A";
      })
      .join("|||");

    if (!grouped[rowKey]) {
      grouped[rowKey] = {
        rowKey,
        data: {},
        ...Object.fromEntries(localRowFields.map((f) => [f.name, item[f.name]])),
      };
    }

    const colKey = columnTreeConfig.value
      .map((cfg) => {
        const val = item[cfg.field];
        return val != null ? String(val).trim() : "N/A";
      })
      .join("|||");

    grouped[rowKey].data[colKey] = { ...item }; // last wins
  });

  pivotData.value = Object.values(grouped);
};

// Header calculations
const maxHeaderDepth = computed(() => {
  const getDepth = (nodes, depth = 1) => {
    if (!nodes?.length) return depth - 1;
    return Math.max(depth, ...nodes.map((n) => getDepth(n.children, depth + 1)));
  };
  return getDepth(columnHeaderTree.value) + 1;
});

const getNodesAtLevel = (level) => {
  let current = columnHeaderTree.value;
  for (let i = 0; i < level; i++) {
    current = current.flatMap((n) => n.children || []);
  }
  return current;
};

const getColspan = (node) => {
  const countLeaves = (n) => {
    if (!n.children?.length) return 1;
    return n.children.reduce((sum, c) => sum + countLeaves(c), 0);
  };
  return countLeaves(node);
};

const getRowspan = (node, level) => {
  if (node.isLeaf) return 1;
  if (node.children?.length) return 1;
  return maxHeaderDepth.value - level;
};

const allLeafColumns = computed(() => {
  const getLeaves = (nodes) => {
    return nodes.flatMap((node) => {
      if (!node.children?.length) return [node];
      return getLeaves(node.children);
    });
  };
  return getLeaves(columnHeaderTree.value);
});

// Formatting
const formatCellValue = (value, field) => {
  if (value === undefined || value === null || value === "") return "-";

  if (field.format === "number") {
    const num = $parseNum(value);
    if (num === undefined) return "-";
    return $formatNum(num, "vi-VN", field.decimal || 0, field.decimal || 0);
  }

  if (field.format === "date") {
    return $dayjs(value).format("L");
  }

  return value;
};

// Styles
const tableStyle = computed(() => {
  const settings = pagedata.value?.tablesetting || store.tablesetting;
  if (!settings) return "font-size: 13px;";
  const bg = $find(settings, { code: "table-background" }, "detail") || "#ffffff";
  const color = $find(settings, { code: "table-font-color" }, "detail") || "#000000";
  const size = $find(settings, { code: "table-font-size" }, "detail") || 13;
  return `background-color: ${bg}; color: ${color}; font-size: ${size}px;`;
});

const headerStyle = computed(() => {
  const settings = pagedata.value?.tablesetting || store.tablesetting;
  if (!settings)
    return "background-color: #363636; color: white; font-weight: bold; position: sticky; top: 0; z-index: 10;";
  const bg = $find(settings, { code: "header-background" }, "detail") || "#363636";
  const color = $find(settings, { code: "header-font-color" }, "detail") || "#ffffff";
  const size = $find(settings, { code: "header-font-size" }, "detail") || 14;
  return `background-color: ${bg}; color: ${color}; font-weight: bold; font-size: ${size}px; position: sticky; top: 0; z-index: 10; padding: 8px; cursor: pointer;`;
});

const getSettingStyle = (name, field) => {
  const settings = pagedata.value?.tablesetting || store.tablesetting;
  if (!settings) return "";

  let value = "";
  if (name === "header") {
    value += "background-color:" + $find(settings, { code: "header-background" }, "detail") + "; ";
    if (field.minwidth) value += " min-width: " + field.minwidth + "px; ";
    if (field.maxwidth) style += " max-width: " + field.maxwidth + "px; ";
  } else if (name === "dropdown") {
    value += "font-size:" + $find(settings, { code: "header-font-size" }, "detail") + "px; ";
    const found = filters.value.find((v) => v.name === field.name);
    value += found
      ? "color:" + $find(settings, { code: "header-filter-color" }, "detail") + "; "
      : "color:" + $find(settings, { code: "header-font-color" }, "detail") + "; ";
  }
  return value;
};

const getHeaderStyle = (node, level) => {
  let style = headerStyle.value;
  if (node.minwidth) style += ` min-width: ${node.minwidth}px;`;
  return style + " border-bottom: 1px solid #dbdbdb;";
};

const getRowCellStyle = (field, row) => {
  let style =
    "padding: 8px; vertical-align: middle; border: solid 1px #dbdbdb; background-color: #f5f5f5; cursor: pointer;";
  if (field.bgcolor) style += ` background-color: ${field.bgcolor};`;
  if (field.color) style += ` color: ${field.color};`;
  if (field.minwidth) style += ` min-width: ${field.minwidth}px;`;
  if (field.maxwidth) style += ` max-width: ${field.maxwidth}px;`;
  return style;
};

const getDataCellStyle = (row, leaf) => {
  let style = "padding: 6px 8px; vertical-align: middle; border: solid 1px #dbdbdb; cursor: pointer;";
  const cell = row.data[leaf.colKey];

  if (cell && leaf.field) {
    if (leaf.field === "paid_amount") {
      const amount = $parseNum(cell.amount) || 0;
      const paid = $parseNum(cell.paid_amount) || 0;
      if (paid >= amount) {
        style += " background-color: #d4edda;";
      } else if (paid > 0) {
        style += " background-color: #fff3cd;";
      } else {
        style += " background-color: #f8d7da;";
      }
    }
    if (leaf.field === "to_date") {
      const dueDate = cell.to_date;
      const paid = $parseNum(cell.paid_amount) || 0;
      const amount = $parseNum(cell.amount) || 0;
      if (dueDate && paid < amount) {
        const today = new Date();
        const due = new Date(dueDate);
        if (due < today) style += " background-color: #f8d7da; color: #721c24;";
      }
    }
  }
  return style;
};

// Filter functions
const applyFilters = () => {
  if (!pagedata.value?.data) {
    pivotData.value = [];
    return;
  }

  let filtered = $copy(pagedata.value.data);

  filters.value.forEach((filter) => {
    if (filter.select && filter.select.length > 0) {
      filtered = filtered.filter((row) => {
        return filter.select.some((selectedValue) => {
          const rowValue = row[filter.name];
          if (typeof selectedValue === "undefined" || selectedValue === null || selectedValue === "") {
            return !rowValue || rowValue === "" || rowValue === null;
          }
          return String(rowValue) === String(selectedValue);
        });
      });
    }
  });

  transformToPivot(filtered);
};

const clearFilters = () => {
  filters.value = [];
  applyFilters();
};

const removeFilter = (index) => {
  $remove(filters.value, index);
  applyFilters();
};

const showCondition = (filter) => {
  // Placeholder
};

const showFieldMenu = (field) => {
  if (pagedata.value?.contextMenu === false || field.menu === "no") return;

  currentField.value = field;
  filterData.value = $unique(pagedata.value.data, [field.name]);

  showmodal.value = {
    vbind: {
      pagename: vpagename,
      field: field,
      filters: filters.value,
      filterData: filterData.value,
      width: 100,
    },
    component: "datatable/ContextMenu",
    title: field.name,
    width: "650px",
    height: "500px",
  };
};

const doSelect = (value) => {
  showmodal.value = undefined;
  const field = currentField.value;

  if (!field) return;

  let found = filters.value.find((v) => v.name === field.name);
  if (found) {
    if (!found.select) found.select = [];
    const idx = found.select.findIndex((x) => String(x) === String(value));
    if (idx >= 0) {
      found.select.splice(idx, 1);
    } else {
      found.select.push(value);
    }

    if (found.select.length === 0) {
      const filterIdx = filters.value.findIndex((v) => v.name === field.name);
      if (filterIdx >= 0) filters.value.splice(filterIdx, 1);
    }
  } else {
    const newFilter = {
      name: field.name,
      label: field.label,
      select: [value],
      format: field.format,
    };
    filters.value.push(newFilter);
  }

  filters.value = [...filters.value];
  setTimeout(() => applyFilters(), 0);
};

const doubleClick = (field, value) => {
  currentField.value = field;
  doSelect(value);
};

// Event handlers
const handleCellClick = (row, leaf) => {
  const cell = row.data[leaf.colKey];
  if (!cell) return;

  if (props.modal) {
    const copy = $copy(props.modal);
    const f = copy.vbind ? copy.vbind : { pagename: vpagename, api: props.api, row: cell };
    f.pagename = vpagename;
    f.row = cell;
    copy.vbind = f;
    showmodal.value = copy;
  }
};

const clickEvent = (event, row, field) => {
  const name = typeof event === "string" ? event : event.name;
  const data = typeof event === "string" ? event : event.data;

  if (name === "remove") {
    currentRow.value = row;
    showmodal.value = {
      component: "dialog/Confirm",
      vbind: {
        content: "Bạn có muốn xóa bản ghi này không?",
        duration: 10,
      },
      title: "Xác nhận",
      width: "500px",
      height: "100px",
    };
  }

  emit(name, row, field, data);
};

const confirmRemove = () => {
  if (currentRow.value?.id && pagedata.value?.api) {
    $deleterow(pagedata.value.api.name, currentRow.value.id, vpagename, true);
  }
};

const closeModal = () => {
  showmodal.value = undefined;
};

// Realtime & API functions (giữ nguyên)
const generateDataHash = (data) => {
  if (!data) return null;
  try {
    const replacer = (key, value) =>
      value && typeof value === "object" && !Array.isArray(value)
        ? Object.keys(value)
            .sort()
            .reduce((sorted, key) => {
              sorted[key] = value[key];
              return sorted;
            }, {})
        : value;
    const stringToHash = JSON.stringify(data, replacer);
    return stringToHash.split("").reduce((a, b) => {
      a = (a << 5) - a + b.charCodeAt(0);
      return a & a;
    }, 0);
  } catch (e) {
    console.error("Error generating data hash:", e);
    return null;
  }
};

const startAutoCheck = () => {
  if (pollingInterval.value) clearInterval(pollingInterval.value);
  if (realtimeConfig.value.time && realtimeConfig.value.time > 0) {
    pollingInterval.value = setInterval(() => checkDataChanges(), realtimeConfig.value.time * 1000);
  }
};

const checkDataChanges = async () => {
  try {
    const connlist = [];
    const conn1 = $findapi(props.api);

    if (vfilter) {
      const filter = $copy(conn1.params.filter) || {};
      for (const [key, value] of Object.entries(vfilter)) {
        filter[key] = value;
      }
      for (const [key, value] of Object.entries(filter)) {
        if (value.toString().indexOf("$") >= 0) {
          filter[key] = store[value.replace("$", "")].id;
        }
      }
      conn1.params.filter = filter;
    }

    if (vparams.value) conn1.params = $copy(vparams.value);

    delete conn1.params.sort;
    delete conn1.params.values;

    conn1.params.summary = "aggregate";
    conn1.params.distinct_values = JSON.stringify({
      total_count: { type: "Count", field: "id" },
      last_updated: { type: "Max", field: "update_time" },
      last_created: { type: "Max", field: "create_time" },
    });

    connlist.push(conn1);
    const rs = await $getapi(connlist);
    const obj = $find(rs, { name: props.api });
    const newMetadata = obj ? obj.data.rows : {};
    const newHash = generateDataHash(newMetadata);

    if (lastDataHash.value === null) {
      lastDataHash.value = newHash;
      return;
    }

    if (newHash !== lastDataHash.value) {
      lastDataHash.value = newHash;

      if (realtimeConfig.value.update === "true") {
        await loadFullDataAsync();
        emit("dataUpdated", {
          newData: store[vpagename].data,
          autoUpdate: true,
          hasChanges: true,
        });
      } else {
        newDataAvailable.value = true;
        emit("dataUpdated", {
          newData: null,
          autoUpdate: false,
          hasChanges: true,
        });
      }
    }
  } catch (error) {
    console.error("Error checking data:", error);
  }
};

const loadFullDataAsync = async () => {
  try {
    const connlist = [];
    const conn1 = $findapi(props.api);

    if (vfilter) {
      const filter = $copy(conn1.params.filter) || {};
      for (const [key, value] of Object.entries(vfilter)) {
        filter[key] = value;
      }
      for (const [key, value] of Object.entries(filter)) {
        if (value.toString().indexOf("$") >= 0) {
          filter[key] = store[value.replace("$", "")].id;
        }
      }
      conn1.params.filter = filter;
    }

    if (vparams.value) conn1.params = $copy(vparams.value);

    delete conn1.params.summary;
    delete conn1.params.distinct_values;

    connlist.push(conn1);
    const rs = await $getapi(connlist);
    const obj = $find(rs, { name: props.api });
    const newData = obj ? $copy(obj.data.rows) : [];

    updateDataDisplay(newData);
  } catch (error) {
    console.error("Error loading full data:", error);
  }
};

const updateDataDisplay = (newData) => {
  const copy = cloneDeep(store[vpagename]);
  copy.data = newData;
  copy.update = { data: newData };
  store.commit(vpagename, copy);
  newDataAvailable.value = false;
  transformToPivot(newData);
};

const manualRefresh = () => {
  refreshData();
};

const refreshData = async () => {
  if (pollingInterval.value) clearInterval(pollingInterval.value);
  newDataAvailable.value = false;

  await getApi();

  lastDataHash.value = null;
  await checkDataChanges();

  newDataAvailable.value = false;
  startAutoCheck();
};

const openImportModal = () => {
  const copy = $copy(props.importdata);
  showmodal.value = copy;
};

const timeOption = (v) => {
  if (!v) return getApi();

  if (v.filter_or) {
    if (vfilter) vfilter = undefined;
    if (vparams.value) {
      vparams.value.filter_or = v.filter_or;
    }
    return getApi();
  }

  let filter = vfilter ? vfilter : vparams.value ? vparams.value.filter || {} : {};
  for (const [key, value] of Object.entries(v.filter)) {
    filter[key] = value;
  }

  for (const [key, value] of Object.entries(filter)) {
    if (value.toString().indexOf("$") >= 0) {
      filter[key] = store[value.replace("$", "")].id;
    }
  }

  if (vfilter) {
    vfilter = filter;
  } else if (vparams.value) {
    vparams.value.filter = filter;
    vparams.value.filter_or = undefined;
  }

  if (!vfilter && !vparams.value) vfilter = filter;
  getApi();
};

const insert = () => {
  const copy = props.modal ? $copy(props.modal) : {};
  const f = copy.vbind ? copy.vbind : { pagename: vpagename, api: props.api };
  f.pagename = vpagename;
  copy.vbind = f;
  showmodal.value = copy;
};

// API load
const getApi = async () => {
  const connlist = [];
  let row = props.setting?.id ? $copy(props.setting) : undefined;

  if (!row) {
    const found = $find(
      store.settings.filter((v) => v),
      props.setting > 0 ? { id: props.setting } : { name: props.setting },
    );
    if (found) row = $copy(found);
  }

  if (!row && props.setting) {
    const conn = $findapi("usersetting");
    conn.params.filter = props.setting > 0 ? { id: props.setting } : { name: props.setting };
    connlist.push(conn);
  }

  let data = props.data ? $copy(props.data) : undefined;

  if (!data) {
    const conn1 = $findapi(props.api);
    if (vfilter) {
      const filter = conn1.params.filter || {};
      for (const [key, value] of Object.entries(vfilter)) {
        filter[key] = value;
      }
      for (const [key, value] of Object.entries(filter)) {
        if (value.toString().indexOf("$") >= 0) {
          filter[key] = store[value.replace("$", "")].id;
        }
      }
      conn1.params.filter = filter;
    }
    if (vparams.value) conn1.params = vparams.value;
    connection = conn1;
    connlist.push(conn1);
  }

  let obj = undefined;
  if (connlist.length > 0) {
    const rs = await $getapi(connlist);
    const ele = $find(rs, { name: "usersetting" });
    if (ele) {
      row = $find(ele.data.rows, { name: props.setting.name || props.setting });
      const copy = $copy(store.settings);
      copy.push(row);
      store.commit("settings", copy);
    }
    obj = $find(rs, { name: props.api });
    if (obj) data = $copy(obj.data.rows);
  }

  pagedata.value = $setpage(vpagename, row, obj);
  const copy = cloneDeep(pagedata.value);
  copy.data = data;
  copy.update = { data: data };
  store.commit(vpagename, copy);

  transformToPivot(data);
};

const exportExcel = async () => {
  if (!props.api || !connection) return;

  const found = $findapi("exportcsv");
  found.params = connection.params;
  const fields = pagedata.value.fields
    .filter((v) => (v.show && v.export !== "no") || v.export === "yes")
    .map((x) => ({ name: x.name, label: $stripHtml(x.label) }));
  found.params.fields = JSON.stringify(fields);
  found.url = connection.url.replace("data/", "exportcsv/");

  try {
    const rs = await $getapi([found]);
    if (rs === "error") {
      $snackbar("Đã xảy ra lỗi. Vui lòng thử lại.");
      return;
    }

    const url = window.URL.createObjectURL(new Blob([rs[0].data]));
    const link = document.createElement("a");
    const fileName = `${$dayjs(new Date()).format("YYYYMMDDhhmmss")}-data.csv`;
    link.href = url;
    link.setAttribute("download", fileName);
    document.body.appendChild(link);
    link.click();
    link.remove();
  } catch (error) {
    console.error("Export error:", error);
    $snackbar("Lỗi xuất file. Vui lòng thử lại.");
  }
};

// Watchers
watch(
  () => filters.value,
  (newVal) => {
    console.log("[watch filters] Filters changed:", newVal);
    applyFilters();
  },
  { deep: true },
);

watch(
  () => store[vpagename],
  (newVal) => {
    if (newVal?.update?.data) {
      console.log("[watch store] Data updated");
      pagedata.value = newVal;
      transformToPivot(newVal.data);
    }
  },
  { deep: true },
);

watch(
  () => props.realtime,
  (newVal) => {
    if (newVal) {
      realtimeConfig.value.time = newVal.time || 0;
      realtimeConfig.value.update = newVal.update === true ? "true" : "false";
      startAutoCheck();
    }
  },
  { deep: true },
);

// Lifecycle
onMounted(async () => {
  console.log("[PivotDataView] Component mounted");
  if (!props.timeopt) await getApi();
  startAutoCheck();
});

onBeforeUnmount(() => {
  if (pollingInterval.value) clearInterval(pollingInterval.value);
});
</script>

<style scoped>
:deep(.table tbody tr:hover td) {
  background-color: hsl(0, 0%, 88%) !important;
  color: rgb(0, 0, 0);
}

:deep(.table thead th) {
  cursor: pointer;
  user-select: none;
}

:deep(.is-sticky-left) {
  position: sticky;
  left: 0;
  z-index: 1000;
}

:deep(.table-container) {
  overflow-x: auto;
  overflow-y: auto;
}

:deep(.help) {
  font-size: 0.75rem;
  display: block;
  margin-top: 0.25rem;
}
</style>
