"use client"

import * as React from "react"
import {
  Check,
  Copy,
  Eye,
  EyeOff,
  Loader2,
  X,
} from "lucide-react"

import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export interface EnhancedInputProps
  extends Omit<
    React.InputHTMLAttributes<HTMLInputElement>,
    "size" | "prefix"
  > {
  value?: string
  defaultValue?: string
  onValueChange?: (value: string) => void

  label?: React.ReactNode
  description?: React.ReactNode
  helperText?: React.ReactNode
  error?: React.ReactNode
  success?: React.ReactNode

  clearable?: boolean
  onClear?: () => void

  prefix?: React.ReactNode
  suffix?: React.ReactNode
  leftIcon?: React.ReactNode
  rightIcon?: React.ReactNode

  loading?: boolean

  passwordToggle?: boolean
  copyable?: boolean

  showCount?: boolean
  maxLength?: number

  inputSize?: "sm" | "md" | "lg"
  status?: "default" | "error" | "success" | "warning"

  debounce?: number
  trimOnBlur?: boolean

  allowEnterClear?: boolean
  onEnter?: (value: string) => void
  onEscape?: () => void

  containerClassName?: string
  labelClassName?: string
  inputWrapperClassName?: string
  inputClassName?: string
}

const sizeClasses: Record<NonNullable<EnhancedInputProps["inputSize"]>, string> =
  {
    sm: "h-8 text-sm",
    md: "h-10 text-sm",
    lg: "h-12 text-base",
  }

const statusClasses: Record<
  NonNullable<EnhancedInputProps["status"]>,
  string
> = {
  default: "border-input focus-visible:ring-ring",
  error:
    "border-destructive focus-visible:ring-destructive aria-invalid:border-destructive",
  success: "border-success focus-visible:ring-success",
  warning: "border-warning focus-visible:ring-warning",
}

const rightPaddingBySlots: Record<number, string> = {
  1: "pr-9",
  2: "pr-14",
  3: "pr-20",
  4: "pr-24",
  5: "pr-28",
  6: "pr-32",
}

function mergeRefs<T>(
  ...refs: Array<React.Ref<T> | undefined>
): React.RefCallback<T> {
  return (node) => {
    refs.forEach((ref) => {
      if (typeof ref === "function") {
        ref(node)
      } else if (ref) {
        ;(ref as React.MutableRefObject<T | null>).current = node
      }
    })
  }
}

function getLeftPadding(hasLeftIcon: boolean, hasPrefix: boolean): string {
  if (hasLeftIcon && hasPrefix) return "pl-[4.5rem]"
  if (hasLeftIcon || hasPrefix) return "pl-10"
  return ""
}

function getRightPadding(slotCount: number): string {
  if (slotCount <= 0) return ""
  return rightPaddingBySlots[Math.min(slotCount, 6)] ?? "pr-32"
}

