import {
  createContext,
  forwardRef,
  useContext,
  useId,
  type InputHTMLAttributes,
} from 'react'
import { cn } from '@/lib/utils'

interface RadioGroupContextValue {
  name: string
  value?: string
  disabled?: boolean
  hasError?: boolean
  onChange?: (value: string) => void
}

const RadioGroupContext = createContext<RadioGroupContextValue | null>(null)

export interface RadioGroupProps {
  label?: string
  description?: string
  error?: string
  value?: string
  defaultValue?: string
  disabled?: boolean
  orientation?: 'vertical' | 'horizontal'
  name?: string
  onChange?: (value: string) => void
  children: React.ReactNode
  className?: string
}

export const RadioGroup = forwardRef<HTMLFieldSetElement, RadioGroupProps>(
  (
    {
      label,
      description,
      error,
      value,
      defaultValue,
      disabled,
      orientation = 'vertical',
      name: nameProp,
      onChange,
      children,
      className,
    },
    ref,
  ) => {
    const autoId = useId()
    const name = nameProp ?? autoId
    const descriptionId = `${name}-description`
    const errorId = `${name}-error`
    const hasError = !!error

    return (
      <RadioGroupContext.Provider value={{ name, value: value ?? defaultValue, disabled, hasError, onChange }}>
        <fieldset
          ref={ref}
          disabled={disabled}
          className={cn('flex flex-col gap-1.5', className)}
          aria-describedby={
            [description ? descriptionId : undefined, hasError ? errorId : undefined]
              .filter(Boolean)
              .join(' ') || undefined
          }
        >
          {(label || description) && (
            <div className="mb-1 flex flex-col gap-0.5">
              {label && (
                <legend
                  className={cn(
                    'text-small font-bold',
                    hasError ? 'text-control-error' : 'text-control-label',
                    disabled && 'opacity-50',
                  )}
                >
                  {label}
                </legend>
              )}
              {description && (
                <p
                  id={descriptionId}
                  className={cn('text-small text-text', disabled && 'opacity-50')}
                >
                  {description}
                </p>
              )}
            </div>
          )}
          <div
            className={cn(
              'flex gap-3',
              orientation === 'vertical' ? 'flex-col' : 'flex-row flex-wrap',
            )}
            role="radiogroup"
          >
            {children}
          </div>
          {hasError && (
            <div id={errorId} className="flex items-center gap-1 text-small text-control-error">
              <svg
                className="size-4 shrink-0"
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                strokeWidth={2}
                strokeLinecap="round"
                strokeLinejoin="round"
                aria-hidden="true"
              >
                <circle cx="12" cy="12" r="10" />
                <line x1="12" y1="8" x2="12" y2="12" />
                <line x1="12" y1="16" x2="12.01" y2="16" />
              </svg>
              <p>{error}</p>
            </div>
          )}
        </fieldset>
      </RadioGroupContext.Provider>
    )
  },
)

RadioGroup.displayName = 'RadioGroup'

export interface RadioProps
  extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'size'> {
  label?: string
  description?: string
  value: string
}

export const Radio = forwardRef<HTMLInputElement, RadioProps>(
  ({ label, description, value, disabled: disabledProp, className, id: idProp, ...props }, ref) => {
    const autoId = useId()
    const id = idProp ?? autoId
    const descriptionId = `${id}-description`
    const group = useContext(RadioGroupContext)
    const name = group?.name
    const isChecked = group?.value != null ? group.value === value : undefined
    const disabled = disabledProp ?? group?.disabled
    const hasError = group?.hasError

    const handleChange = () => {
      group?.onChange?.(value)
    }

    return (
      <div className={cn('flex gap-2', className)}>
        <div className="flex h-6 items-center">
          <input
            ref={ref}
            type="radio"
            id={id}
            name={name}
            value={value}
            checked={isChecked}
            disabled={disabled}
            onChange={handleChange}
            aria-describedby={description ? descriptionId : undefined}
            className={cn(
              'peer size-5 cursor-pointer appearance-none rounded-full border-2 border-control-border bg-control-bg transition-colors',
              'hover:border-control-border-hover',
              'checked:border-[6px] checked:border-control-checked',
              'checked:hover:border-control-checked-hover',
              'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-control-focus-ring focus-visible:ring-offset-1',
              'active:scale-95',
              'disabled:pointer-events-none disabled:opacity-50',
              hasError &&
                'border-control-error hover:border-control-error checked:border-control-error checked:hover:border-control-error focus-visible:ring-red-03',
            )}
            {...props}
          />
        </div>

        {(label || description) && (
          <div className="flex flex-col gap-0.5 pt-px">
            {label && (
              <label
                htmlFor={id}
                className={cn(
                  'cursor-pointer text-body font-normal text-grey-01',
                  disabled && 'pointer-events-none opacity-50',
                )}
              >
                {label}
              </label>
            )}
            {description && (
              <p
                id={descriptionId}
                className={cn('text-small text-text', disabled && 'opacity-50')}
              >
                {description}
              </p>
            )}
          </div>
        )}
      </div>
    )
  },
)

Radio.displayName = 'Radio'
