<script setup>
import { ref, watch, onMounted, onUnmounted, computed } from 'vue'
import { AgGridVue } from 'ag-grid-vue3'
import { ModuleRegistry, AllCommunityModule } from 'ag-grid-community'
import DataTableHeader from './components/DataTableHeader.vue'
import UserDetailsDialog from './components/UserDetailsDialog.vue'
import ConfirmDeleteDialog from './components/ConfirmDeleteDialog.vue'
import { useDataTableGrid } from './composables/useDataTableGrid'
import { useDataTableActions } from './composables/useDataTableActions'
import { useResponsive } from './composables/useResponsive'
import jsPDF from 'jspdf'
import autoTable from 'jspdf-autotable'

ModuleRegistry.registerModules([AllCommunityModule])

const props = defineProps({
 apiUrl: {
   type: String,
   default: ''
 },
 apiHeaders: {
   type: Object,
   default: () => ({})
 },
 data: {
   type: Array,
   default: () => []
 },
 columns: {
   type: Array,
   default: () => []
 },
 height: {
   type: String,
   default: '600px'
 },
 mobileHeight: {
   type: String,
   default: '400px'
 },
 enableExport: {
   type: Boolean,
   default: true
 },
 enableEdit: {
   type: Boolean,
   default: true
 },
 enableDelete: {
   type: Boolean,
   default: true
 },
 enableSearch: {
   type: Boolean,
   default: true
 },
 loading: {
   type: Boolean,
   default: false
 },
 customActions: {
   type: Array,
   default: () => []
 },
 dataProcessor: {
   type: Function,
   default: null
 },
 schemaMap: {
   type: Object,
   default: () => ({})
 },
 keepOriginalKeys: {
   type: Boolean,
   default: false
 }
})

const emit = defineEmits([
 'data-updated',
 'row-selected',
 'row-deleted',
 'row-edited',
 'export-completed',
 'api-error'
])

const { isMobile, isTablet, windowWidth } = useResponsive()
const gridContainer = ref(null)

const fetchedData = ref([])
const isLoading = ref(false)

const normalizeRecord = (record, index) => {
  if (!props.schemaMap || Object.keys(props.schemaMap).length === 0) {
    return record
  }
  const normalized = {}
  for (const [canonicalKey, candidates] of Object.entries(props.schemaMap)) {
    const keys = Array.isArray(candidates) ? candidates : [candidates]
    let found = undefined
    for (const k of keys) {
      if (record[k] !== undefined) {
        found = record[k]
        break
      }
    }
    if (canonicalKey === 'id' && (found === undefined || found === null)) {
      normalized[canonicalKey] = record.id ?? index + 1
    } else {
      normalized[canonicalKey] = found
    }
  }
  return props.keepOriginalKeys ? { ...record, ...normalized } : normalized
}

const processedData = computed(() => {
  const sourceData = props.data?.length ? props.data : fetchedData.value
  if (!Array.isArray(sourceData)) return []

  return sourceData.map((item, index) => {
    const base = normalizeRecord(item, index)
    return {
      ...base,
      id: base?.id ?? item?.id ?? index + 1,
    }
  })
})

// Dynamically infer columns from data when not provided
const inferredColumns = computed(() => {
  if (Array.isArray(props.columns) && props.columns.length) return props.columns
  const rows = Array.isArray(processedData.value) ? processedData.value.slice(0, 50) : []
  if (!rows.length) return []

  // Collect union of keys across sample rows
  const keySet = new Set()
  rows.forEach(r => {
    if (r && typeof r === 'object') {
      Object.keys(r).forEach(k => {
        if (k !== 'action' && r[k] !== undefined && typeof r[k] !== 'object') keySet.add(k)
      })
    }
  })

  const keys = Array.from(keySet)
  const toTitle = (k) => k
    .replace(/_/g, ' ')
    .replace(/([a-z])([A-Z])/g, '$1 $2')
    .replace(/^./, s => s.toUpperCase())

  const isLikelyDate = (v) => {
    if (typeof v !== 'string') return false
    // ISO or yyyy-mm-dd or timestamps in string
    if (/^\d{4}-\d{2}-\d{2}/.test(v) || /T\d{2}:\d{2}:\d{2}/.test(v)) return true
    const d = new Date(v)
    return !isNaN(d.getTime())
  }

  return keys.map(k => {
    // Detect type by scanning a few rows
    let sampleVal = undefined
    for (let i = 0; i < rows.length; i++) {
      const val = rows[i]?.[k]
      if (val !== undefined && val !== null) { sampleVal = val; break }
    }

    const col = {
      field: k,
      headerName: toTitle(k),
      sortable: true,
      filter: true,
      editable: false,
      minWidth: 100,
      flex: 1,
    }

    if (typeof sampleVal === 'number') {
      col.filter = true
      col.width = 120
      col.valueFormatter = p => (typeof p.value === 'number' ? p.value : p.value ?? '')
    } else if (typeof sampleVal === 'boolean') {
      col.width = 120
      col.valueFormatter = p => (p.value ? 'Yes' : 'No')
    } else if (isLikelyDate(sampleVal)) {
      col.width = 140
      col.valueFormatter = p => {
        if (!p.value) return ''
        const d = new Date(p.value)
        return isNaN(d.getTime()) ? String(p.value) : d.toISOString().split('T')[0]
      }
    } else {
      col.flex = 1
    }
    return col
  })
})

