import {
  createContext,
  useCallback,
  useContext,
  useMemo,
  useState
} from 'react'
import { useSearchParams } from 'react-router-dom'
import PropTypes from 'prop-types'

const TableStateContext = createContext()

export const FILTER_URL_PARAM = 'filter'
export const SORT_URL_PARAM = 'sort'

const getSessionFilterKey = (scope) => `tableState:${scope}:filter`
const getSessionSortKey = (scope) => `tableState:${scope}:sort`

// eslint-disable-next-line react-refresh/only-export-components
export const getActiveFilterValues = (filterState) => {
  const active = {}
  Object.entries(filterState || {}).forEach(([k, v]) => {
    if (v !== '' && v !== undefined) active[k] = v
  })
  return active
}

const parseJsonParam = (raw) => {
  if (!raw) return null
  try {
    const parsed = JSON.parse(raw)
    return typeof parsed === 'object' && parsed !== null ? parsed : null
  } catch {
    return null
  }
}

const readFilterFromUrl = (searchParams) =>
  parseJsonParam(searchParams.get(FILTER_URL_PARAM))

const readSortFromUrl = (searchParams) => {
  const parsed = parseJsonParam(searchParams.get(SORT_URL_PARAM))
  if (!parsed?.field || !parsed?.order) return null
  return { field: parsed.field, order: parsed.order }
}

const readFilterFromSession = (scope) => {
  try {
    return parseJsonParam(sessionStorage.getItem(getSessionFilterKey(scope)))
  } catch {
    return null
  }
}

// eslint-disable-next-line react-refresh/only-export-components
export const hasStoredSessionFilter = (scope) => {
  if (!scope) return false
  return Object.keys(getActiveFilterValues(readFilterFromSession(scope) || {})).length > 0
}

const readSortFromSession = (scope) => {
  try {
    const parsed = parseJsonParam(sessionStorage.getItem(getSessionSortKey(scope)))
    if (!parsed?.field || !parsed?.order) return null
    return { field: parsed.field, order: parsed.order }
  } catch {
    return null
  }
}

const writeFilterToSession = (scope, filter) => {
  const key = getSessionFilterKey(scope)
  if (Object.keys(filter).length > 0) {
    sessionStorage.setItem(key, JSON.stringify(filter))
  } else {
    sessionStorage.removeItem(key)
  }
}

const writeSortToSession = (scope, sorter) => {
  const key = getSessionSortKey(scope)
  if (sorter?.field && sorter?.order) {
    sessionStorage.setItem(key, JSON.stringify(sorter))
  } else {
    sessionStorage.removeItem(key)
  }
}

const normalizeSorter = (sorter) => {
  if (sorter?.field && sorter?.order) {
    return { field: sorter.field, order: sorter.order }
  }
  return {}
}

export const TableStateProvider = ({ children }) => {
  const [searchParams, setSearchParams] = useSearchParams()
  const [pageFilters, setPageFilters] = useState({})

  const getPersistedFilter = useCallback(
    (scope, { useFilterInUrl = false, useFilterInSession = false } = {}) => {
      if (useFilterInUrl) {
        const fromUrl = readFilterFromUrl(searchParams)
        if (fromUrl) return fromUrl
      }
      if (useFilterInSession) {
        const fromSession = readFilterFromSession(scope)
        if (fromSession) return fromSession
      }
      return {}
    },
    [searchParams]
  )

  const getPersistedSorter = useCallback(
    (scope, { useSortInUrl = false, useSortInSession = false } = {}) => {
      if (useSortInUrl) {
        const fromUrl = readSortFromUrl(searchParams)
        if (fromUrl) return fromUrl
      }
      if (useSortInSession) {
        const fromSession = readSortFromSession(scope)
        if (fromSession) return fromSession
      }
      return {}
    },
    [searchParams]
  )

  const persistFilter = useCallback(
    (
      scope,
      filterState,
      { saveFilterInSession = false, saveFilterInUrl = false } = {}
    ) => {
      const active = getActiveFilterValues(filterState)
      if (saveFilterInSession) {
        writeFilterToSession(scope, active)
      }
      if (saveFilterInUrl) {
        const next = new URLSearchParams(searchParams)
        if (Object.keys(active).length > 0) {
          next.set(FILTER_URL_PARAM, JSON.stringify(active))
        } else {
          next.delete(FILTER_URL_PARAM)
        }
        setSearchParams(next, { replace: true })
      }
    },
    [searchParams, setSearchParams]
  )

  const persistSort = useCallback(
    (
      scope,
      sorter,
      { saveSortInSession = false, saveSortInUrl = false } = {}
    ) => {
      const nextSorter = normalizeSorter(sorter)
      if (saveSortInSession) {
        writeSortToSession(scope, nextSorter)
      }
      if (saveSortInUrl) {
        const next = new URLSearchParams(searchParams)
        if (nextSorter.field && nextSorter.order) {
          next.set(SORT_URL_PARAM, JSON.stringify(nextSorter))
        } else {
          next.delete(SORT_URL_PARAM)
        }
        setSearchParams(next, { replace: true })
      }
    },
    [searchParams, setSearchParams]
  )

  const persistTableState = useCallback(
    (
      scope,
      filterState,
      sorter,
      {
        saveFilterInSession = false,
        saveFilterInUrl = false,
        saveSortInSession = false,
        saveSortInUrl = false
      } = {}
    ) => {
      const active = getActiveFilterValues(filterState)
      const nextSorter = normalizeSorter(sorter)

      if (saveFilterInSession) {
        writeFilterToSession(scope, active)
      }
      if (saveSortInSession) {
        writeSortToSession(scope, nextSorter)
      }

      if (saveFilterInUrl || saveSortInUrl) {
        const next = new URLSearchParams(searchParams)
        if (saveFilterInUrl) {
          if (Object.keys(active).length > 0) {
            next.set(FILTER_URL_PARAM, JSON.stringify(active))
          } else {
            next.delete(FILTER_URL_PARAM)
          }
        }
        if (saveSortInUrl) {
          if (nextSorter.field && nextSorter.order) {
            next.set(SORT_URL_PARAM, JSON.stringify(nextSorter))
          } else {
            next.delete(SORT_URL_PARAM)
          }
        }
        setSearchParams(next, { replace: true })
      }
    },
    [searchParams, setSearchParams]
  )

  const setPageFilter = useCallback((path, filter) => {
    const active = getActiveFilterValues(filter)
    setPageFilters((prev) => {
      const hasActive = Object.keys(active).length > 0
      if (!hasActive) {
        if (!(path in prev)) return prev
        const next = { ...prev }
        delete next[path]
        return next
      }
      return { ...prev, [path]: active }
    })
  }, [])

  const hasPageFilter = useCallback(
    (path) => Object.keys(pageFilters[path] || {}).length > 0,
    [pageFilters]
  )

  const hasStoredFilter = useCallback((scope) => hasStoredSessionFilter(scope), [])

  const value = useMemo(
    () => ({
      getPersistedFilter,
      getPersistedSorter,
      persistFilter,
      persistSort,
      persistTableState,
      setPageFilter,
      hasPageFilter,
      hasStoredFilter,
      pageFilters
    }),
    [
      getPersistedFilter,
      getPersistedSorter,
      persistFilter,
      persistSort,
      persistTableState,
      setPageFilter,
      hasPageFilter,
      hasStoredFilter,
      pageFilters
    ]
  )

  return (
    <TableStateContext.Provider value={value}>
      {children}
    </TableStateContext.Provider>
  )
}

