<template>
  <TimeOption
    v-bind="{ pagename: vpagename, api: api, timeopt: 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="timeopt">
  </TimeOption>
  <DataTable v-bind="{ pagename: vpagename }" @edit="edit" @insert="insert" @dataevent="dataEvent" @displayDataChange="displayDataChange" v-if="pagedata" />
  <Modal @close="showmodal = undefined" v-bind="showmodal" v-if="showmodal" />
</template>

<script setup>
import TimeOption from '~/components/datatable/TimeOption'
import { ref, watch, onBeforeUnmount, onActivated, onDeactivated } from 'vue'
import { useStore } from '~/stores/index'

const emit = defineEmits(['modalevent', 'dataevent', 'dataUpdated', 'changedata'])
const store = useStore()

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

const { $copy, $find, $findapi, $getapi, $setpage, $clone, $stripHtml, $snackbar, $dayjs, $exportTableExcel } = useNuxtApp()

const showmodal        = ref()
const pagedata         = ref()
const newDataAvailable = ref(false)
const pendingNewData   = ref(null)
const lastDataHash     = ref(null)
const pollingInterval  = ref(null)

let vpagename  = props.pagename
let vfilter    = props.filter ? $copy(props.filter) : undefined
let vparams    = props.params ? $copy(props.params) : undefined
let connection = undefined
let optfilter  = props.filter || (props.params ? props.params.filter : undefined)

const realtimeConfig = ref({ time: 0, update: 'true' })
if (props.realtime) {
  realtimeConfig.value = { time: props.realtime.time || 0, update: props.realtime.update }
}

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

// ── helpers ───────────────────────────────────────────────────────────────────
function resolveStoreVars(filter) {
  for (const [key, value] of Object.entries(filter)) {
    if (value?.toString().indexOf('$') >= 0)
      filter[key] = store[value.replace('$', '')].id
  }
  return filter
}

function buildFilteredConn(apiName) {
  const conn = $findapi(apiName)
  if (vfilter) {
    const filter = { ...(conn.params.filter || {}) }
    for (const [k, v] of Object.entries(vfilter)) filter[k] = v
    conn.params.filter = resolveStoreVars(filter)
  }
  if (vparams) conn.params = $copy(vparams)
  return conn
}

// ── enrich array fields: lookup FK từ bảng khác, inject vào array items ──────
// Cấu hình trong field config:
// {
//   name: "allocation_detail", split: "stack", splitField: "schedule_code",
//   lookup: {
//     api: "payment_schedule", localKey: "schedule_id", remoteKey: "id",
//     values: "id,code,batch_date",
//     inject: { "schedule_code": "code", "schedule_date": "batch_date" }
//   }
// }
const lookupCache = {}

function collectLookupTasks(fields) {
  const tasks = []
  fields.forEach(field => {
    if (!field.name) return
    if (field.lookup && field.split === 'stack') {
      tasks.push({
        fieldName: field.name,
        lookup: {
          localKey:  field.lookup.localKey  || field.splitField || 'id',
          remoteKey: field.lookup.remoteKey || 'id',
          api:       field.lookup.api,
          values:    field.lookup.values,
          inject:    field.lookup.inject || {},
        },
      })
    }
    if (field.split === 'columns' && field.subfields?.length) {
      field.subfields.forEach(sf => {
        if (!sf.lookup) return
        tasks.push({
          fieldName:    field.name,
          subfieldName: sf.name,
          lookup: {
            localKey:  sf.name,
            remoteKey: sf.lookup.remoteKey || 'id',
            api:       sf.lookup.api,
            values:    sf.lookup.values,
            display:   sf.lookup.display,
            inject:    sf.lookup.inject || {},
          },
        })
      })
    }
  })
  return tasks
}

