'use client';

import { createContext, useContext, useId } from 'react';
import type { InputHTMLAttributes, LabelHTMLAttributes, ReactNode, Ref, SelectHTMLAttributes, TextareaHTMLAttributes } from 'react';
import { cn } from '@/lib/cn';
import { AlertIcon, CheckIcon, ChevronDownIcon } from './icons';

/**
 * Form primitives. <Field> owns the label/hint/error wiring and hands ids to the control inside it
 * through context, so every control gets `id`, `aria-describedby` and `aria-invalid` for free.
 */
interface FieldContextValue {
  id: string;
  describedBy: string | undefined;
  invalid: boolean;
  required: boolean;
}

const FieldContext = createContext<FieldContextValue | null>(null);

export function Label({ className, ...rest }: LabelHTMLAttributes<HTMLLabelElement>) {
  return <label className={cn('text-sm font-semibold text-fg', className)} {...rest} />;
}

export function FieldError({ id, children, className }: { id?: string; children: ReactNode; className?: string }) {
  return (
    <p id={id} className={cn('flex items-start gap-1.5 text-sm font-medium text-danger-soft', className)}>
      <AlertIcon className="mt-0.5 size-4 shrink-0" />
      <span>{children}</span>
    </p>
  );
}

export interface FieldProps {
  label: ReactNode;
  hint?: ReactNode;
  error?: string | undefined;
  required?: boolean;
  /** Shows "(optional)" after the label. */
  optional?: boolean;
  id?: string;
  className?: string;
  children: ReactNode;
}

export function Field({ label, hint, error, required = false, optional, id: idProp, className, children }: FieldProps) {
  const autoId = useId();
  const id = idProp ?? `field${autoId.replace(/[^a-zA-Z0-9_-]/g, '')}`;
  const hintId = hint ? `${id}-hint` : undefined;
  const errorId = error ? `${id}-error` : undefined;
  const describedBy = [hintId, errorId].filter(Boolean).join(' ') || undefined;
  return (
    <div className={cn('flex flex-col gap-2', className)}>
      <Label htmlFor={id}>
        {label}
        {optional && <span className="font-normal text-subtle"> (optional)</span>}
        {required && (
          <span aria-hidden="true" className="text-danger-soft">
            {' '}
            *
          </span>
        )}
      </Label>
      <FieldContext value={{ id, describedBy, invalid: Boolean(error), required }}>{children}</FieldContext>
      {hint && (
        <p id={hintId} className="text-sm text-subtle">
          {hint}
        </p>
      )}
      {error && <FieldError id={errorId}>{error}</FieldError>}
    </div>
  );
}

/** Shared look for text-like controls. 16 px text stops iOS zooming on focus. */
export const controlStyles =
  'block w-full min-h-12 rounded-xl border border-line-strong bg-night/70 px-4 py-3 text-base text-fg ' +
  'placeholder:text-subtle transition-colors duration-150 hover:border-muted/70 ' +
  'focus-visible:border-cyan focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-cyan/60 ' +
  'aria-invalid:border-danger aria-invalid:bg-danger/[0.05] disabled:opacity-60';

function useFieldProps<T extends { id?: string; 'aria-describedby'?: string; 'aria-invalid'?: unknown; 'aria-required'?: unknown }>(props: T) {
  const field = useContext(FieldContext);
  return {
    id: props.id ?? field?.id,
    'aria-describedby': props['aria-describedby'] ?? field?.describedBy,
    'aria-invalid': (props['aria-invalid'] as boolean | undefined) ?? (field?.invalid ? true : undefined),
    'aria-required': (props['aria-required'] as boolean | undefined) ?? (field?.required ? true : undefined),
  };
}

export function Input({ className, ref, ...rest }: InputHTMLAttributes<HTMLInputElement> & { ref?: Ref<HTMLInputElement> }) {
  const a11y = useFieldProps(rest);
  return <input ref={ref} className={cn(controlStyles, className)} {...rest} {...a11y} />;
}