// eslint-disable-next-line react-refresh/only-export-components
export const useTableState = () => {
  const context = useContext(TableStateContext)
  if (!context) {
    throw new Error('useTableState must be used within a TableStateProvider')
  }
  return context
}

// eslint-disable-next-line react-refresh/only-export-components
export const useTableStatePersistence = ({
  scope,
  pagePath,
  saveFilterInSession = false,
  saveFilterInUrl = false,
  useFilterInSession = false,
  useFilterInUrl = false,
  saveSortInSession = false,
  saveSortInUrl = false,
  useSortInSession = false,
  useSortInUrl = false
}) => {
  const {
    getPersistedFilter: getPersistedFilterCtx,
    getPersistedSorter: getPersistedSorterCtx,
    persistFilter: persistFilterCtx,
    persistSort: persistSortCtx,
    persistTableState: persistTableStateCtx,
    setPageFilter
  } = useTableState()

  const readOptions = useMemo(
    () => ({ useFilterInUrl, useFilterInSession, useSortInUrl, useSortInSession }),
    [useFilterInUrl, useFilterInSession, useSortInUrl, useSortInSession]
  )

  const saveOptions = useMemo(
    () => ({
      saveFilterInSession,
      saveFilterInUrl,
      saveSortInSession,
      saveSortInUrl
    }),
    [saveFilterInSession, saveFilterInUrl, saveSortInSession, saveSortInUrl]
  )

  const getPersistedFilter = useCallback(
    () => getPersistedFilterCtx(scope, readOptions),
    [getPersistedFilterCtx, scope, readOptions]
  )

  const getPersistedSorter = useCallback(
    () => getPersistedSorterCtx(scope, readOptions),
    [getPersistedSorterCtx, scope, readOptions]
  )

  const persistFilter = useCallback(
    (filterState) => persistFilterCtx(scope, filterState, saveOptions),
    [persistFilterCtx, scope, saveOptions]
  )

  const persistSort = useCallback(
    (sorter) => persistSortCtx(scope, sorter, saveOptions),
    [persistSortCtx, scope, saveOptions]
  )

  const persistTableState = useCallback(
    (filterState, sorter) =>
      persistTableStateCtx(scope, filterState, sorter, saveOptions),
    [persistTableStateCtx, scope, saveOptions]
  )

  const registerPageFilter = useCallback(
    (filterState) => {
      if (pagePath) {
        setPageFilter(pagePath, filterState)
      }
    },
    [pagePath, setPageFilter]
  )

  return {
    getPersistedFilter,
    getPersistedSorter,
    persistFilter,
    persistSort,
    persistTableState,
    registerPageFilter,
    getActiveFilterValues
  }
}

TableStateProvider.propTypes = {
  children: PropTypes.node.isRequired
}

export default TableStateContext
