<template>
  <div class="explorer-table h-full m-4">
    <div ref="tabulatorRef" />
  </div>
</template>

<script setup lang="ts">
import { debounce } from 'lodash'
import {
  type SorterFromTable,
  TabulatorFull as Tabulator,
  type CellComponent,
  type ColumnDefinition,
  type RowComponent
} from 'tabulator-tables'
import { ref, onMounted, watch, computed, nextTick } from 'vue'

import type { TraceNode, UsageMetadata } from '@/common/interface/trace'
import { WT1iser } from '@/common/wt1'
import { dateToTimeShort } from '@/utils/formatDate'
import { classifyNodeGroup, getGroupColor } from '@/utils/nodeGroups'
import { useTracesStore } from '@/stores/traces'
import { isEmptyNode } from '@/utils/emptyNode'

function buildTabulatorData(root: TraceNode | undefined): any[] {
  if (!root) return []

  function usageToLine(usage: UsageMetadata | undefined): any {
    if (!usage) return ''
    const parts = []
    if (usage.promptTokens != null) parts.push(`Prompt: ${usage.promptTokens}`)
    if (usage.completionTokens != null) parts.push(`Completion: ${usage.completionTokens}`)
    if (usage.totalTokens != null) parts.push(`Total: ${usage.totalTokens}`)
    if (usage.estimatedCost != null) parts.push(`Cost: ${usage.estimatedCost}`)
    return parts.join(', ')
  }

  function buildRow(n: TraceNode): any {
    const childRows =
      Array.isArray(n.children) && n.children.length > 0 ? n.children.map(buildRow) : null

    return {
      id: n.id,
      name: n.name || n.type || '(no name)',
      duration: n.duration,
      begin: n.begin ? dateToTimeShort(new Date(n.begin)) : '',
      end: n.end ? dateToTimeShort(new Date(n.end)) : '',
      usageLine: usageToLine(n.usageMetadata),
      isEmptyNode: isEmptyNode(n),
      group: classifyNodeGroup(n),
      ...(childRows ? { children: childRows } : {})
    }
  }

  return [buildRow(root)]
}

const tabulatorRef = ref(null)
const tracesStore = useTracesStore()
let tableInstance: Tabulator | null = null

const tableIsBuilt = ref(false)

const tableData = computed(() => {
  if (!tracesStore.selectedTrace || !tracesStore.selectedTrace.id) return []
  return buildTabulatorData(tracesStore.selectedTrace.parentNode)
})

async function reselectCurrentNode(scroll = false) {
  if (!tableIsBuilt.value || !tracesStore.selectedNodeId || !tableInstance) {
    tableInstance?.deselectRow()
    return
  }

  function findRowById(table: Tabulator, id: string) {
    function searchRows(rows: RowComponent[]): RowComponent | null {
      for (const row of rows) {
        if (row.getData().id === id) {
          return row
        }
        const children = row.getTreeChildren()
        if (children?.length) {
          const result = searchRows(children)
          if (result) return result
        }
      }
      return null
    }
    return searchRows(table.getRows('all'))
  }

  try {
    const row = findRowById(tableInstance, tracesStore.selectedNodeId)
    if (row) {
      const parentRow = row.getTreeParent()
      if (parentRow) {
        parentRow.treeExpand()
      }
      tableInstance.deselectRow()
      row.select()
      if (scroll) {
        // Scroll?
      }
    } else {
      tableInstance.deselectRow()
    }
  } catch (err) {
    console.error('[ExplorerView] error selecting row:', err)
  }
}

watch(
  () => tableData.value,
  async (newVal) => {
    if (!tableInstance) return

    if (!tableIsBuilt.value) {
      return
    }

    await tableInstance.setData(newVal)
    tableInstance.getRows().forEach((r) => r.treeExpand())
    await nextTick()
    reselectCurrentNode()
  },
  { immediate: true }
)

watch(
  () => tracesStore.selectedNodeId,
  async () => {
    if (!tableIsBuilt.value || !tableInstance) {
      return
    }
    await nextTick()
    reselectCurrentNode(true)
  },
  { immediate: true }
)