// Build grid with inferred columns (or props.columns if provided)
// Note: Passing inferredColumns.value at setup time; if you need live re-init on schema change, we can add a watcher.
let gridSetup = useDataTableGrid(processedData, isMobile, isTablet, inferredColumns)
const gridApi = gridSetup.gridApi
const columnDefs = gridSetup.columnDefs
const rowData = gridSetup.rowData
const defaultColDef = gridSetup.defaultColDef
const gridOptions = gridSetup.gridOptions
const gridReady = gridSetup.onGridReady
const onCellValueChanged = gridSetup.onCellValueChanged
const updateColumnVisibility = gridSetup.updateColumnVisibility
const updatePagination = gridSetup.updatePagination

watch(
  processedData,
  (newData) => {
    if (Array.isArray(newData) && gridApi.value) {
      try {
        if (typeof gridApi.value.setRowData === 'function') {
          gridApi.value.setRowData(newData)
        } else if (typeof gridApi.value.setGridOption === 'function') {
          gridApi.value.setGridOption("rowData", newData)
        }
      } catch (error) {
        console.warn('Error updating grid data:', error)
      }
    }
  },
  { deep: true, immediate: true }
)

const fetchData = async () => {
 if (!props.apiUrl) return
 
 isLoading.value = true
 try {
   const response = await fetch(props.apiUrl, {
     headers: {
       'Content-Type': 'application/json',
       ...props.apiHeaders
     }
   })
   
   if (!response.ok) {
     throw new Error(`HTTP error! status: ${response.status}`)
   }
   
   const result = await response.json()
   
   let processedApiData = []
   if (props.dataProcessor && typeof props.dataProcessor === 'function') {
     processedApiData = props.dataProcessor(result)
   } else {
     processedApiData = result.todos || result.data || result.users || result.products || (Array.isArray(result) ? result : [])
   }
   
   fetchedData.value = Array.isArray(processedApiData) ? processedApiData : []
   console.log('Fetched data set to:', fetchedData.value)
   emit('data-updated', fetchedData.value)
 } catch (error) {
   console.error('Error fetching data:', error)
   emit('api-error', error)
 } finally {
   isLoading.value = false
 }
}

const saveToAPI = async (userData, isUpdate = false) => {
 if (!props.apiUrl) return
 
 try {
   const method = isUpdate ? 'PUT' : 'POST'
   const url = isUpdate ? `${props.apiUrl}/${userData.id}` : props.apiUrl
   
   const response = await fetch(url, {
     method,
     headers: {
       'Content-Type': 'application/json',
       ...props.apiHeaders
     },
     body: JSON.stringify(userData)
   })
   
   if (!response.ok) {
     throw new Error(`HTTP error! status: ${response.status}`)
   }
   
   await fetchData()
   return await response.json()
 } catch (error) {
   console.error('Error saving data:', error)
   emit('api-error', error)
   throw error
 }
}

const deleteFromAPI = async (id) => {
 if (!props.apiUrl) return
 
 try {
   const response = await fetch(`${props.apiUrl}/${id}`, {
     method: 'DELETE',
     headers: {
       'Content-Type': 'application/json',
       ...props.apiHeaders
     }
   })
   
   if (!response.ok) {
     throw new Error(`HTTP error! status: ${response.status}`)
   }
   
   await fetchData()
   return true
 } catch (error) {
   console.error('Error deleting data:', error)
   emit('api-error', error)
   throw error
 }
}

const {
 setupGlobalHandlers,
 cleanupGlobalHandlers,
 exportToCSV,
 exportToExcel,
 saveUser,
 confirmDelete,
 cancelDelete,
 showDetailsDialog,
 selectedRowData,
 deleteRowData,
 showDeleteDialog,
 deleteRow
} = useDataTableActions(gridApi, rowData)

const handleSaveUser = async (updatedData) => {
try {
  // Optimistic update: update grid first
  saveUser(updatedData)

  if (props.apiUrl) {
    try {
      await saveToAPI(updatedData, updatedData.id ? true : false)
    } catch (apiErr) {
      console.error('API save failed, refetching to sync:', apiErr)
      // Re-sync from server on failure
      await fetchData()
      emit('api-error', apiErr)
      return
    }
  }
  emit('row-edited', updatedData)
} catch (error) {
  console.error('Error saving user:', error)
}
}

const handleConfirmDelete = async () => {
try {
  // Optimistic remove from grid first
  confirmDelete()

  if (props.apiUrl && deleteRowData.value?.id) {
    try {
      await deleteFromAPI(deleteRowData.value.id)
    } catch (apiErr) {
      console.error('API delete failed, refetching to restore:', apiErr)
      await fetchData()
      emit('api-error', apiErr)
      return
    }
  }
  emit('row-deleted', deleteRowData.value)
} catch (error) {
  console.error('Error deleting row:', error)
}
}

