'use client'

import React, { useEffect, useRef, useSyncExternalStore } from 'react'
import { Modal, ModalContent, ModalTitle } from '../Modal'
import { toast } from '../Toast'
import { TransactionalModalTemplate } from './TransactionalModalTemplate'
import type {
  TransactionStep,
  TransactionConfig,
  TransactionModal,
  TransactionModalState,
} from './types'

type Action =
  | {
      type: 'COMPLETE_TRANSACTION'
      successLinkHref?: string
      successLinkText?: string
      successLinkAction?: { label: string; onClick: VoidFunction }
      closeButtonText?: string
      closeButtonCallback?: VoidFunction
    }
  | {
      type: 'START_TRANSACTION'
      steps: TransactionStep[]
      successBody: string
      successHeading: string
      context: string
    }
  | { type: 'ERROR'; error: string }
  | {
      type: 'NEXT_STEP'
      transactions?: { type?: string; hash: string }[]
      pending?: boolean
    }

type CompleteTransaction = (
  state?: Pick<
    TransactionModalState,
    | 'successLinkHref'
    | 'successLinkText'
    | 'successLinkAction'
    | 'closeButtonText'
    | 'closeButtonCallback'
  > & { delay?: number },
) => Promise<void>

type HandleTransactionError = (error: any) => Promise<void>

type NextStep = (
  state?: Pick<TransactionModalState, 'transactions'> & { pending?: boolean },
) => void

type StartTransaction = (newTransaction: TransactionConfig) => void

/**
 * Simple external store for modal state.
 * Using useSyncExternalStore allows us to:
 * 1. Keep the TransactionModalComponent callback identity stable (preventing blinking)
 * 2. Still properly trigger re-renders when state changes (fixing React 19 issues)
 */
type ModalStateStore = {
  state: TransactionModalState
  networkExplorerTxUrl?: string
}

type ModalStore = {
  getSnapshot: () => ModalStateStore
  subscribe: (listener: () => void) => () => void
}

const createModalStore = (
  initialState: TransactionModalState,
  initialNetworkExplorerTxUrl?: string,
): ModalStore & {
  update: (state: TransactionModalState, url?: string) => void
} => {
  let currentState: ModalStateStore = {
    state: initialState,
    networkExplorerTxUrl: initialNetworkExplorerTxUrl,
  }
  const listeners = new Set<() => void>()

  return {
    getSnapshot: () => currentState,
    subscribe: (listener: () => void) => {
      listeners.add(listener)
      return () => listeners.delete(listener)
    },
    update: (state: TransactionModalState, networkExplorerTxUrl?: string) => {
      currentState = { state, networkExplorerTxUrl }
      listeners.forEach((listener) => listener())
    },
  }
}

export const initialState: TransactionModalState = {
  progressBarSteps: [],
  steps: [],
  error: undefined,
  isTransactionComplete: false,
  currentStepIndex: 0,
  nextStepIndex: 1,
  successBody: '',
  successHeading: '',
  successLinkHref: '',
  successLinkText: '',
  successLinkAction: undefined,
  closeButtonText: '',
  closeButtonCallback: () => undefined,
  context: '',
}

export const reducer = (
  state: TransactionModalState,
  action: Action,
): TransactionModalState => {
  const stateCopy: TransactionModalState = JSON.parse(JSON.stringify(state))

  switch (action.type) {
    case 'START_TRANSACTION':
      return {
        ...initialState, // Show initial transaction state when user re-opens the modal.
        progressBarSteps: action.steps.map((step, index) => ({
          status: index === 0 ? 'ACTIVE' : 'PENDING',
          label: step.label,
        })),
        steps: action.steps,
        successBody: action.successBody,
        successHeading: action.successHeading,
        context: action.context,
      }

    case 'NEXT_STEP':
      stateCopy.progressBarSteps[stateCopy.currentStepIndex].status = 'DONE'
      stateCopy.progressBarSteps[stateCopy.nextStepIndex].status =
        action.transactions || action.pending ? 'IN_PROGRESS' : 'DONE'

      return {
        ...stateCopy,
        currentStepIndex: stateCopy.nextStepIndex,
        nextStepIndex: stateCopy.nextStepIndex + 1,
        transactions: action.transactions,
      }

    case 'COMPLETE_TRANSACTION':
      return {
        ...stateCopy,
        progressBarSteps: state.progressBarSteps.map((step) => ({
          ...step,
          status: 'DONE',
        })),
        isTransactionComplete: true,
        successLinkHref: action.successLinkHref,
        successLinkText: action.successLinkText,
        successLinkAction: action.successLinkAction,
        closeButtonText: action.closeButtonText,
        closeButtonCallback: action.closeButtonCallback,
      }

    case 'ERROR':
      stateCopy.progressBarSteps[stateCopy.currentStepIndex].status = 'ERROR'
      return { ...stateCopy, error: action.error }
  }
}