async function fetchLookupMaps(tasks, rows) {
  const groups = {}
  tasks.forEach(task => {
    const { api, remoteKey, localKey, values } = task.lookup
    const cacheKey = `${api}:${remoteKey}`
    if (!groups[cacheKey]) groups[cacheKey] = { api, remoteKey, values, ids: new Set() }
    rows.forEach(row => {
      const arr = row[task.fieldName]
      if (!arr?.length) return
      arr.forEach(item => {
        const id = item[localKey]
        if (id != null && !lookupCache[cacheKey]?.has(id))
          groups[cacheKey].ids.add(id)
      })
    })
  })
  await Promise.all(Object.entries(groups).map(async ([cacheKey, group]) => {
    if (!group.ids.size) return
    try {
      const conn  = $findapi(group.api)
      conn.params = {
        filter: { [`${group.remoteKey}__in`]: [...group.ids] },
        page:   -1,
        ...(group.values ? { values: group.values } : {}),
      }
      const rs   = await $getapi([conn])
      const data = rs?.[0]?.data?.rows || rs?.[0]?.data || []
      if (!lookupCache[cacheKey]) lookupCache[cacheKey] = new Map()
      const map = lookupCache[cacheKey]
      ;(Array.isArray(data) ? data : []).forEach(r => map.set(r[group.remoteKey], r))
    } catch (e) {
      console.warn(`[DataView] lookup failed for ${group.api}:`, e)
    }
  }))
  return lookupCache
}

function applyLookups(rows, tasks, maps) {
  if (!tasks.length) return rows
  return rows.map(row => {
    const newRow = { ...row }
    tasks.forEach(task => {
      const arr = row[task.fieldName]
      if (!arr?.length) return
      const { localKey, remoteKey, inject, display } = task.lookup
      const cacheKey = `${task.lookup.api}:${remoteKey}`
      const map = maps[cacheKey]
      if (!map) return
      newRow[task.fieldName] = arr.map(item => {
        const remote = map.get(item[localKey])
        if (!remote) return item
        const enriched = { ...item }
        if (inject) Object.entries(inject).forEach(([lf, rf]) => { enriched[lf] = remote[rf] })
        if (display && task.subfieldName) enriched[`_${task.subfieldName}_display`] = remote[display]
        return enriched
      })
    })
    return newRow
  })
}

async function enrichArrayFields(rows) {
  if (!rows?.length) return rows
  const fields = store[vpagename]?.fields || pagedata.value?.fields || []
  const tasks  = collectLookupTasks(fields)
  if (!tasks.length) return rows
  const maps = await fetchLookupMaps(tasks, rows)
  return applyLookups(rows, tasks, maps)
}

// ── hash & polling ────────────────────────────────────────────────────────────
const generateDataHash = (data) => {
  if (!data) return null
  try {
    const replacer = (key, value) =>
      value && typeof value === 'object' && !Array.isArray(value)
        ? Object.keys(value).sort().reduce((s, k) => { s[k] = value[k]; return s }, {})
        : value
    const str = JSON.stringify(data, replacer)
    return str.split('').reduce((a, b) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a }, 0)
  } catch (e) { return null }
}

const stopAutoCheck = () => {
  if (pollingInterval.value) { clearInterval(pollingInterval.value); pollingInterval.value = null }
}

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

const checkDataChanges = async () => {
  try {
    const conn = buildFilteredConn(props.api)
    delete conn.params.sort
    delete conn.params.values
    conn.params.summary = 'aggregate'
    conn.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' },
    })
    const rs      = await $getapi([conn])
    const obj     = $find(rs, { name: props.api })
    const newHash = generateDataHash(obj ? obj.data.rows : {})
    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 conn = buildFilteredConn(props.api)
    delete conn.params.summary
    delete conn.params.distinct_values
    const rs      = await $getapi([conn])
    const obj     = $find(rs, { name: props.api })
    const rawData = obj ? $copy(obj.data.rows) : []
    const data    = await enrichArrayFields(rawData)
    updateDataDisplay(data)
  } catch (error) { console.error('Error loading full data:', error) }
}

const updateDataDisplay = (newData) => {
  const copy  = $clone(store[vpagename])
  copy.data   = newData
  copy.update = { data: newData }
  store.commit(vpagename, copy)
  newDataAvailable.value = false
  pendingNewData.value   = null
}

