/* eslint-disable  @typescript-eslint/no-explicit-any */
import { useState, useEffect } from 'react'

import { queryParamStorage } from 'features/QueryParamsStorage'

import { readStorageInitialValue } from './helpers'

const defaultValidator = () => true
const defaultSerializer = (key: string, value: any) => value

type Args<T> = {
  clearOnUnmount?: boolean,

  /**
   * Дефолтное знаение
   * используется если полученный из сторежда значение не валидно
   */
  defaultValue?: T,

  /**
   * Начальное значение
   * если указано пропускает считывание из стореджа
   */
  initialValue?: T,
  key: string,
  serialize?: (key: string, value: T) => string,

  /**
   * функция для валидации полученного значения из стора
   * если значение не валидно то используется defaultValue.
   * Если не передан валидатор то полученное значение из стореджа
   * считается валидным.
   */
  validator?: (value: T) => boolean,
}

/**
 * Хук высшего порядка, получает сторедж
 * и возвращает хук работающий с данным стореджем
 * Хук считывает значение из стора и использует как начальное значение стейта,
 * при вызове сеттера обновляется сторедж и реакт стейет
 */
const createHook = (storage: Storage) => (
  <T extends any>({
    clearOnUnmount,
    defaultValue,
    initialValue,
    key,
    serialize = defaultSerializer,
    validator = defaultValidator,
  }: Args<T>) => {
    const getInitialState = () => {
      const storeValue = readStorageInitialValue(storage, key)
      const isValid = validator(storeValue)
      return isValid ? storeValue : defaultValue
    }

    const [state, setState] = useState<T>(initialValue || getInitialState)

    useEffect(() => {
      const storeValue = readStorageInitialValue(storage, key)
      const isValid = validator(storeValue)
      if (!isValid) {
        storage.removeItem(key)
      }
    }, [key, validator])

    useEffect(() => {
      storage.setItem(key, JSON.stringify(serialize(key, state)))
    }, [
      key,
      state,
      serialize,
    ])

    useEffect(() => {
      if (clearOnUnmount) return () => storage.removeItem(key)
      return undefined
    }, [key, clearOnUnmount])

    return [state, setState] as const
  }
)

export const useLocalStore = createHook(localStorage)
export const useSessionStore = createHook(sessionStorage)
export const useQueryParamStore = createHook(queryParamStorage)
