'use client';
import { useState, useEffect, forwardRef, InputHTMLAttributes, SelectHTMLAttributes, TextareaHTMLAttributes, ReactNode } from 'react';
import { capitalizeFirst } from '@/lib/format';

type Common = { label: string; id: string; error?: string; hint?: string };

/** Input types that must never be re-cased (credentials, contacts, links, numbers, dates, search). */
const NO_CAP_TYPES = ['email', 'password', 'url', 'tel', 'number', 'date', 'datetime-local', 'month', 'time', 'week', 'search'];

function Wrap({ id, label, required, error, hint, floated, children }: {
  id: string; label: string; required?: boolean; error?: string; hint?: string; floated: boolean; children: ReactNode;
}) {
  return (
    <div className={`ff${floated ? ' floated' : ''}`}>
      {children}
      <label htmlFor={id}>{label}{required && <span aria-hidden="true"> *</span>}</label>
      {hint && !error && <p className="hint mt-1">{hint}</p>}
      {error && <p role="alert" className="field-error">{error}</p>}
    </div>
  );
}

function useFilled(value: unknown, defaultValue: unknown) {
  const [inner, setInner] = useState(() => String(value ?? defaultValue ?? '') !== '');
  const controlled = value !== undefined;
  // Async-loaded defaultValues (e.g. settings fetched after mount) must re-float the label.
  useEffect(() => {
    if (!controlled) setInner(String(defaultValue ?? '') !== '');
  }, [defaultValue]);
  return {
    filled: controlled ? String(value ?? '') !== '' : inner,
    sync: (v: string) => { if (!controlled) setInner(v !== ''); },
  };
}

/** Input types whose browser chrome always shows content (date text or picker UI). */
const ALWAYS_FLOAT_TYPES = ['date', 'datetime-local', 'month', 'time', 'week'];

export const FloatInput = forwardRef<HTMLInputElement, Common & InputHTMLAttributes<HTMLInputElement> & { capitalize?: boolean }>(
  function FloatInput({ label, id, error, hint, capitalize, ...props }, ref) {
    const { filled, sync } = useFilled(props.value, props.defaultValue);
    const { onChange, onBlur, className, required, placeholder: _ph, type, ...rest } = props;
    const cap = capitalize ?? !NO_CAP_TYPES.includes(type ?? 'text');
    const floated = filled || ALWAYS_FLOAT_TYPES.includes(type ?? 'text');
    return (
      <Wrap id={id} label={label} required={required} error={error} hint={hint} floated={floated}>
        <input
          id={id} placeholder=" " aria-label={label} aria-invalid={!!error}
          className={`input${className ? ` ${className}` : ''}`}
          {...rest} type={type} required={required} ref={ref}
          onChange={(e) => {
            if (cap && e.target.value) {
              const capped = capitalizeFirst(e.target.value);
              if (capped !== e.target.value) e.target.value = capped;
            }
            sync(e.target.value); onChange?.(e);
          }}
          onBlur={(e) => { sync(e.target.value); onBlur?.(e); }}
        />
      </Wrap>
    );
  }
);

export const FloatSelect = forwardRef<HTMLSelectElement, Common & SelectHTMLAttributes<HTMLSelectElement>>(
  function FloatSelect({ label, id, error, hint, children, ...props }, ref) {
    // Selects always render a visible option, so the label must always float.
    const { sync } = useFilled(props.value, props.defaultValue);
    const { onChange, onBlur, className, required, ...rest } = props;
    return (
      <Wrap id={id} label={label} required={required} error={error} hint={hint} floated>
        <select
          id={id} aria-label={label} aria-invalid={!!error}
          className={`input${className ? ` ${className}` : ''}`}
          {...rest} required={required} ref={ref}
          onChange={(e) => { sync(e.target.value); onChange?.(e); }}
          onBlur={(e) => { sync(e.target.value); onBlur?.(e); }}
        >
          {children}
        </select>
      </Wrap>
    );
  }
);

export const FloatArea = forwardRef<HTMLTextAreaElement, Common & TextareaHTMLAttributes<HTMLTextAreaElement> & { capitalize?: boolean }>(
  function FloatArea({ label, id, error, hint, capitalize, ...props }, ref) {
    const { filled, sync } = useFilled(props.value, props.defaultValue);
    const { onChange, onBlur, className, required, placeholder: _ph, ...rest } = props;
    const cap = capitalize ?? true;
    return (
      <Wrap id={id} label={label} required={required} error={error} hint={hint} floated={filled}>
        <textarea
          id={id} placeholder=" " aria-label={label} aria-invalid={!!error}
          className={`input${className ? ` ${className}` : ''}`}
          {...rest} required={required} ref={ref}
          onChange={(e) => {
            if (cap && e.target.value) {
              const capped = capitalizeFirst(e.target.value);
              if (capped !== e.target.value) e.target.value = capped;
            }
            sync(e.target.value); onChange?.(e);
          }}
          onBlur={(e) => { sync(e.target.value); onBlur?.(e); }}
        />
      </Wrap>
    );
  }
);