onMounted(() => {
  if (!tabulatorRef.value) {
    console.error('[ExplorerView] No tabulatorRef!')
    return
  }

  const columns = [
    {
      title: 'Name',
      field: 'name',
      minWidth: 150,
      formatter: (cell: CellComponent) => {
        const row = cell.getRow()
        const data = row.getData()
        const emptyNode = row.getData().isEmptyNode
        const color = getGroupColor(data.group || 'span')
        const val = cell.getValue() || ''
        const badgeClass = emptyNode ? 'node-col-badge-empty' : 'node-col-badge'
        const style = emptyNode
          ? `background-color: white; border-color: ${color}`
          : `background-color:${color};`

        return `
        <div class="name-block">
          <div class="name-inline">
            <div class="${badgeClass}" style="${style}"></div>
            <div>${val}</div>
          </div>
        </div>`
      }
    },
    {
      title: 'Duration',
      field: 'duration',
      width: 140,
      hozAlign: 'left',
      sorter: 'number',
      formatter: (cell: CellComponent) => {
        const dur = cell.getValue()
        return dur ? (dur / 1000).toFixed(2) + ' s' : ''
      }
    },
    { title: 'Begin', field: 'begin', width: 140 },
    { title: 'End', field: 'end', width: 140 },
    { title: 'Usage', field: 'usageLine', minWidth: 140 }
  ]

  tableInstance = new Tabulator(tabulatorRef.value, {
    data: tableData.value,
    autoResize: false,
    layout: 'fitColumns',
    height: '100%',
    maxHeight: '100%',
    dataTree: true,
    dataTreeChildField: 'children',
    dataTreeStartExpanded: true,
    columns: columns as ColumnDefinition[],
    placeholder: 'No data available'
  })

  tableInstance.on('tableBuilt', () => {
    tableIsBuilt.value = true
    tableInstance?.getRows().forEach((r) => r.treeExpand())

    reselectCurrentNode()
  })

  tableInstance.on('rowClick', (_e, row) => {
    if (!tableIsBuilt.value) return
    tableInstance?.deselectRow()
    row.select()
    if (!row.getData().isEmptyNode) {
      tracesStore.selectedNodeId = row.getData().id
    }
  })

  const sendSortEvent = debounce((sorters: SorterFromTable[]) => {
    // if no sorters (default state after table is built or tab switch), don't send an event
    if (sorters.length === 0) {
      return
    }

    WT1iser.sortExplorerNodes(
      sorters.map((s) => ({
        field: s.field,
        dir: s.dir
      }))
    )
  }, 1000)

  tableInstance.on('dataSorting', (sorters: SorterFromTable[]) => {
    sendSortEvent(sorters)
  })
})
</script>

<style scoped lang="scss">
.explorer-table {
  border: 1px solid hsl(var(--border));
  border-radius: 4px;
  overflow-x: auto;
}

:deep(.tabulator-selectable:hover) {
  background-color: hsl(var(--muted)) !important;
}

:deep(.tabulator-selected) {
  background-color: hsl(var(--border)) !important;
}

:deep(.tabulator-cell) {
  font-family: var(--font-mono);
  padding-left: 12px !important;
}

:deep(.tabulator-col-title) {
  padding-left: 8px;
}

:deep(.tabulator-header) {
  position: sticky;
  top: 0;
  z-index: 10;
  border-bottom: 1px solid hsl(var(--border)) !important;
}

:deep(.node-col-badge) {
  width: 14px;
  height: 14px;
  border-radius: 2px;
  margin-right: 6px;
}

:deep(.node-col-badge-empty) {
  width: 14px;
  height: 14px;
  border-radius: 2px;
  margin-right: 6px;
  border: 3px;
  border: dotted;
}

:deep(.name-block) {
  display: inline-block;
}

:deep(.name-inline) {
  display: inline-flex;
  align-items: center;
}

:deep(.name-inline) .node-col-badge,
:deep(.name-inline) div {
  position: relative;
  top: 4px;
}
:deep(.name-inline) .node-col-badge-empty,
:deep(.name-inline) div {
  position: relative;
  top: 4px;
}
</style>