const openImportModal = () => { showmodal.value = $copy(props.importdata) }
const manualRefresh   = () => { if (pendingNewData.value) updateDataDisplay(pendingNewData.value) }

const refreshData = async () => {
  stopAutoCheck()
  newDataAvailable.value = false
  pendingNewData.value   = null
  await getApi()
  lastDataHash.value = null
  await checkDataChanges()
  newDataAvailable.value = false
  startAutoCheck()
}

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

onDeactivated(()   => stopAutoCheck())
onActivated(()     => startAutoCheck())
onBeforeUnmount(() => stopAutoCheck())

// ── timeOption ────────────────────────────────────────────────────────────────
const timeOption = (v) => {
  if (!v) return getApi()
  if (v.filter_or) {
    if (vfilter) vfilter = undefined
    if (vparams) {
      vparams.filter_or = v.filter_or
    } else {
      const found = $copy($findapi(props.api))
      found.params.filter_or = v.filter_or
      if (props.filter) found.params.filter = resolveStoreVars($copy(props.filter))
      vparams = found.params
    }
    return getApi()
  }
  let filter = vfilter ? vfilter : (props.params ? props.params.filter || {} : {})
  for (const [key, value] of Object.entries(v.filter)) filter[key] = value
  resolveStoreVars(filter)
  if (vfilter) { vfilter = filter; vparams = undefined }
  else if (vparams) { vparams.filter = filter; vparams.filter_or = undefined }
  if (!vfilter && !vparams) vfilter = filter
  getApi()
}

// ── edit / insert / events ────────────────────────────────────────────────────
const edit = (v) => {
  const copy = props.modal ? $copy(props.modal) : {}
  const f    = copy.vbind ? copy.vbind : { pagename: vpagename, api: props.api, row: v }
  f.pagename = vpagename
  f.row      = v
  copy.vbind = f
  showmodal.value = copy
}

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
}

const dataEvent = (v, field, data) => {
  if (data?.modal) {
    const copy = $copy(data.modal)
    const f    = copy.vbind ? copy.vbind : {}
    if (!f.api) f.api  = props.api
    f.pagename = vpagename
    if (!f.row) f.row  = v
    copy.vbind = f
    copy.field = field
    showmodal.value = copy
  }
  emit('modalevent', { name: 'dataevent', data: { row: v, field: field } })
  emit('dataevent', v, field)
}

const displayDataChange = (args) => {
  emit('displayDataChange', args);
}

// ── getApi ────────────────────────────────────────────────────────────────────
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) {
    const conn = $findapi('usersetting')
    conn.params.filter = props.setting > 0 ? { id: props.setting } : { name: props.setting }
    connlist.push(conn)
  }

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

  if (!rawData) {
    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) conn1.params = vparams
    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) rawData = $copy(obj.data.rows)
  }

  pagedata.value = $setpage(vpagename, row, obj)
  const data = await enrichArrayFields(rawData)
  const copy = $clone(pagedata.value)
  copy.data   = data
  copy.update = { data }
  store.commit(vpagename, copy)
}

// ── exportExcel ───────────────────────────────────────────────────────────────
const exportExcel = async () => {
  if (!pagedata.value) return

  const ACTION = ['DebtCheckbox', 'SvgIcon', "name: 'dataevent'", '$emit(', 'modal/']
  const fields = (pagedata.value.fields || []).filter(f => {
    if (f.export === 'yes') return true
    if (!f.show && f.show !== 'true') return false
    if (f.export === 'no') return false
    if (f.template && ACTION.some(k => f.template.includes(k))) return false
    return true
  })

  const allData = $copy(store[vpagename]?.data || [])
  if (!allData.length) { $snackbar('Không có dữ liệu để xuất.'); return }

  const filename = $dayjs(new Date()).format('YYYYMMDDHHmmss') + '-data'

  await $exportTableExcel({
    fields,
    data:         allData,
    tablesetting: pagedata.value.tablesetting || [],
    filename,
  })
}
if (!props.timeopt) await getApi()
startAutoCheck()
</script>