import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { getLocalTimeZone } from '@internationalized/date'
import type { DateRange } from 'reka-ui'

import { useTracesStore } from '@/stores/tracesStore'
import { useTrace } from '@/composables/useTraces'
import { useSettings } from '@/composables/useSettings'
import type { TraceNode } from '@/types/trace'
import type { NodeGroupName } from '@/utils/nodeGroups'
import { getAllAncestors, getAllParentIds } from '@/utils/treeTraversal'
import { classifyNodeGroup } from '@/utils/nodeGroups'

export type TraceViewMode = 'explorer' | 'timeline' | 'tree'
export type NodeViewMode = 'formatted' | 'raw'

export const useUiStore = defineStore('ui', () => {
  const tracesStore = useTracesStore()
  const { defaultTraceView, defaultNodeView } = useSettings()

  // -----------------------------------------------------------------------------
  // Selection State
  // -----------------------------------------------------------------------------
  const selectedTraceId = ref<string | undefined>(undefined)
  const selectedNodeId = ref<string | undefined>(undefined)

  // fetch selected trace data via TanStack Query
  const {
    data: fetchedTrace,
    isLoading: isLoadingTrace,
    isError: isTraceError,
  } = useTrace(selectedTraceId)

  // -----------------------------------------------------------------------------
  // Trace Filters
  // -----------------------------------------------------------------------------
  const nameSearchQuery = ref('')
  const resultSearchQuery = ref('')
  const durationRange = ref<[number, number] | null>(null)
  const dateRange = ref<DateRange>()

  // -----------------------------------------------------------------------------
  // Node Filters
  // -----------------------------------------------------------------------------
  const activeGroupFilters = ref<Set<NodeGroupName>>(new Set())
  const nodeSearchQuery = ref('')

  // -----------------------------------------------------------------------------
  // UI State
  // -----------------------------------------------------------------------------
  // node inspector view mode
  const nodeViewMode = ref<NodeViewMode>(defaultNodeView.value)

  const viewMode = ref<TraceViewMode | null>(null)
  const effectiveViewMode = computed<TraceViewMode>(
    () => viewMode.value ?? defaultTraceView.value ?? 'explorer',
  )
  const collapseAll = ref(false)
  const zoomLevel = ref(1)
  const collapsedNodeIds = ref<Set<string>>(new Set())

  // sidebar auto-collapse tracking
  const sidebarUserOverride = ref(false)

  // -----------------------------------------------------------------------------
  // Derived Data: Selected Trace & Node
  // -----------------------------------------------------------------------------

  /**
   * The currently selected trace fetched from API.
   */
  const selectedTrace = computed(() => {
    return fetchedTrace.value?.data
  })

  /**
   * The currently selected node within the selected trace.
   */
  const selectedNode = computed(() => {
    if (!selectedNodeId.value || !selectedTrace.value?.parentNode) return undefined

    const findNode = (node: TraceNode): TraceNode | undefined => {
      if (node.id === selectedNodeId.value) return node
      if (node.children) {
        for (const child of node.children) {
          const found = findNode(child as TraceNode)
          if (found) return found
        }
      }
      return undefined
    }

    return findNode(selectedTrace.value.parentNode)
  })

  // -----------------------------------------------------------------------------
  // Derived Data: Filtered Traces
  // -----------------------------------------------------------------------------

  /**
   * Max duration across all traces (for duration filter slider).
   */
  const durationMax = computed(() => {
    const allTraces = tracesStore.traces?.data || []
    if (allTraces.length === 0) return 10000
    const maxDuration = Math.max(...allTraces.map((t) => t.duration ?? 0))
    return Math.ceil(maxDuration / 1000) * 1000
  })

  /**
   * Traces filtered by search query, duration range, and date range.
   */
  const filteredTraces = computed(() => {
    const allTraces = tracesStore.traces?.data?.slice() || []

    return allTraces
      .filter((trace) => {
        // name filter (user message)
        if (nameSearchQuery.value) {
          const query = nameSearchQuery.value.toLowerCase()
          if (!trace.name?.toLowerCase().includes(query)) return false
        }

        // result filter (response text)
        if (resultSearchQuery.value) {
          const query = resultSearchQuery.value.toLowerCase()
          if (!trace.result?.toLowerCase().includes(query)) return false
        }

        // duration filter
        if (durationRange.value) {
          const duration = trace.duration ?? 0
          const [minDuration, maxDuration] = durationRange.value
          if (duration < minDuration || duration > maxDuration) {
            return false
          }
        }

        // date range filter
        if (dateRange.value?.start && trace.begin) {
          const traceDate = new Date(trace.begin)
          const startDate = dateRange.value.start.toDate(getLocalTimeZone())
          startDate.setHours(0, 0, 0, 0)
          if (traceDate < startDate) return false
        }
        if (dateRange.value?.end && trace.begin) {
          const traceDate = new Date(trace.begin)
          const endDate = dateRange.value.end.toDate(getLocalTimeZone())
          endDate.setHours(23, 59, 59, 999)
          if (traceDate > endDate) return false
        }

        return true
      })
      .sort((a, b) => {
        // sort by date (newest first)
        return new Date(b.begin!).getTime() - new Date(a.begin!).getTime()
      })
  })

  // -----------------------------------------------------------------------------
  // Derived Data: Filtered Tree (Node Visibility)
  // -----------------------------------------------------------------------------

  /**
   * Whether any node filters are active (group filters or text search).
   */
  const hasActiveNodeFilters = computed(
    () => activeGroupFilters.value.size > 0 || nodeSearchQuery.value.length > 0,
  )

  /**
   * Checks if a node group is visible based on active filters.
   * - If no filters active (empty set): all groups are visible
   * - If filters active: only selected groups are visible
   */
  function isGroupVisible(group: NodeGroupName) {
    if (activeGroupFilters.value.size === 0) {
      return true // No filter = show all
    }
    return activeGroupFilters.value.has(group)
  }

  /**
   * Checks if a group filter is currently selected/active.
   */
  function isGroupFilterActive(group: NodeGroupName) {
    return activeGroupFilters.value.has(group)
  }

  /**
   * Extracts searchable text from a node (name only).
   */
  function getNodeSearchableText(node: TraceNode): string {
    return (node.name || '').toLowerCase()
  }

  /**
   * Checks if a node matches the current text search query.
   */
  function nodeMatchesSearch(node: TraceNode): boolean {
    if (!nodeSearchQuery.value) return true
    return getNodeSearchableText(node).includes(nodeSearchQuery.value.toLowerCase())
  }

  /**
   * Filtered copy of the selected trace's tree with non-visible nodes removed.
   * Shows nodes that match active filters OR have descendants that match.
   */
  const filteredTree = computed<TraceNode | null>(() => {
    const rootNode = selectedTrace.value?.parentNode
    if (!rootNode) return null

    const activeFilters = activeGroupFilters.value
    const hasGroupFilters = activeFilters.size > 0
    const hasTextSearch = nodeSearchQuery.value.length > 0

    function filterNode(node: TraceNode): TraceNode | null {
      const group = classifyNodeGroup(node) as NodeGroupName
      // if no group filters, all groups visible; if filters, only selected groups visible
      const groupVisible = !hasGroupFilters || activeFilters.has(group)
      // if no text search, all nodes match; otherwise check node content
      const matchesSearch = !hasTextSearch || nodeMatchesSearch(node)
      // node is directly visible if both group AND text filters pass
      const isDirectlyVisible = groupVisible && matchesSearch

      const filteredChildren: TraceNode[] = []
      if (node.children?.length) {
        for (const child of node.children) {
          const filtered = filterNode(child as TraceNode)
          if (filtered) {
            filteredChildren.push(filtered)
          }
        }
      }

      const hasVisibleDescendant = filteredChildren.length > 0
      if (!isDirectlyVisible && !hasVisibleDescendant) {
        return null
      }

      return {
        ...node,
        children: filteredChildren,
      }
    }

    return filterNode(rootNode)
  })

  /**
   * Quick check if a single node should be shown based on current filters.
   */
  function shouldShowNode(node: TraceNode): boolean {
    const group = classifyNodeGroup(node) as NodeGroupName
    // node is directly visible if both group AND text filters pass
    if (isGroupVisible(group) && nodeMatchesSearch(node)) {
      return true
    }

    if (node.children && Array.isArray(node.children)) {
      for (const child of node.children) {
        if (shouldShowNode(child as TraceNode)) {
          return true
        }
      }
    }

    return false
  }

  /**
   * Checks if a node is shown only as a pass-through (ancestor of a visible node).
   */
  function isPassThroughNode(node: TraceNode): boolean {
    const group = classifyNodeGroup(node) as NodeGroupName
    const matchesSearch = nodeMatchesSearch(node)
    // not a pass-through if node matches both group AND text filters
    if (isGroupVisible(group) && matchesSearch) {
      return false
    }

    if (node.children && Array.isArray(node.children)) {
      for (const child of node.children) {
        if (shouldShowNode(child as TraceNode)) {
          return true
        }
      }
    }

    return false
  }

  // -----------------------------------------------------------------------------
  // Actions: Selection
  // -----------------------------------------------------------------------------

  function selectTrace(traceId: string | undefined) {
    selectedTraceId.value = traceId
    selectedNodeId.value = undefined
    resetCollapseState()
  }

  function toggleSelectNode(nodeId: string) {
    if (selectedNodeId.value === nodeId) {
      selectedNodeId.value = undefined
    } else {
      selectedNodeId.value = nodeId
    }
  }

  // -----------------------------------------------------------------------------
  // Actions: Trace Filters
  // -----------------------------------------------------------------------------

  function clearTraceFilters() {
    nameSearchQuery.value = ''
    resultSearchQuery.value = ''
    durationRange.value = null
    dateRange.value = { start: undefined, end: undefined }
  }

  // -----------------------------------------------------------------------------
  // Actions: Node Filters
  // -----------------------------------------------------------------------------

  /**
   * Toggle a group filter on/off.
   * When a group is selected, only nodes from selected groups (and ancestors) are shown.
   */
  function toggleGroupFilter(group: NodeGroupName) {
    if (activeGroupFilters.value.has(group)) {
      activeGroupFilters.value.delete(group)
    } else {
      activeGroupFilters.value.add(group)
    }
    activeGroupFilters.value = new Set(activeGroupFilters.value)
  }

  /**
   * Clear all node filters (group filters and text search).
   */
  function clearNodeFilters() {
    activeGroupFilters.value = new Set()
    nodeSearchQuery.value = ''
  }

  // -----------------------------------------------------------------------------
  // Actions: Collapse State
  // -----------------------------------------------------------------------------

  function toggleNodeCollapse(id: string) {
    if (collapsedNodeIds.value.has(id)) {
      collapsedNodeIds.value.delete(id)
    } else {
      collapsedNodeIds.value.add(id)
    }
  }

  function toggleGlobalCollapse() {
    const rootNode = selectedTrace.value?.parentNode
    if (!rootNode) return

    collapseAll.value = !collapseAll.value

    const allParentIds = getAllParentIds(rootNode)

    const protectedIds = new Set<string>()
    if (selectedNodeId.value) {
      const ancestors = getAllAncestors(rootNode, selectedNodeId.value)
      if (ancestors) {
        ancestors.forEach((id) => protectedIds.add(id))
      }
    }

    if (collapseAll.value) {
      allParentIds.forEach((id) => {
        if (id !== rootNode.id && !protectedIds.has(id)) {
          collapsedNodeIds.value.add(id)
        }
      })

      if (rootNode.id) collapsedNodeIds.value.delete(rootNode.id)
      protectedIds.forEach((id) => collapsedNodeIds.value.delete(id))
    } else {
      collapsedNodeIds.value.clear()
    }
  }

  function resetCollapseState() {
    collapsedNodeIds.value.clear()
    collapseAll.value = false
  }

  // -----------------------------------------------------------------------------
  // Actions: Sidebar
  // -----------------------------------------------------------------------------

  function setSidebarUserOverride(value: boolean) {
    sidebarUserOverride.value = value
  }

  return {
    // selection
    selectedTraceId,
    selectedNodeId,
    selectedTrace,
    selectedNode,
    isLoadingTrace,
    isTraceError,

    // trace filters
    nameSearchQuery,
    resultSearchQuery,
    durationRange,
    durationMax,
    dateRange,
    filteredTraces,

    // node filters
    activeGroupFilters,
    nodeSearchQuery,
    hasActiveNodeFilters,
    filteredTree,

    // ui state
    nodeViewMode,
    viewMode,
    effectiveViewMode,
    collapseAll,
    zoomLevel,
    collapsedNodeIds,

    // sidebar
    sidebarUserOverride,
    setSidebarUserOverride,

    // actions
    selectTrace,
    toggleSelectNode,
    clearTraceFilters,
    toggleGroupFilter,
    clearNodeFilters,
    isGroupVisible,
    isGroupFilterActive,
    shouldShowNode,
    isPassThroughNode,
    toggleNodeCollapse,
    toggleGlobalCollapse,
    resetCollapseState,
  }
})