const EnhancedInput = React.forwardRef<HTMLInputElement, EnhancedInputProps>(
  (
    {
      value,
      defaultValue,
      onValueChange,
      label,
      description,
      helperText,
      error,
      success,
      clearable = false,
      onClear,
      prefix,
      suffix,
      leftIcon,
      rightIcon,
      loading = false,
      passwordToggle = false,
      copyable = false,
      showCount = false,
      maxLength,
      inputSize = "md",
      status = "default",
      debounce,
      trimOnBlur = false,
      allowEnterClear = false,
      onEnter,
      onEscape,
      containerClassName,
      labelClassName,
      inputWrapperClassName,
      inputClassName,
      className,
      id,
      type = "text",
      disabled,
      readOnly,
      onChange,
      onBlur,
      onFocus,
      onKeyDown,
      ...inputProps
    },
    ref
  ) => {
    const generatedId = React.useId()
    const inputId = id ?? generatedId
    const helperId = `${inputId}-helper`
    const errorId = `${inputId}-error`
    const successId = `${inputId}-success`

    const isControlled = value !== undefined
    const [internalValue, setInternalValue] = React.useState(
      defaultValue ?? ""
    )
    const currentValue = isControlled ? (value ?? "") : internalValue

    const inputRef = React.useRef<HTMLInputElement>(null)
    const debounceTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(
      null
    )
    const onValueChangeRef = React.useRef(onValueChange)
    const isComposingRef = React.useRef(false)

    const [showPassword, setShowPassword] = React.useState(false)
    const [copied, setCopied] = React.useState(false)

    onValueChangeRef.current = onValueChange

    React.useEffect(() => {
      return () => {
        if (debounceTimerRef.current) {
          clearTimeout(debounceTimerRef.current)
        }
      }
    }, [])

    const triggerValueChange = React.useCallback(
      (nextValue: string, immediate = false) => {
        if (!onValueChangeRef.current) return

        if (debounce && !immediate) {
          if (debounceTimerRef.current) {
            clearTimeout(debounceTimerRef.current)
          }
          debounceTimerRef.current = setTimeout(() => {
            onValueChangeRef.current?.(nextValue)
          }, debounce)
          return
        }

        onValueChangeRef.current(nextValue)
      },
      [debounce]
    )

    const setValue = React.useCallback(
      (nextValue: string, immediate = false) => {
        if (!isControlled) {
          setInternalValue(nextValue)
        }
        if (inputRef.current && inputRef.current.value !== nextValue) {
          inputRef.current.value = nextValue
        }
        triggerValueChange(nextValue, immediate)
      },
      [isControlled, triggerValueChange]
    )

    const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
      const nextValue = event.target.value

      if (!isControlled) {
        setInternalValue(nextValue)
      }

      onChange?.(event)

      if (!isComposingRef.current) {
        triggerValueChange(nextValue)
      }
    }

    const handleCompositionStart = () => {
      isComposingRef.current = true
    }

    const handleCompositionEnd = (
      event: React.CompositionEvent<HTMLInputElement>
    ) => {
      isComposingRef.current = false
      const nextValue = event.currentTarget.value

      if (!isControlled) {
        setInternalValue(nextValue)
      }

      triggerValueChange(nextValue)
    }

    const handleClear = () => {
      if (disabled || readOnly) return

      setValue("", true)
      onClear?.()
      inputRef.current?.focus()
    }

    const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {
      if (trimOnBlur) {
        const trimmedValue = event.target.value.trim()
        if (trimmedValue !== event.target.value) {
          setValue(trimmedValue, true)
        }
      }
      onBlur?.(event)
    }

    const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
      if (event.key === "Enter") {
        onEnter?.(currentValue)
      }

      if (event.key === "Escape") {
        if (allowEnterClear) {
          handleClear()
        }
        onEscape?.()
      }

      onKeyDown?.(event)
    }

    const handleCopy = async () => {
      if (!currentValue || disabled) return

      try {
        await navigator.clipboard.writeText(currentValue)
        setCopied(true)
        window.setTimeout(() => setCopied(false), 1000)
      } catch {
        // Clipboard API may be unavailable in some environments.
      }
    }

    const showClearButton =
      clearable && !!currentValue && !disabled && !readOnly

    const showPasswordButton =
      passwordToggle && type === "password" && !disabled

    const showCopyButton = copyable

    const rightSlotCount = [
      !!suffix,
      !!rightIcon,
      loading,
      showClearButton,
      showPasswordButton,
      showCopyButton,
    ].filter(Boolean).length

    const visualStatus: NonNullable<EnhancedInputProps["status"]> = error
      ? "error"
      : success
        ? "success"
        : status

    const describedByIds = [
      error ? errorId : null,
      !error && success ? successId : null,
      !error && !success && helperText ? helperId : null,
    ]
      .filter(Boolean)
      .join(" ")

    const resolvedType =
      passwordToggle && type === "password"
        ? showPassword
          ? "text"
          : "password"
        : type

    const iconButtonClass = cn(
      "shrink-0 text-muted-foreground hover:text-foreground",
      inputSize === "sm" && "h-6 w-6",
      inputSize === "md" && "h-7 w-7",
      inputSize === "lg" && "h-8 w-8"
    )

    const showFooter = !!(error || success || helperText || showCount)

    return (
      <div className={cn("w-full space-y-2", containerClassName)}>
        {label ? (
          <Label htmlFor={inputId} className={cn(labelClassName)}>
            {label}
            {inputProps.required ? (
              <span className="ml-0.5 text-destructive" aria-hidden="true">
                *
              </span>
            ) : null}
          </Label>
        ) : null}

        {description ? (
          <p className="text-sm text-muted-foreground">{description}</p>
        ) : null}

        <div
          className={cn("relative flex w-full items-center", inputWrapperClassName)}
        >
          {(leftIcon || prefix) && (
            <div
              className={cn(
                "pointer-events-none absolute left-3 z-10 flex items-center gap-1.5 text-muted-foreground",
                inputSize === "sm" && "left-2.5",
                inputSize === "lg" && "left-3.5"
              )}
            >
              {leftIcon ? (
                <span className="flex shrink-0 items-center [&_svg]:size-4">
                  {leftIcon}
                </span>
              ) : null}
              {prefix ? (
                <span
                  className={cn(
                    "shrink-0 text-sm",
                    inputSize === "lg" && "text-base"
                  )}
                >
                  {prefix}
                </span>
              ) : null}
            </div>
          )}

          <Input
            {...inputProps}
            id={inputId}
            ref={mergeRefs(ref, inputRef)}
            type={resolvedType}
            disabled={disabled}
            readOnly={readOnly}
            maxLength={maxLength}
            value={currentValue}
            aria-invalid={!!error || visualStatus === "error" || undefined}
            aria-describedby={describedByIds || undefined}
            className={cn(
              sizeClasses[inputSize],
              statusClasses[visualStatus],
              getLeftPadding(!!leftIcon, !!prefix),
              getRightPadding(rightSlotCount),
              type === "search" &&
                "[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none",
              inputClassName,
              className
            )}
            onChange={handleChange}
            onBlur={handleBlur}
            onFocus={onFocus}
            onKeyDown={handleKeyDown}
            onCompositionStart={handleCompositionStart}
            onCompositionEnd={handleCompositionEnd}
          />

          {rightSlotCount > 0 && (
            <div
              className={cn(
                "absolute right-1.5 z-10 flex items-center gap-0.5",
                inputSize === "sm" && "right-1",
                inputSize === "lg" && "right-2"
              )}
            >
              {suffix ? (
                <span
                  className={cn(
                    "px-1 text-sm text-muted-foreground",
                    inputSize === "lg" && "text-base"
                  )}
                >
                  {suffix}
                </span>
              ) : null}

              {rightIcon ? (
                <span className="flex shrink-0 items-center px-0.5 text-muted-foreground [&_svg]:size-4">
                  {rightIcon}
                </span>
              ) : null}

              {loading ? (
                <span
                  className="flex shrink-0 items-center px-0.5 text-muted-foreground"
                  aria-hidden="true"
                >
                  <Loader2
                    className={cn(
                      "animate-spin",
                      inputSize === "sm" ? "size-3.5" : "size-4"
                    )}
                  />
                </span>
              ) : null}

              {showClearButton ? (
                <Button
                  type="button"
                  variant="ghost"
                  size="icon"
                  className={iconButtonClass}
                  onClick={handleClear}
                  aria-label="清空输入"
                  tabIndex={-1}
                >
                  <X className={inputSize === "sm" ? "size-3.5" : "size-4"} />
                </Button>
              ) : null}

              {showPasswordButton ? (
                <Button
                  type="button"
                  variant="ghost"
                  size="icon"
                  className={iconButtonClass}
                  onClick={() => setShowPassword((prev) => !prev)}
                  aria-label={showPassword ? "隐藏密码" : "显示密码"}
                  tabIndex={-1}
                >
                  {showPassword ? (
                    <EyeOff
                      className={inputSize === "sm" ? "size-3.5" : "size-4"}
                    />
                  ) : (
                    <Eye
                      className={inputSize === "sm" ? "size-3.5" : "size-4"}
                    />
                  )}
                </Button>
              ) : null}

              {showCopyButton ? (
                <Button
                  type="button"
                  variant="ghost"
                  size="icon"
                  className={iconButtonClass}
                  onClick={handleCopy}
                  disabled={!currentValue || disabled}
                  aria-label="复制内容"
                  tabIndex={-1}
                >
                  {copied ? (
                    <Check
                      className={cn(
                        "text-success",
                        inputSize === "sm" ? "size-3.5" : "size-4"
                      )}
                    />
                  ) : (
                    <Copy
                      className={inputSize === "sm" ? "size-3.5" : "size-4"}
                    />
                  )}
                </Button>
              ) : null}
            </div>
          )}
        </div>

        {showFooter ? (
          <div className="flex items-start justify-between gap-2">
            <div className="min-h-0 flex-1 space-y-1">
              {error ? (
                <p
                  id={errorId}
                  className="text-sm text-destructive"
                  role="alert"
                >
                  {error}
                </p>
              ) : null}

              {!error && success ? (
                <p id={successId} className="text-sm text-success">
                  {success}
                </p>
              ) : null}

              {!error && !success && helperText ? (
                <p id={helperId} className="text-sm text-muted-foreground">
                  {helperText}
                </p>
              ) : null}
            </div>

            {showCount ? (
              <p
                className="shrink-0 text-xs tabular-nums text-muted-foreground"
                aria-live="polite"
              >
                {currentValue.length}
                {maxLength !== undefined ? ` / ${maxLength}` : ""}
              </p>
            ) : null}
          </div>
        ) : null}
      </div>
    )
  }
)

EnhancedInput.displayName = "EnhancedInput"

export { EnhancedInput }

/*
Usage examples:

import { Search } from "lucide-react"
import { EnhancedInput } from "@/components/common/shadcn-enhanced/enhanced-input"

// 1. 普通输入框
<EnhancedInput placeholder="请输入名称" />

// 2. 可清空输入框
<EnhancedInput clearable placeholder="请输入关键词" />

// 3. 搜索输入框
<EnhancedInput
  leftIcon={<Search className="h-4 w-4" />}
  clearable
  placeholder="搜索"
/>

// 4. 密码输入框
<EnhancedInput
  type="password"
  passwordToggle
  placeholder="请输入密码"
/>

// 5. 带单位输入框
<EnhancedInput
  suffix="kg"
  placeholder="请输入重量"
/>

// 6. 带错误提示
<EnhancedInput
  label="用户名"
  error="用户名不能为空"
  required
/>

// 7. 字数统计
<EnhancedInput
  showCount
  maxLength={50}
  placeholder="请输入备注"
/>

// 8. 防抖搜索
<EnhancedInput
  debounce={500}
  onValueChange={(value) => console.log(value)}
  placeholder="请输入搜索关键词"
/>
*/
