'use client';

import React, { forwardRef } from 'react';
import classNames from 'classnames';
import dynamic from 'next/dynamic';
import { CircleHelp } from 'lucide-react';
import { getDoc } from '@/docs';
import { openDoc } from '@/components/DocModal';
import { ConfigDoc, GroupedSelectOption, SelectOption } from '@/types';

const Select = dynamic(() => import('react-select'), { ssr: false });

const labelClasses = 'block text-xs mb-1 mt-2 text-gray-300';
const inputClasses =
  'w-full text-sm px-3 py-1 bg-gray-950 dark:bg-gray-800 border border-gray-700 rounded-sm text-gray-100 placeholder:text-gray-500 focus:ring-2 focus:ring-gray-600 focus:border-transparent';

export interface InputProps {
  label?: string;
  docKey?: string | null;
  doc?: ConfigDoc | null;
  className?: string;
  placeholder?: string;
  required?: boolean;
}

export interface TextInputProps extends InputProps {
  value: string;
  onChange: (value: string) => void;
  type?: 'text' | 'password';
  disabled?: boolean;
}

export const TextInput = forwardRef<HTMLInputElement, TextInputProps>((props: TextInputProps, ref) => {
  const { label, value, onChange, placeholder, required, disabled, type = 'text', className, docKey = null } = props;
  let { doc } = props;
  if (!doc && docKey) {
    doc = getDoc(docKey);
  }
  return (
    <div className={classNames(className)}>
      {label && (
        <label className={labelClasses}>
          {label}{' '}
          {doc && (
            <div className="inline-block ml-1 text-xs text-gray-500 cursor-pointer" onClick={() => openDoc(doc)}>
              <CircleHelp className="inline-block w-4 h-4 cursor-pointer" />
            </div>
          )}
        </label>
      )}
      <input
        ref={ref}
        type={type}
        value={value}
        onChange={e => {
          if (!disabled) onChange(e.target.value);
        }}
        className={`${inputClasses} ${disabled ? 'opacity-30 cursor-not-allowed' : ''}`}
        placeholder={placeholder}
        required={required}
        disabled={disabled}
      />
    </div>
  );
});

// 👇 Helpful for debugging
TextInput.displayName = 'TextInput';

export interface TextAreaInputProps extends InputProps {
  value: string;
  onChange: (value: string) => void;
  disabled?: boolean;
  rows?: number;
}

export const TextAreaInput = forwardRef<HTMLTextAreaElement, TextAreaInputProps>((props: TextAreaInputProps, ref) => {
  const { label, value, onChange, placeholder, required, disabled, rows = 4, className, docKey = null } = props;
  let { doc } = props;
  if (!doc && docKey) {
    doc = getDoc(docKey);
  }
  return (
    <div className={classNames(className)}>
      {label && (
        <label className={labelClasses}>
          {label}{' '}
          {doc && (
            <div className="inline-block ml-1 text-xs text-gray-500 cursor-pointer" onClick={() => openDoc(doc)}>
              <CircleHelp className="inline-block w-4 h-4 cursor-pointer" />
            </div>
          )}
        </label>
      )}
      <textarea
        ref={ref}
        value={value}
        onChange={e => {
          if (!disabled) onChange(e.target.value);
        }}
        className={`${inputClasses} ${disabled ? 'opacity-30 cursor-not-allowed' : ''}`}
        placeholder={placeholder}
        required={required}
        disabled={disabled}
        rows={rows}
      />
    </div>
  );
});

TextAreaInput.displayName = 'TextAreaInput';

export interface NumberInputProps extends InputProps {
  value: number | null;
  onChange: (value: number | null) => void;
  min?: number;
  max?: number;
}

