import { useClient, useQuery } from '@based/react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { FinderView } from '../components/BasedFinder/index.js'
import { Filter, Sort } from '../utils/common.js'
import { getCalendarVisiblePeriod } from '../components/Calendar/index.js'

type QueryOptions = {
  view: FinderView
  // for table and grid
  offset?: number
  limit?: number
  sort?: Sort
  filter?: Filter
  // for calendar
  start?: number
  end?: number
}

type UseInfiniteQueryOptions = {
  view: FinderView
  sort?: Sort
  filter?: Filter
  query: (options: QueryOptions) => any
  totalQuery: () => any
  transformQueryResult: (queryResult: any) => any
  endpoints?: {
    query?: string
    totalQuery?: string
  }
}

type Chunk = {
  offset: number
  limit: number
  data?: any[]
  unsubscribe?: () => void
}

type CalendarChunk = {
  data?: any[]
  unsubscribe?: () => void
}

function useInfiniteQuery({
  view,
  sort,
  filter,
  query,
  totalQuery,
  transformQueryResult,
  endpoints,
}: UseInfiniteQueryOptions) {
  const client = useClient()
  const [chunks, setChunks] = useState<Chunk[]>([])
  const [calendarChunk, setCalendarChunk] = useState<CalendarChunk>()
  const [scroll, setScroll] = useState({
    first: 0,
    last: 0,
    realFirst: 0,
    realLast: 0,
  })
  const { data: totalData } = useQuery(endpoints?.totalQuery, totalQuery())

  const data = useMemo(() => {
    if (view !== 'calendar') {
      const { first, last } = scroll
      const res: any[] = []

      for (let i = 0; i < chunks.length; i++) {
        const chunk = chunks[i]
        if (!chunk?.data) continue
        const unwrappedData = transformQueryResult(chunk.data)

        for (let j = 0; j < unwrappedData.length; j++) {
          if (chunk.offset + j >= first && chunk.offset + j <= last) {
            res.push({ ...unwrappedData[j] })
          }
        }
      }

      return res
    } else {
      if (calendarChunk?.data) {
        return transformQueryResult(calendarChunk.data)
      }

      return []
    }
  }, [scroll, chunks, calendarChunk, view])

  const handleScroll = useCallback(
    (
      firstVisibleIndex: number,
      lastVisibleIndex: number,
      realFirst: number,
      realLast: number,
    ) => {
      if (
        scroll.first === firstVisibleIndex &&
        scroll.last === lastVisibleIndex
      )
        return

      setScroll({
        first: firstVisibleIndex,
        last: lastVisibleIndex,
        realFirst,
        realLast,
      })

      const chunkSize = 128
      const firstChunk = Math.floor(firstVisibleIndex / chunkSize)
      const lastChunk = Math.ceil(lastVisibleIndex / chunkSize)

      for (let i = firstChunk; i < lastChunk; i++) {
        if (!chunks[i]) {
          setChunks((p) => {
            const c = [...p]
            c[i] = { offset: i * chunkSize, limit: chunkSize }
            return c
          })
        }

        const offset = i * chunkSize
        const limit = chunkSize

        const unsubscribe = client
          .query(
            endpoints?.query ?? 'db',
            query({
              view,
              offset,
              limit,
              sort,
              filter,
            }),
          )
          .subscribe((data) => {
            setChunks((p) => {
              const c = [...p]
              c[i] = { offset, limit, unsubscribe, data }
              return c
            })
          })
      }

      for (let j = 0; j < chunks.length; j++) {
        if (firstChunk > j || lastChunk < j) {
          const chunk = chunks[j]

          if (chunk) {
            chunk.unsubscribe?.()
          }
        }
      }
    },
    [scroll, chunks, query, view, client, sort, filter],
  )

  const handleCalendar = useCallback(
    (start: number, end: number) => {
      calendarChunk?.unsubscribe?.()
      setCalendarChunk(undefined)

      const unsubscribe = client
        .query(
          endpoints?.query ?? 'db',
          query({ view: 'calendar', start, end }),
        )
        .subscribe((data) => {
          setCalendarChunk(() => ({ data, unsubscribe }))
        })
    },
    [query, client, calendarChunk],
  )

  const reset = useCallback(() => {
    setScroll({
      first: 0,
      last: 0,
      realFirst: 0,
      realLast: 0,
    })
    setChunks([])
  }, [])

  useEffect(() => {
    if (view === 'calendar' && !calendarChunk) {
      const { start, end } = getCalendarVisiblePeriod(Date.now(), 'monthly')
      handleCalendar(start, end)
    }
  }, [handleCalendar, view, calendarChunk])

  return {
    data,
    total: totalData?.total,
    handleScroll,
    handleCalendar,
    scroll,
    reset,
  }
}

export { useInfiniteQuery }
export type { UseInfiniteQueryOptions }