export function Textarea({
  className,
  ref,
  ...rest
}: TextareaHTMLAttributes<HTMLTextAreaElement> & { ref?: Ref<HTMLTextAreaElement> }) {
  const a11y = useFieldProps(rest);
  return <textarea ref={ref} className={cn(controlStyles, 'min-h-32 resize-y leading-relaxed', className)} {...rest} {...a11y} />;
}

export function Select({
  className,
  children,
  ref,
  ...rest
}: SelectHTMLAttributes<HTMLSelectElement> & { ref?: Ref<HTMLSelectElement> }) {
  const a11y = useFieldProps(rest);
  return (
    <div className="relative">
      <select ref={ref} className={cn(controlStyles, 'cursor-pointer appearance-none pr-11', className)} {...rest} {...a11y}>
        {children}
      </select>
      <ChevronDownIcon className="pointer-events-none absolute top-1/2 right-4 size-5 -translate-y-1/2 text-muted" />
    </div>
  );
}

/** Classic checkbox with a 48 px tall hit area. */
export function Checkbox({
  label,
  description,
  className,
  ...rest
}: Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> & { label: ReactNode; description?: ReactNode }) {
  return (
    <label className={cn('flex min-h-12 cursor-pointer items-start gap-3 py-2', className)}>
      <input type="checkbox" className="mt-0.5 size-5 shrink-0 cursor-pointer accent-violet" {...rest} />
      <span className="flex flex-col gap-0.5">
        <span className="text-base text-fg">{label}</span>
        {description && <span className="text-sm text-subtle">{description}</span>}
      </span>
    </label>
  );
}

/**
 * Toggle chip backed by a real checkbox (multi-select groups like "busiest nights").
 * The check icon means state is never conveyed by colour alone.
 */
export function ChipCheckbox({
  label,
  className,
  ...rest
}: Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> & { label: ReactNode }) {
  return (
    <label className={cn('relative inline-flex cursor-pointer', className)}>
      <input type="checkbox" className="peer sr-only" {...rest} />
      <span
        className={
          'inline-flex min-h-12 min-w-16 items-center justify-center gap-1.5 rounded-xl border border-line-strong bg-night/70 px-4 ' +
          'text-sm font-semibold text-muted transition-[background-color,border-color,color,box-shadow] duration-150 hover:border-violet-soft hover:text-fg ' +
          'peer-checked:border-violet peer-checked:bg-violet/25 peer-checked:text-fg peer-checked:shadow-glow-violet ' +
          'peer-focus-visible:outline-3 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-cyan ' +
          '[&>svg]:hidden peer-checked:[&>svg]:block'
        }
      >
        <CheckIcon className="size-4" />
        {label}
      </span>
    </label>
  );
}

/** <fieldset> with legend, hint and error, for groups of checkboxes/radios. */
export function ChoiceGroup({
  legend,
  hint,
  error,
  required,
  className,
  children,
}: {
  legend: ReactNode;
  hint?: ReactNode;
  error?: string | undefined;
  required?: boolean;
  className?: string;
  children: ReactNode;
}) {
  const id = `group${useId().replace(/[^a-zA-Z0-9_-]/g, '')}`;
  const hintId = hint ? `${id}-hint` : undefined;
  const errorId = error ? `${id}-error` : undefined;
  return (
    <fieldset
      className={cn('flex flex-col gap-3', className)}
      aria-describedby={[hintId, errorId].filter(Boolean).join(' ') || undefined}
    >
      <legend className="mb-1 text-sm font-semibold text-fg">
        {legend}
        {required && (
          <span aria-hidden="true" className="text-danger-soft">
            {' '}
            *
          </span>
        )}
      </legend>
      {hint && (
        <p id={hintId} className="-mt-2 text-sm text-subtle">
          {hint}
        </p>
      )}
      {children}
      {error && <FieldError id={errorId}>{error}</FieldError>}
    </fieldset>
  );
}
