import {createContext, useContext, useMemo, useRef} from 'react'
import {
  StyleSheet,
  TextInput,
  View,
  type TextInputProps,
  type ViewStyle,
} from 'react-native'

import {MAX_FONT_SCALE} from '@/components/ui/typography'
import {useInteractionState} from '@/hooks/use-interaction-state'
import {mergeRefs} from '@/lib/merge-refs'
import {
  atoms as a,
  FONT_FAMILY,
  useTheme,
  type ViewStyleProp,
} from '@/sprinkles'

const HITSLOP_20 = {top: 20, bottom: 20, left: 20, right: 20}
const INPUT_PADDING_Y = 13
const CHROME_RADIUS = 10
const LINE_HEIGHT_RATIO = 1.2
const TRANSPARENT = 'transparent'

const Context = createContext<{
  inputRef: React.RefObject<TextInput | null> | null
  isInvalid: boolean
  focused: boolean
  onFocus: () => void
  onBlur: () => void
}>({
  inputRef: null,
  isInvalid: false,
  focused: false,
  onFocus: () => {},
  onBlur: () => {},
})
Context.displayName = 'TextFieldContext'

export type RootProps = React.PropsWithChildren<{
  isInvalid?: boolean
  style?: ViewStyleProp
}>

export function Root({
  children,
  isInvalid = false,
  style,
}: RootProps) {
  const inputRef = useRef<TextInput>(null)
  const {
    state: focused,
    onIn: onFocus,
    onOut: onBlur,
  } = useInteractionState()

  const context = useMemo(
    () => ({
      inputRef,
      focused,
      onFocus,
      onBlur,
      isInvalid,
    }),
    [focused, onFocus, onBlur, isInvalid],
  )

  return (
    <Context.Provider value={context}>
      <View
        style={[
          a.flex_row,
          a.align_center,
          a.relative,
          a.w_full,
          a.px_md,
          style,
        ]}
      >
        {children}
      </View>
    </Context.Provider>
  )
}

export function useSharedInputStyles() {
  const t = useTheme()
  return useMemo(() => {
    const focus: ViewStyle = {
      borderColor: t.palette.primary_500,
    }
    const error: ViewStyle = {
      backgroundColor: t.palette.negative_25,
      borderColor: t.palette.negative_300,
    }

    return {
      chromeFocus: focus,
      chromeError: error,
    }
  }, [t])
}

export type InputProps = Omit<
  TextInputProps,
  'value' | 'onChangeText' | 'placeholder'
> & {
  label: string
  value?: string
  onChangeText?: (value: string) => void
  isInvalid?: boolean
  inputRef?:
    | React.RefObject<TextInput | null>
    | React.ForwardedRef<TextInput>
  // pass `null` for no placeholder; otherwise falls back to label
  placeholder?: string | null | undefined
}

export function createInput(Component: typeof TextInput) {
  return function Input({
    label,
    placeholder,
    value,
    onChangeText,
    onFocus,
    onBlur,
    isInvalid,
    inputRef,
    style,
    ...rest
  }: InputProps) {
    const t = useTheme()
    const ctx = useContext(Context)
    const withinRoot = Boolean(ctx.inputRef)

    const {chromeFocus, chromeError} = useSharedInputStyles()

    // when used standalone (no parent Root), wrap self in one so the
    // input still gets focus/error chrome
    if (!withinRoot) {
      return (
        <Root isInvalid={isInvalid}>
          <Input
            label={label}
            placeholder={placeholder}
            value={value}
            onChangeText={onChangeText}
            isInvalid={isInvalid}
            {...rest}
          />
        </Root>
      )
    }

    const refs = mergeRefs([ctx.inputRef, inputRef!].filter(Boolean))

    const flattened = StyleSheet.flatten([
      a.relative,
      a.z_20,
      a.flex_1,
      a.text_md,
      t.atoms.text,
      a.px_xs,
      {
        fontFamily: FONT_FAMILY,
        // paddingVertical doesn't work w/multiline
        lineHeight: a.text_md.fontSize * LINE_HEIGHT_RATIO,
        textAlignVertical: (rest.multiline ? 'top' : undefined) as
          | 'top'
          | undefined,
        minHeight: rest.multiline ? 80 : undefined,
        minWidth: 0,
        paddingTop: INPUT_PADDING_Y,
        paddingBottom: INPUT_PADDING_Y,
      },
      style,
    ])

    return (
      <>
        <Component
          hitSlop={HITSLOP_20}
          maxFontSizeMultiplier={MAX_FONT_SCALE}
          // multiline inputs auto-grow instead of scrolling internally
          // so the surrounding ScrollView stays the single scroll surface
          scrollEnabled={rest.multiline ? false : undefined}
          {...rest}
          accessibilityLabel={label}
          ref={refs}
          value={value}
          onChangeText={onChangeText}
          onFocus={(e) => {
            ctx.onFocus()
            onFocus?.(e)
          }}
          onBlur={(e) => {
            ctx.onBlur()
            onBlur?.(e)
          }}
          placeholder={
            placeholder === null ? undefined : placeholder || label
          }
          placeholderTextColor={t.palette.contrast_500}
          keyboardAppearance={t.name === 'light' ? 'light' : 'dark'}
          style={flattened}
        />

        <View
          style={[
            a.z_10,
            a.absolute,
            a.inset_0,
            {borderRadius: CHROME_RADIUS},
            t.atoms.bg_contrast_50,
            {borderColor: TRANSPARENT, borderWidth: 1},
            ctx.focused ? chromeFocus : {},
            ctx.isInvalid || isInvalid ? chromeError : {},
          ]}
        />
      </>
    )
  }
}

export const Input = createInput(TextInput)