export const NumberInput = (props: NumberInputProps) => {
  const { label, value, onChange, placeholder, required, min, max, docKey = null } = props;
  let { doc } = props;
  if (!doc && docKey) {
    doc = getDoc(docKey);
  }

  // Add controlled internal state to properly handle partial inputs
  const [inputValue, setInputValue] = React.useState<string | number>(value ?? '');

  // Sync internal state with prop value
  React.useEffect(() => {
    setInputValue(value ?? '');
  }, [value]);

  return (
    <div className={classNames(props.className)}>
      {label && (
        <label className={labelClasses}>
          {label}{' '}
          {doc && (
            <div className="inline-block ml-1 text-xs text-gray-500 cursor-pointer" onClick={() => openDoc(doc)}>
              <CircleHelp className="inline-block w-4 h-4 cursor-pointer" />
            </div>
          )}
        </label>
      )}
      <input
        type="number"
        value={inputValue}
        onChange={e => {
          const rawValue = e.target.value;

          // Update the input display with the raw value
          setInputValue(rawValue);

          // Handle empty or partial inputs
          if (rawValue === '' || rawValue === '-') {
            // For empty or partial negative input, don't call onChange yet
            return;
          }

          const numValue = Number(rawValue);

          // Only apply constraints and call onChange when we have a valid number
          if (!isNaN(numValue)) {
            let constrainedValue = numValue;

            // Apply min/max constraints if they exist
            if (min !== undefined && constrainedValue < min) {
              constrainedValue = min;
            }
            if (max !== undefined && constrainedValue > max) {
              constrainedValue = max;
            }

            onChange(constrainedValue);
          }
        }}
        className={inputClasses}
        placeholder={placeholder}
        required={required}
        min={min}
        max={max}
        step="any"
      />
    </div>
  );
};

export interface SelectInputProps extends InputProps {
  value: string;
  disabled?: boolean;
  onChange: (value: string) => void;
  options: GroupedSelectOption[] | SelectOption[];
}

export const SelectInput = (props: SelectInputProps) => {
  const { label, value, onChange, options, docKey = null } = props;
  let { doc } = props;
  if (!doc && docKey) {
    doc = getDoc(docKey);
  }
  let selectedOption: SelectOption | undefined;
  if (options && options.length > 0) {
    // see if grouped options
    if ('options' in options[0]) {
      selectedOption = (options as GroupedSelectOption[])
        .flatMap(group => group.options)
        .find(opt => opt.value === value);
    } else {
      selectedOption = (options as SelectOption[]).find(opt => opt.value === value);
    }
  }
  return (
    <div
      className={classNames(props.className, {
        'opacity-30 cursor-not-allowed': props.disabled,
      })}
    >
      {label && (
        <label className={labelClasses}>
          {label}{' '}
          {doc && (
            <div className="inline-block ml-1 text-xs text-gray-500 cursor-pointer" onClick={() => openDoc(doc)}>
              <CircleHelp className="inline-block w-4 h-4 cursor-pointer" />
            </div>
          )}
        </label>
      )}
      <Select
        value={selectedOption}
        options={options}
        isDisabled={props.disabled}
        className="aitk-react-select-container"
        classNamePrefix="aitk-react-select"
        onChange={selected => {
          if (selected) {
            onChange((selected as { value: string }).value);
          }
        }}
      />
    </div>
  );
};

export interface CreatableSelectInputProps extends InputProps {
  value: string;
  disabled?: boolean;
  onChange: (value: string) => void;
  options: GroupedSelectOption[] | SelectOption[];
}

const CUSTOM_SELECT_VALUE = '__custom__';

export const CreatableSelectInput = (props: CreatableSelectInputProps) => {
  const { label, value, onChange, options, docKey = null } = props;
  let { doc } = props;
  if (!doc && docKey) {
    doc = getDoc(docKey);
  }

  // Check if current value matches any predefined option
  let isInOptions = false;
  if (options && options.length > 0) {
    if ('options' in options[0]) {
      isInOptions = (options as GroupedSelectOption[]).flatMap(g => g.options).some(opt => opt.value === value);
    } else {
      isInOptions = (options as SelectOption[]).some(opt => opt.value === value);
    }
  }

  const [isCustom, setIsCustom] = React.useState(!isInOptions && !!value);

  // Build select options with "Custom" at the top
  const customOption: SelectOption = { value: CUSTOM_SELECT_VALUE, label: 'Custom' };
  const selectOptions = React.useMemo(() => {
    if (options && options.length > 0 && 'options' in options[0]) {
      return [{ label: '', options: [customOption] }, ...(options as GroupedSelectOption[])];
    }
    return [customOption, ...(options as SelectOption[])];
  }, [options]);

  const selectedOption = isCustom
    ? customOption
    : (() => {
        if (!options || options.length === 0) return undefined;
        if ('options' in options[0]) {
          return (options as GroupedSelectOption[]).flatMap(g => g.options).find(opt => opt.value === value);
        }
        return (options as SelectOption[]).find(opt => opt.value === value);
      })();

  return (
    <div
      className={classNames(props.className, {
        'opacity-30 cursor-not-allowed': props.disabled,
      })}
    >
      {label && (
        <label className={labelClasses}>
          {label}{' '}
          {doc && (
            <div className="inline-block ml-1 text-xs text-gray-500 cursor-pointer" onClick={() => openDoc(doc)}>
              <CircleHelp className="inline-block w-4 h-4 cursor-pointer" />
            </div>
          )}
        </label>
      )}
      <div className="flex gap-2">
        <div className={isCustom ? 'w-1/3' : 'w-full'}>
          <Select
            value={selectedOption}
            options={selectOptions}
            isDisabled={props.disabled}
            className="aitk-react-select-container"
            classNamePrefix="aitk-react-select"
            formatOptionLabel={(option: unknown) => {
              const opt = option as SelectOption;
              return opt.value === CUSTOM_SELECT_VALUE ? (
                <span className="opacity-50 italic">~ Custom ~</span>
              ) : (
                opt.label
              );
            }}
            onChange={selected => {
              if (selected) {
                const val = (selected as { value: string }).value;
                if (val === CUSTOM_SELECT_VALUE) {
                  setIsCustom(true);
                  onChange('');
                } else {
                  setIsCustom(false);
                  onChange(val);
                }
              }
            }}
          />
        </div>
        {isCustom && (
          <input
            type="text"
            value={value}
            onChange={e => onChange(e.target.value)}
            className={`${inputClasses} w-2/3`}
            placeholder={props.placeholder ?? 'Enter custom value'}
            disabled={props.disabled}
            autoFocus
          />
        )}
      </div>
    </div>
  );
};

