import type { UseFormGraphQlOptions } from '@graphcommerce/ecommerce-ui'
import type { ApolloClient } from '@graphcommerce/graphql'
import { useApolloClient } from '@graphcommerce/graphql'
import type { CrosssellsQuery } from '@graphcommerce/magento-cart'
import { CrosssellsDocument, useFormGqlMutationCart } from '@graphcommerce/magento-cart'
import { nonNullable } from '@graphcommerce/next-ui'
import type { SxProps, Theme } from '@mui/material'
import { Box } from '@mui/material'
import { useRouter } from 'next/router'
import { useMemo, useRef } from 'react'
import { AddProductsToCartDocument, type AddProductsToCartMutation } from './AddProductsToCart.gql'
import {
  AddProductsToCartSnackbar,
  type AddProductsToCartSnackbarProps,
} from './AddProductsToCartSnackbar'
import { toUserErrors } from './toUserErrors'
import {
  AddProductsToCartContext,
  type AddProductsToCartFields,
  type RedirectType,
} from './useFormAddProductsToCart'

export type AddProductsToCartFormProps = {
  children: React.ReactNode
  sx?: SxProps<Theme>
  redirect?: RedirectType
  snackbarProps?: AddProductsToCartSnackbarProps
} & UseFormGraphQlOptions<AddProductsToCartMutation, AddProductsToCartFields>

const name = 'AddProductsToCartForm'

/**
 * Component that handles adding products to the cart. Used on the product page, but can be used for
 * any product listing.
 *
 * Can be configured globally in your theme.ts;
 *
 * - Uses react-hook-form's useForm hook under the hood and exposes the form as a context which can be
 *   consumed with `useFormAddProductsToCart` hook.
 * - Cleans up the submitted data.
 * - Redirects the user to the cart/checkout/added page after successful submission.
 */
export function AddProductsToCartForm(props: AddProductsToCartFormProps) {
  const { children, redirect, onComplete, sx, snackbarProps, ...formProps } = props
  const router = useRouter()
  const client = useApolloClient()
  const crosssellsQuery = useRef<Promise<ApolloClient.QueryResult<CrosssellsQuery>> | undefined>(
    undefined,
  )

  const form = useFormGqlMutationCart<AddProductsToCartMutation, AddProductsToCartFields>(
    AddProductsToCartDocument,
    {
      ...formProps,
      defaultValues: { redirect },
      // We're stripping out incomplete entered options.
      onBeforeSubmit: async (variables) => {
        const variables2 = (await formProps.onBeforeSubmit?.(variables)) ?? variables
        if (variables2 === false) return false

        const { cartId, cartItems, ...rest } = variables2

        const requestData = {
          cartId,
          cartItems: cartItems
            .filter((cartItem) => cartItem.sku && cartItem.quantity !== 0)
            .map(
              ({
                selected_options_record = {},
                entered_options_record = {},
                keep_sku,
                ...cartItem
              }) => ({
                ...cartItem,
                quantity: cartItem.quantity || 1,
                selected_options: [
                  ...(cartItem.selected_options ?? []).filter(nonNullable),
                  ...Object.values(selected_options_record).flat(1).filter(nonNullable),
                ],
                entered_options: [
                  ...(cartItem.entered_options ?? []).filter(nonNullable),
                  ...Object.entries(entered_options_record).map(([uid, value]) => {
                    if (value instanceof Date) {
                      const dateValue = value.toISOString().replace(/.000Z/, '').replace('T', ' ')
                      return { uid, value: dateValue }
                    }
                    return { uid, value: value.toString() }
                  }),
                ],
              }),
            ),
          ...rest,
        }

        const sku = requestData.cartItems[requestData.cartItems.length - 1]?.sku

        if (sku && requestData.redirect === 'added') {
          // Preload crosssells
          crosssellsQuery.current = client.query({
            query: CrosssellsDocument,
            variables: { pageSize: 1, filters: { sku: { eq: sku } } },
          })
        }

        return requestData
      },
      onComplete: async (result, variables) => {
        await onComplete?.(result, variables)

        // After the form has been submitted, we're resetting the submitted SKU's to disable them all.
        // We use the presence of the SKU to determine if the line is 'enabled'.
        // After submitting we reset (when using multiple buttons in a single form)
        form.getValues('cartItems').forEach((item, index) => {
          if (item.sku && !item.keep_sku) form.setValue(`cartItems.${index}.sku`, '')
        })

        if (toUserErrors(result.data).length || result.errors?.length || !variables.redirect) return

        if (variables.redirect === 'added') {
          await crosssellsQuery.current
          const method = router.pathname.startsWith('/checkout/added')
            ? router.replace
            : router.push
          await method({
            pathname: '/checkout/added',
            query: { sku: variables.cartItems.map((i) => i.sku) },
          })
        } else if (variables.redirect) {
          await router.push({ pathname: variables.redirect })
        }

        form.resetField('redirect', { defaultValue: redirect })
      },
    },
  )

  const submit = form.handleSubmit(() => {})

  return (
    <AddProductsToCartContext.Provider value={useMemo(() => ({ ...form }), [form])}>
      <Box component='form' onSubmit={submit} noValidate sx={sx} className={name}>
        {children}
      </Box>
      <AddProductsToCartSnackbar {...snackbarProps} />
    </AddProductsToCartContext.Provider>
  )
}
