import {createContext, useContext, useReducer} from 'react'
import {type GestureResponderEvent} from 'react-native'

import {type CountryCode} from '#/lib/international-telephone-codes'
import {type ExistingContact} from '#/platform/contacts'
import type * as bsky from '#/types/bsky'

export type Contact = ExistingContact

export type Match = {
  profile: bsky.profile.AnyProfileView
  contact?: Contact
}

export type State =
  | {
      step: '1: phone input'
      phoneCountryCode?: CountryCode
      phoneNumber?: string
    }
  | {
      step: '2: verify number'
      phoneCountryCode: CountryCode
      phoneNumber: string
      lastSentAt: Date | null
    }
  | {
      step: '3: get contacts'
      phoneCountryCode: CountryCode
      phoneNumber: string
      token: string
      contacts?: Contact[]
    }
  | {
      step: '4: view matches'
      contacts: Contact[]
      matches: Match[]
      // rather than mutating `matches`, we keep track of dismissed matches
      // so we can roll back optimistic updates
      dismissedMatches: string[]
    }

export type Action =
  | {
      type: 'SUBMIT_PHONE_NUMBER'
      payload: {
        phoneCountryCode: CountryCode
        phoneNumber: string
      }
    }
  | {
      type: 'RESEND_VERIFICATION_CODE'
    }
  | {
      type: 'VERIFY_PHONE_NUMBER_SUCCESS'
      payload: {
        token: string
      }
    }
  | {
      type: 'GET_CONTACTS_SUCCESS'
      payload: {
        contacts: Contact[]
      }
    }
  | {
      type: 'SYNC_CONTACTS_SUCCESS'
      payload: {
        matches: Match[]
        // filter out matched contacts
        contacts: Contact[]
      }
    }
  | {
      type: 'BACK'
    }
  | {
      type: 'DISMISS_MATCH'
      payload: {
        did: string
      }
    }
  | {
      type: 'DISMISS_MATCH_FAILED'
      payload: {
        did: string
      }
    }

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'SUBMIT_PHONE_NUMBER': {
      assertCurrentStep(state, '1: phone input')
      return {
        step: '2: verify number',
        ...action.payload,
        lastSentAt: null,
      }
    }
    case 'RESEND_VERIFICATION_CODE': {
      assertCurrentStep(state, '2: verify number')
      return {
        ...state,
        lastSentAt: new Date(),
      }
    }
    case 'VERIFY_PHONE_NUMBER_SUCCESS': {
      assertCurrentStep(state, '2: verify number')
      return {
        step: '3: get contacts',
        token: action.payload.token,
        phoneCountryCode: state.phoneCountryCode,
        phoneNumber: state.phoneNumber,
      }
    }
    case 'BACK': {
      assertCurrentStep(state, '2: verify number')
      return {
        step: '1: phone input',
        phoneNumber: state.phoneNumber,
        phoneCountryCode: state.phoneCountryCode,
      }
    }
    case 'GET_CONTACTS_SUCCESS': {
      assertCurrentStep(state, '3: get contacts')
      return {
        ...state,
        contacts: action.payload.contacts,
      }
    }
    case 'SYNC_CONTACTS_SUCCESS': {
      assertCurrentStep(state, '3: get contacts')
      return {
        step: '4: view matches',
        contacts: action.payload.contacts,
        matches: action.payload.matches,
        dismissedMatches: [],
      }
    }
    case 'DISMISS_MATCH': {
      assertCurrentStep(state, '4: view matches')
      return {
        ...state,
        dismissedMatches: [
          ...new Set(state.dismissedMatches),
          action.payload.did,
        ],
      }
    }
    case 'DISMISS_MATCH_FAILED': {
      assertCurrentStep(state, '4: view matches')
      return {
        ...state,
        dismissedMatches: state.dismissedMatches.filter(
          did => did !== action.payload.did,
        ),
      }
    }
  }
}

class InvalidStateTransitionError extends Error {
  constructor(message: string) {
    super(message)
    this.name = 'InvalidStateTransitionError'
  }
}

function assertCurrentStep<S extends State['step']>(
  state: State,
  step: S,
): asserts state is Extract<State, {step: S}> {
  if (state.step !== step) {
    throw new InvalidStateTransitionError(
      `Invalid state transition: expecting ${step}, got ${state.step}`,
    )
  }
}

export function useFindContactsFlowState(
  initialState: State = {step: '1: phone input'},
) {
  return useReducer(reducer, initialState)
}

export const FindContactsGoBackContext = createContext<
  (() => void) | undefined
>(undefined)
export function useOnPressBackButton() {
  const goBack = useContext(FindContactsGoBackContext)
  if (!goBack) {
    return undefined
  }
  return (evt: GestureResponderEvent) => {
    evt.preventDefault()
    goBack()
  }
}