export interface CheckboxProps {
  label?: string | React.ReactNode;
  checked: boolean;
  onChange: (checked: boolean) => void;
  className?: string;
  required?: boolean;
  disabled?: boolean;
  docKey?: string | null;
  doc?: ConfigDoc | null;
}

export const Checkbox = (props: CheckboxProps) => {
  const { label, checked, onChange, required, disabled } = props;
  let { doc } = props;
  if (!doc && props.docKey) {
    doc = getDoc(props.docKey);
  }

  const id = React.useId();

  return (
    <div className={classNames('flex items-center gap-3', props.className)}>
      <button
        type="button"
        role="switch"
        id={id}
        aria-checked={checked}
        aria-required={required}
        disabled={disabled}
        onClick={() => !disabled && onChange(!checked)}
        className={classNames(
          'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-offset-2',
          checked ? 'bg-blue-500' : 'bg-gray-600',
          disabled ? 'opacity-50 cursor-not-allowed' : 'hover:bg-opacity-80',
        )}
      >
        <span className="sr-only">Toggle {label}</span>
        <span
          className={classNames(
            'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
            checked ? 'translate-x-5' : 'translate-x-0',
          )}
        />
      </button>
      {label && (
        <>
          <label
            htmlFor={id}
            className={classNames(
              'text-sm font-medium cursor-pointer select-none',
              disabled ? 'text-gray-500' : 'text-gray-300',
            )}
          >
            {label}
          </label>
          {doc && (
            <div className="inline-block ml-1 text-xs text-gray-500 cursor-pointer" onClick={() => openDoc(doc)}>
              <CircleHelp className="inline-block w-4 h-4 cursor-pointer" />
            </div>
          )}
        </>
      )}
    </div>
  );
};

interface FormGroupProps {
  label?: string;
  className?: string;
  docKey?: string | null;
  doc?: ConfigDoc | null;
  children: React.ReactNode;
}

export const FormGroup: React.FC<FormGroupProps> = props => {
  const { label, className, children, docKey = null } = props;
  let { doc } = props;
  if (!doc && docKey) {
    doc = getDoc(docKey);
  }
  return (
    <div className={classNames(className)}>
      {label && (
        <label className={classNames(labelClasses, 'mb-2')}>
          {label}{' '}
          {doc && (
            <div className="inline-block ml-1 text-xs text-gray-500 cursor-pointer" onClick={() => openDoc(doc)}>
              <CircleHelp className="inline-block w-4 h-4 cursor-pointer" />
            </div>
          )}
        </label>
      )}
      <div className="space-y-2">{children}</div>
    </div>
  );
};

export interface SliderInputProps extends InputProps {
  value: number;
  onChange: (value: number) => void;
  min: number;
  max: number;
  step?: number;
  disabled?: boolean;
  showValue?: boolean;
}