const quickFilter = ref('')

watch(quickFilter, (newValue) => {
 if (gridApi.value) {
   gridApi.value.setGridOption('quickFilterText', newValue || '')
 }
}, { immediate: false })

const onGridReady = params => {
 gridReady(params)
 setupGlobalHandlers()
 
 if (props.apiUrl) {
   fetchData()
 }

 // Observe container size to recalc pagination
 try {
   const container = gridContainer?.value
   if (container && typeof ResizeObserver !== 'undefined') {
     const ro = new ResizeObserver(() => {
       if (gridApi.value) {
         updatePagination()
         setTimeout(() => {
           gridApi.value?.sizeColumnsToFit?.()
         }, 50)
       }
     })
     ro.observe(container)
   }
 } catch (e) {
   // no-op
 }
}

const handleQuickFilterUpdate = (value) => {
 quickFilter.value = value || ''
 
 if (gridApi.value) {
   gridApi.value.setGridOption('quickFilterText', value || '')
 }
}

const refreshData = () => {
 if (props.apiUrl) {
   fetchData()
 }
}

defineExpose({
 refreshData,
 fetchData,
 gridApi
})

onMounted(() => {
 setupGlobalHandlers()
 window.addEventListener('resize', handleResize)
})

onUnmounted(() => {
 cleanupGlobalHandlers()
 window.removeEventListener('resize', handleResize)
})

const handleResize = () => {
 windowWidth.value = window.innerWidth
 if (gridApi.value) {
   updateColumnVisibility()
   updatePagination()
   setTimeout(() => {
     if (gridApi.value && gridApi.value.sizeColumnsToFit) {
       gridApi.value.sizeColumnsToFit()
     }
   }, 100)
 }
}

const exportToPDF = () => {
 if (!gridApi.value) {
   console.error('Grid API not available')
   return
 }

 try {
   const doc = new jsPDF()
   const rowDataForPDF = []
   const headers = []
   
   columnDefs.value.forEach(col => {
     if (col.headerName && col.field && col.field !== 'action') {
       headers.push(col.headerName)
     }
   })
   
   gridApi.value.forEachNodeAfterFilterAndSort(node => {
     const row = []
     columnDefs.value.forEach(col => {
       if (col.field && col.field !== 'action') {
         let value = node.data[col.field] || ''
         row.push(value)
       }
     })
     rowDataForPDF.push(row)
   })
   
   autoTable(doc, {
     head: [headers],
     body: rowDataForPDF,
     styles: { 
       fontSize: 8,
       cellPadding: 2
     },
     headStyles: { 
       fillColor: [41, 128, 185],
       textColor: 255,
       fontStyle: 'bold'
     },
     alternateRowStyles: {
       fillColor: [245, 245, 245]
     },
     margin: { top: 20 }
   })
   
   doc.save(`data-table-${new Date().toISOString().split('T')[0]}.pdf`)
   emit('export-completed', 'pdf')
 } catch (error) {
   console.error('Error exporting PDF:', error)
 }
}
</script>

<template>
 <v-card class="elevation-2">
   <v-overlay
     v-model="isLoading"
     contained
     class="align-center justify-center"
   >
     <v-progress-circular
       indeterminate
       size="64"
       color="primary"
     ></v-progress-circular>
   </v-overlay>

   <DataTableHeader
     v-if="enableSearch || enableExport"
     :is-mobile="isMobile"
     :is-tablet="isTablet"
     :quick-filter="quickFilter"
     :enable-search="enableSearch"
     :enable-export="enableExport"
     @update:quick-filter="handleQuickFilterUpdate"
     @export-csv="exportToCSV"
     @export-excel="exportToExcel"
     @export-pdf="exportToPDF"
   />

   <v-divider v-if="enableSearch || enableExport" />

   <v-card-text class="pa-0">
    <AgGridVue
      class="vuetify-grid ag-theme-alpine-dark"
      :style="`height:${isMobile ? mobileHeight : height}; width:100%`"
      :columnDefs="columnDefs"
      :rowData="processedData"
      :defaultColDef="defaultColDef"
      :gridOptions="gridOptions"
      @grid-ready="onGridReady"
      @cell-value-changed="onCellValueChanged"
    />
  </v-card-text>

   <UserDetailsDialog
     v-if="enableEdit"
     v-model="showDetailsDialog"
     :selected-row-data="selectedRowData"
     :columns="columnDefs"
     :is-mobile="isMobile"
     @save-user="handleSaveUser"
   />

   <ConfirmDeleteDialog
     v-if="enableDelete"
     v-model="showDeleteDialog"
     :selected-row-data="deleteRowData"
     :columns="columnDefs"
     :is-mobile="isMobile"
     @confirm="handleConfirmDelete"
     @cancel="cancelDelete"
   />
 </v-card>
</template>