type Props = {
  isOpen?: boolean
  closeModal: VoidFunction
  store: ModalStore
  setModalClosedBeforeCompletion: (value: boolean) => void
  isInescapable?: boolean
  setIsOpen: React.Dispatch<React.SetStateAction<boolean>>
  router: {
    push: (href: string) => void
  }
}

const TransactionModal = ({
  isOpen = true,
  closeModal,
  store,
  setModalClosedBeforeCompletion,
  isInescapable = false,
  setIsOpen,
  router,
}: Props) => {
  // Subscribe to state changes via useSyncExternalStore
  // This ensures proper re-renders while keeping component identity stable
  const { state, networkExplorerTxUrl } = useSyncExternalStore(
    store.subscribe,
    store.getSnapshot,
    store.getSnapshot, // Server snapshot (same as client for this use case)
  )

  const contextId = state.context
  const stepId =
    state.steps[state.currentStepIndex]?.id ?? state.currentStepIndex.toString()

  const onCloseClick = React.useCallback(() => {
    if (!state.isTransactionComplete) {
      setModalClosedBeforeCompletion(true)
    }
    state.closeButtonCallback?.()
    closeModal()
  }, [
    state.isTransactionComplete,
    setModalClosedBeforeCompletion,
    state.closeButtonCallback,
    closeModal,
  ])

  const onSuccessClick = React.useCallback(
    () => router.push(`${state.successLinkHref || '/'}`),
    [router, state.successLinkHref],
  )

  const onOpenChange = React.useCallback(
    (open: boolean) => {
      if (isInescapable && !state.isTransactionComplete) {
        return
      }
      if (!open) {
        setModalClosedBeforeCompletion(true)
      }
      return setIsOpen(open)
    },
    [
      isInescapable,
      state.isTransactionComplete,
      setModalClosedBeforeCompletion,
      setIsOpen,
    ],
  )

  return (
    <Modal open={isOpen} onOpenChange={onOpenChange}>
      <ModalContent
        data-testid="transaction-modal"
        showCloseButton={false}
        size="lg"
      >
        {/*
          Hidden title keeps DialogContent accessible for screen readers:
          `DialogContent` requires a `DialogTitle` for the component to be accessible for screen reader users.
        */}
        <ModalTitle className="sr-only">Transaction modal</ModalTitle>
        <TransactionalModalTemplate
          {...{
            state,
            networkExplorerTxUrl,
            contextId,
            stepId,
            onCloseClick,
            onSuccessClick,
            isInescapable,
          }}
        />
      </ModalContent>
    </Modal>
  )
}

/**
 * This modal makes an assumption that there are always at least 2
 * steps in the transaction.
 */