export const SliderInput: React.FC<SliderInputProps> = props => {
  const { label, value, onChange, min, max, step = 1, disabled, className, docKey = null, showValue = true } = props;
  let { doc } = props;
  if (!doc && docKey) {
    doc = getDoc(docKey);
  }

  const trackRef = React.useRef<HTMLDivElement | null>(null);
  const [dragging, setDragging] = React.useState(false);

  const clamp = (v: number) => (v < min ? min : v > max ? max : v);
  const snapToStep = (v: number) => {
    if (!Number.isFinite(v)) return min;
    const steps = Math.round((v - min) / step);
    const snapped = min + steps * step;
    return clamp(Number(snapped.toFixed(6)));
  };

  const percent = React.useMemo(() => {
    if (max === min) return 0;
    const p = ((value - min) / (max - min)) * 100;
    return p < 0 ? 0 : p > 100 ? 100 : p;
  }, [value, min, max]);

  const calcFromClientX = React.useCallback(
    (clientX: number) => {
      const el = trackRef.current;
      if (!el || !Number.isFinite(clientX)) return;
      const rect = el.getBoundingClientRect();
      const width = rect.right - rect.left;
      if (!(width > 0)) return;

      // Clamp ratio to [0, 1] so it can never flip ends.
      const ratioRaw = (clientX - rect.left) / width;
      const ratio = ratioRaw <= 0 ? 0 : ratioRaw >= 1 ? 1 : ratioRaw;

      const raw = min + ratio * (max - min);
      onChange(snapToStep(raw));
    },
    [min, max, step, onChange],
  );

  // Mouse/touch pointer drag
  const onPointerDown = (e: React.PointerEvent) => {
    if (disabled) return;
    e.preventDefault();

    // Capture the pointer so moves outside the element are still tracked correctly
    try {
      (e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId);
    } catch {}

    setDragging(true);
    calcFromClientX(e.clientX);

    const handleMove = (ev: PointerEvent) => {
      ev.preventDefault();
      calcFromClientX(ev.clientX);
    };
    const handleUp = (ev: PointerEvent) => {
      setDragging(false);
      // release capture if we got it
      try {
        (e.currentTarget as HTMLElement).releasePointerCapture?.(e.pointerId);
      } catch {}
      window.removeEventListener('pointermove', handleMove);
      window.removeEventListener('pointerup', handleUp);
    };

    window.addEventListener('pointermove', handleMove);
    window.addEventListener('pointerup', handleUp);
  };

  return (
    <div className={classNames(className, disabled ? 'opacity-30 cursor-not-allowed' : '')}>
      {label && (
        <label className={labelClasses}>
          {label}{' '}
          {doc && (
            <div className="inline-block ml-1 text-xs text-gray-500 cursor-pointer" onClick={() => openDoc(doc)}>
              <CircleHelp className="inline-block w-4 h-4 cursor-pointer" />
            </div>
          )}
        </label>
      )}

      <div className="flex items-center gap-3">
        <div className="flex-1">
          <div
            ref={trackRef}
            onPointerDown={onPointerDown}
            className={classNames(
              'relative w-full h-6 select-none outline-none',
              disabled ? 'pointer-events-none' : 'cursor-pointer',
            )}
          >
            {/* Thicker track */}
            <div className="pointer-events-none absolute left-0 right-0 top-1/2 -translate-y-1/2 h-3 rounded-sm bg-gray-800 border border-gray-700" />

            {/* Fill */}
            <div
              className="pointer-events-none absolute left-0 top-1/2 -translate-y-1/2 h-3 rounded-sm bg-blue-500"
              style={{ width: `${percent}%` }}
            />

            {/* Thumb */}
            <div
              onPointerDown={onPointerDown}
              className={classNames(
                'absolute top-1/2 -translate-y-1/2 -ml-2',
                'h-4 w-4 rounded-full bg-white shadow border border-gray-300 cursor-pointer',
                'after:content-[""] after:absolute after:inset-[-6px] after:rounded-full after:bg-transparent', // expands hit area
                dragging ? 'ring-2 ring-blue-600' : '',
              )}
              style={{ left: `calc(${percent}% )` }}
            />
          </div>

          <div className="flex justify-between text-xs text-gray-500 mt-0.5 select-none">
            <span>{min}</span>
            <span>{max}</span>
          </div>
        </div>

        {showValue && (
          <div className="min-w-[3.5rem] text-right text-sm px-3 py-1 bg-gray-950 dark:bg-gray-800 border border-gray-700 rounded-sm">
            {Number.isFinite(value) ? value : ''}
          </div>
        )}
      </div>
    </div>
  );
};
