import { createContext, useContext, useEffect, useState, type ReactNode } from 'react'
import { getCodes, type CodeMap } from '../api/client'

/**
 * 화면 선택지를 서버에서 한 번 받아 앱 전체에 나눠준다.
 *
 * 화면마다 따로 부르지 않는 이유는 코드가 자주 안 바뀌고, 선택지를 기다리느라 폼이
 * 깜빡이는 것을 피하기 위해서다. 로그인 화면에서는 필요 없으므로 인증 이후에 감싼다.
 */
const CodeContext = createContext<CodeMap | null>(null)

type LoadState = 'loading' | 'ready' | 'error'

/**
 * @param codes 넘기면 서버를 부르지 않고 그 값을 그대로 쓴다. 테스트에서 화면 하나만
 *   떼어 그릴 때 쓰라고 열어둔 자리다.
 */
export function CodeProvider({ children, codes: given }: { children: ReactNode; codes?: CodeMap }) {
  const [codes, setCodes] = useState<CodeMap | null>(given ?? null)
  const [state, setState] = useState<LoadState>(given ? 'ready' : 'loading')
  const [message, setMessage] = useState('')

  async function load() {
    setState('loading')
    try {
      setCodes(await getCodes())
      setState('ready')
    } catch (e) {
      setMessage((e as Error).message)
      setState('error')
    }
  }

  useEffect(() => {
    if (!given) {
      void load()
    }
  }, [given])

  if (state === 'loading') {
    return <div className="p-8 text-sm text-gray-500">불러오는 중…</div>
  }

  // 선택지를 못 받은 채로 화면을 그리면 빈 드롭다운이 뜨고, 거기서 저장하면 값이
  // 지워진다. 그리지 않고 오류를 보여주는 편이 안전하다.
  if (state === 'error' || !codes) {
    return (
      <div className="flex min-h-screen flex-col items-center justify-center gap-3 p-8 text-sm">
        <p className="text-red-600">선택지 정보를 불러오지 못했습니다. {message}</p>
        <button
          type="button"
          onClick={() => void load()}
          className="rounded-md border border-gray-300 px-3 py-1.5 text-sm"
        >
          다시 시도
        </button>
      </div>
    )
  }

  return <CodeContext.Provider value={codes}>{children}</CodeContext.Provider>
}

/** Provider 밖에서 부르면 개발 중에 바로 알아채도록 예외를 던진다. */
export function useCodeMap(): CodeMap {
  const codes = useContext(CodeContext)
  if (!codes) {
    throw new Error('CodeProvider 안에서만 쓸 수 있습니다.')
  }
  return codes
}