export function useTransactionalModalBootstrap({
  router,
  networkExplorerTxUrl,
}: {
  networkExplorerTxUrl?: string
  router: {
    push: (href: string) => void
  }
}) {
  const [state, dispatch] = React.useReducer(reducer, initialState)
  const [isOpen, setIsOpen] = React.useState(false)
  const openModal = React.useCallback(() => setIsOpen(true), [setIsOpen])
  const closeModal = React.useCallback(() => setIsOpen(false), [setIsOpen])
  const [modalClosedBeforeCompletion, setModalClosedBeforeCompletion] =
    React.useState(false)

  const startTransaction: StartTransaction = (newTransaction) => {
    dispatch({ type: 'START_TRANSACTION', ...newTransaction })
    openModal()
  }

  const setErrorOnCurrentStep = (error: string) => {
    dispatch({ type: 'ERROR', error })
  }

  /**
   * This handles the error that is thrown by the transaction.
   *
   * Since the type of error is any we have to commit some typescript crimes to differentiate between Wagmi and Ethers errors.
   */
  const handleTransactionError: HandleTransactionError = (error) => {
    console.error(error)

    // both Viem and Ethers return a "message" key in the error object
    const ethersErrorMessage = 'message' in error ? error.message : undefined
    const ethersErrorCode = 'code' in error ? error.code : undefined

    // check if the error is a Wagmi error otherwise fall back to ethers error
    const viemErrorCode =
      'cause' in error && error.cause ? error.cause.code : undefined
    const viemErrorMessage =
      'shortMessage' in error ? error.shortMessage : undefined

    // Give preference to the viem message since the ethers error is defined in both error objects
    let errorMessage =
      viemErrorMessage || ethersErrorMessage || 'Something went wrong.'

    const errorCode = viemErrorCode || ethersErrorCode

    switch (errorCode) {
      case -32603:
        errorMessage =
          'details' in error
            ? `${errorMessage}.. ${error.details}`
            : errorMessage
        break
      case 'ACTION_REJECTED':
      case 4001:
        errorMessage = 'Transaction was denied by the user.'
        break
      case 'UNPREDICTABLE_GAS_LIMIT':
        errorMessage =
          'Transaction was not submitted as gas estimation failed - it may revert.'
        break
    }

    setErrorOnCurrentStep(errorMessage)

    return Promise.reject(error)
  }

  const nextStep: NextStep = React.useCallback(
    (state = {}) => {
      dispatch({ type: 'NEXT_STEP', ...state })
    },
    [dispatch],
  )

  const completeTransaction: CompleteTransaction = async ({
    delay = 12000,
    ...state
  } = {}) => {
    /**
     * We have to hard wait here to account for Atlas block ingestion delay
     * If we don't do this, the user's on-chain actions might not be reflected in the UI.
     *
     * TODO: Remove this wait and instead wait for n blocks where n is network dependent https://smartcontract-it.atlassian.net/browse/FRONT-3581
     */
    await new Promise((t) => setTimeout(t, delay))
    dispatch({ type: 'COMPLETE_TRANSACTION', ...state })
  }

  useEffect(() => {
    if (
      modalClosedBeforeCompletion &&
      (state.error || state.isTransactionComplete)
    ) {
      toast({
        toastId: `${state.context}-modal-toast-tx-complete`,
        title: state.error ? 'Error' : state.successHeading,
        variant: state.error ? 'error' : 'success',
        description: state.error ? state.error : state.successBody,
        action: state.error ? undefined : state.successLinkAction,
      })
      setModalClosedBeforeCompletion(false)
    }
  }, [
    modalClosedBeforeCompletion,
    state.isTransactionComplete,
    state.context,
    state.error,
    state.successHeading,
    setModalClosedBeforeCompletion,
  ])

  // Create a stable store that persists across renders.
  // The modal subscribes to this store via useSyncExternalStore, which:
  // 1. Keeps the TransactionModalComponent callback identity stable (prevents blinking)
  // 2. Properly triggers re-renders when state changes (fixes React 19 compatibility)
  const storeRef = useRef<ReturnType<typeof createModalStore> | null>(null)
  if (!storeRef.current) {
    storeRef.current = createModalStore(state, networkExplorerTxUrl)
  }

  // Update the store whenever state or networkExplorerTxUrl changes
  useEffect(() => {
    storeRef.current?.update(state, networkExplorerTxUrl)
  }, [state, networkExplorerTxUrl])

  const TransactionModalComponent = React.useCallback(
    ({ isInescapable }: { isInescapable?: boolean }) => (
      <TransactionModal
        isOpen={isOpen}
        setIsOpen={setIsOpen}
        closeModal={closeModal}
        store={storeRef.current!}
        setModalClosedBeforeCompletion={setModalClosedBeforeCompletion}
        isInescapable={isInescapable}
        router={router}
      />
    ),
    [isOpen, setIsOpen, closeModal, setModalClosedBeforeCompletion, router],
  )
  return {
    ...state,
    completeTransaction,
    nextStep,
    handleTransactionError,
    startTransaction,
    TransactionModal: TransactionModalComponent,
    setIsOpen,
    isOpen,
  }
}
