import { forwardRef, useId, useCallback, useRef, useEffect, type TextareaHTMLAttributes } from 'react'
import { cn } from '@/lib/utils'

export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
  label: string
  description?: string
  hint?: string
  error?: string
  variant?: 'outlined' | 'stacked'
  resize?: 'vertical' | 'horizontal' | 'both' | 'none'
  autoResize?: boolean
}

export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
  (
    {
      label,
      description,
      hint,
      error,
      variant = 'outlined',
      resize = 'vertical',
      autoResize = false,
      disabled,
      readOnly,
      maxLength,
      value,
      defaultValue,
      rows = 3,
      className,
      id: idProp,
      onChange,
      ...props
    },
    ref,
  ) => {
    const autoId = useId()
    const id = idProp ?? autoId
    const descriptionId = `${id}-description`
    const hintId = `${id}-hint`
    const hasError = !!error
    const supportiveText = error || hint
    const isStacked = variant === 'stacked'

    const internalRef = useRef<HTMLTextAreaElement | null>(null)

    const currentLength =
      maxLength != null && typeof value === 'string' ? value.length : undefined

    const describedBy =
      [
        description && isStacked ? descriptionId : undefined,
        supportiveText ? hintId : undefined,
      ]
        .filter(Boolean)
        .join(' ') || undefined

    const resizeClass: Record<string, string> = {
      vertical: 'resize-y',
      horizontal: 'resize-x',
      both: 'resize',
      none: 'resize-none',
    }

    const adjustHeight = useCallback(() => {
      const el = internalRef.current
      if (!el || !autoResize) return
      el.style.height = 'auto'
      el.style.height = `${el.scrollHeight}px`
    }, [autoResize])

    useEffect(() => {
      adjustHeight()
    }, [adjustHeight, value])

    const setRefs = useCallback(
      (node: HTMLTextAreaElement | null) => {
        internalRef.current = node
        if (typeof ref === 'function') ref(node)
        else if (ref) ref.current = node
      },
      [ref],
    )

    return (
      <div className={cn('flex w-full flex-col', isStacked ? 'gap-1.5' : 'gap-1 pt-2', className)}>
        {isStacked && (
          <div className="flex flex-col gap-0.5">
            <label
              htmlFor={id}
              className={cn(
                'text-small font-bold',
                hasError ? 'text-control-error' : 'text-control-label',
                disabled && 'text-control-description',
              )}
            >
              {label}
            </label>
            {description && (
              <p
                id={descriptionId}
                className={cn('text-small text-text', disabled && 'opacity-50')}
              >
                {description}
              </p>
            )}
          </div>
        )}

        <div
          className={cn(
            'relative flex rounded-[4px] border bg-control-bg transition-colors',
            hasError
              ? 'border-control-error focus-within:ring-1 focus-within:ring-control-error'
              : 'border-control-border hover:border-control-border-hover focus-within:border-control-checked focus-within:ring-1 focus-within:ring-control-checked',
            disabled && 'pointer-events-none border-control-border/50 bg-control-bg/50',
            readOnly && 'border-transparent bg-control-bg-readonly',
          )}
        >
          {!isStacked && (
            <label
              htmlFor={id}
              className={cn(
                'absolute left-2 top-0 z-10 -translate-y-1/2 bg-control-bg px-1 text-small font-bold leading-none',
                hasError ? 'text-control-error' : 'text-control-label',
                disabled && 'text-control-description',
              )}
            >
              {label}
            </label>
          )}

          <textarea
            ref={setRefs}
            id={id}
            disabled={disabled}
            readOnly={readOnly}
            maxLength={maxLength}
            value={value}
            defaultValue={defaultValue}
            rows={rows}
            aria-invalid={hasError || undefined}
            aria-describedby={describedBy}
            onChange={(e) => {
              onChange?.(e)
              adjustHeight()
            }}
            className={cn(
              'w-full bg-transparent px-3 py-3 text-body font-normal text-text outline-none',
              'placeholder:text-text/50',
              autoResize ? 'resize-none overflow-hidden' : resizeClass[resize],
              disabled && 'text-text/50',
            )}
            {...props}
          />
        </div>

        {(supportiveText || currentLength != null) && (
          <div
            id={hintId}
            className={cn(
              'flex items-center gap-1 text-small',
              hasError ? 'text-control-error' : 'text-control-description',
              disabled && 'opacity-50',
            )}
          >
            {hasError && (
              <svg
                className="size-4 shrink-0"
                xmlns="http://www.w3.org/2000/svg"
                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>
            )}
            {supportiveText && <p className="flex-1">{supportiveText}</p>}
            {currentLength != null && (
              <p className="shrink-0 text-right">
                {currentLength}/{maxLength}
              </p>
            )}
          </div>
        )}
      </div>
    )
  },
)

Textarea.displayName = 'Textarea'
