/**
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: MIT-0
 */

import { createContext, PropsWithChildren, useCallback, useContext, useEffect, useMemo, useRef } from 'react'
import { castDraft } from 'immer'
import { useImmer } from 'use-immer'
import { IQuery } from '../../../services/base/queryService'

interface State<TData> {
  readonly items: TData[]
  readonly isLoading: boolean
}

interface Updater<TData> {
  readonly setItems: (items: TData[]) => void
  readonly refreshItems: () => void

  readonly getItem: (id: IdType) => Promise<void>
  // readonly createItem: (item: TData) => void
  // readonly updateItem: (item: TData, persist?: boolean) => void
}

export type ContextInterface<TData> = [State<TData>, Updater<TData>]
export type IdSelector<TData> = (item: TData) => IdType

const contextStore: Record<string, any> = {}
export function createQueryContext<TData>(key: string): React.Context<ContextInterface<TData> | null> {
  if (contextStore[key] == null) {
    contextStore[key] = createContext<ContextInterface<TData> | null>(null)
  }

  return contextStore[key]
}

const queryProviderStore: Record<string, any> = {}
export function createQueryProvider<TData, TService extends IQuery<TData>>(
  key: string,
  service: TService,
  idSelector?: IdSelector<TData>,
): any {
  const idSelect: IdSelector<TData> =
    idSelector ||
    ((item: TData) => {
      return (item as any).Id as IdType
    })

  if (queryProviderStore[key] == null) {
    const _queryProvider = ({ children }: PropsWithChildren<any>): any => {
      const [state, updateState] = useImmer<State<TData>>({
        items: [],
        isLoading: false,
      })

      const stateRef = useRef<State<TData>>(state)
      stateRef.current = state

      const fetchItems = useCallback(async () => {
        updateState((draft) => {
          draft.isLoading = true
        })
        const items = await service.list()
        updateState((draft) => {
          draft.items = castDraft(items)
          draft.isLoading = false
        })
      }, [updateState])

      const fetchItem = useCallback(
        async (id: IdType) => {
          updateState((draft) => {
            draft.isLoading = true
          })
          const item = await service.getItem(id)
          updateState((draft) => {
            const index = draft.items.findIndex((x) => idSelect(x as TData) === idSelect(item))

            if (index < 0) {
              draft.items.push(castDraft(item))
            } else {
              draft.items[index] = castDraft(item)
            }
          })
        },
        [updateState],
      )

      useEffect(() => {
        fetchItems()
      }, [fetchItems])

      const updater = useMemo<Updater<TData>>((): Updater<TData> => {
        return {
          setItems: (items: TData[]): void => {
            updateState((draft) => {
              draft.items = castDraft(items)
            })
          },

          refreshItems: (): void => {
            ;(async () => {
              await fetchItems()
            })()
          },

          getItem: async (id: IdType): Promise<void> => {
            await fetchItem(id)
          },
        }
      }, [updateState, fetchItems, fetchItem])

      const contextValue = useMemo<ContextInterface<TData>>(() => [state, updater], [state, updater])

      const QueryContext = createQueryContext<TData>(key)

      return <QueryContext.Provider value={contextValue}>{children}</QueryContext.Provider>
    }

    queryProviderStore[key] = _queryProvider
  }

  return queryProviderStore[key]
}

export function useQueryContext<TData>(key: string): ContextInterface<TData> {
  const queryContext = createQueryContext<TData>(key)
  const context = useContext(queryContext)

  if (context == null) {
    throw new Error(`QueryContext<${key}> is null`)
  }

  return context
}