// components/ui/Combobox.ts

'use client';

import {
  Combobox,
  ComboboxButton,
  ComboboxInput,
  ComboboxOption,
  ComboboxOptions,
  Label,
} from '@headlessui/react';
import { ChevronDownIcon } from '@heroicons/react/20/solid';
import { UserIcon } from '@heroicons/react/16/solid';
import { useEffect, useMemo, useState } from 'react';

function classNames(...classes: Array<string | boolean | null | undefined>) {
  return classes.filter(Boolean).join(' ');
}

export type AppComboboxProps<T> = {
  label?: string;
  options: T[];
  value: T | null;
  onChange: (value: T | null) => void;

  /** Eindeutiger Key pro Option */
  getKey: (option: T) => string | number;

  /** Primärer Text (wie 'name') */
  getPrimaryLabel: (option: T) => string;

  /** Optional: sekundärer Text (z.B. Gruppe) */
  getSecondaryLabel?: (option: T) => string | null;

  /** Optional: zusätzlicher Suchstring über mehrere Felder */
  getSearchText?: (option: T) => string | null;

  /** Optional: Avatar-URL */
  getImageUrl?: (option: T) => string | null;

  /**
   * Optional: Status für Punkt-Indikator
   * Wird nur benutzt, wenn KEIN Avatar gesetzt ist.
   */
  getStatus?: (option: T) => 'online' | 'offline' | 'neutral' | null;

  placeholder?: string;

  /** Query darf einen neuen Wert erzeugen */
  allowCreateFromQuery?: boolean;

  /**
   * Wenn gesetzt, wird bei Auswahl der "Neuen Wert aus Query"-Option
   * diese Funktion aufgerufen und das Ergebnis als Auswahl übernommen.
   */
  onCreateFromQuery?: (query: string) => T;

  /** Ob auch im Secondary-Text gesucht werden soll */
  filterBySecondary?: boolean;

  className?: string;
};

export default function AppCombobox<T>({
  label = 'Auswahl',
  options,
  value,
  onChange,
  getKey,
  getPrimaryLabel,
  getSecondaryLabel,
  getSearchText,
  getImageUrl,
  getStatus,
  placeholder = 'Auswählen…',
  allowCreateFromQuery = false,
  onCreateFromQuery,
  filterBySecondary = true,
  className,
}: AppComboboxProps<T>) {
  // Einzige Quelle der Wahrheit für den Text im Input
  const [query, setQuery] = useState('');

  // Wenn sich die ausgewählte Option von außen ändert,
  // den Input-Text entsprechend synchronisieren.
  useEffect(() => {
    if (value) {
      setQuery(getPrimaryLabel(value));
    } else {
      setQuery('');
    }
    // getPrimaryLabel absichtlich NICHT in deps,
    // sonst triggern wir bei jedem Render ein setState.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [value]);

  const filtered = useMemo(() => {
    const q = query.trim().toLowerCase();
    if (!q) return options;

    return options.filter((opt) => {
      // Primär-Label
      const primary = getPrimaryLabel(opt).toLowerCase();
      if (primary.includes(q)) return true;

      // Secondary (optional, z.B. Gruppe)
      if (filterBySecondary && getSecondaryLabel) {
        const sec = (getSecondaryLabel(opt) ?? '').toLowerCase();
        if (sec.includes(q)) return true;
      }

      // zusätzlicher Suchtext (z.B. Vorname, Nachname, nwkennung, Gruppe)
      if (getSearchText) {
        const extra = (getSearchText(opt) ?? '').toLowerCase();
        if (extra.includes(q)) return true;
      }

      return false;
    });
  }, [
    options,
    query,
    getPrimaryLabel,
    getSecondaryLabel,
    getSearchText,
    filterBySecondary,
  ]);

  const showCreateRow =
    allowCreateFromQuery &&
    query.trim().length > 0 &&
    !options.some(
      (opt) =>
        getPrimaryLabel(opt).toLowerCase() ===
        query.trim().toLowerCase(),
    );

  const handleChange = (selected: any) => {
    // Sonderfall: "neuen Wert aus Query" gewählt
    if (selected && selected.__isCreateOption && onCreateFromQuery) {
      const created = onCreateFromQuery(selected.query as string);
      // Query auf Label der neu erzeugten Option setzen
      setQuery(getPrimaryLabel(created));
      onChange(created);
      return;
    }

    if (selected) {
      // Query auf Label der ausgewählten Option setzen
      setQuery(getPrimaryLabel(selected));
      onChange(selected);
    } else {
      // Auswahl gelöscht
      setQuery('');
      onChange(null);
    }
  };

  const renderOptionInner = (
    opt: T | { __isCreateOption: true; query: string },
  ) => {
    // Create-Row (Freitext)
    if ((opt as any).__isCreateOption) {
      const q = (opt as any).query as string;

      const hasAvatar = !!getImageUrl;
      const hasStatus = !!getStatus && !hasAvatar;

      return (
        <div className="flex items-center">
          {hasAvatar && (
            <div className="grid size-6 shrink-0 place-items-center rounded-full bg-gray-300 in-data-focus:bg-white dark:bg-gray-600">
              <UserIcon
                className="size-4 fill-white in-data-focus:fill-indigo-600 dark:in-data-focus:fill-indigo-500"
                aria-hidden="true"
              />
            </div>
          )}
          {hasStatus && (
            <span
              className="inline-block size-2 shrink-0 rounded-full border border-gray-400 in-aria-active:border-white/75 dark:border-gray-600"
              aria-hidden="true"
            />
          )}
          <span
            className={classNames(
              'block truncate',
              hasAvatar || hasStatus ? 'ml-3' : null,
            )}
          >
            {q}
          </span>
        </div>
      );
    }

    // Normale Option
    const o = opt as T;
    const labelText = getPrimaryLabel(o);
    const secondary = getSecondaryLabel?.(o) ?? null;
    const imageUrl = getImageUrl?.(o) ?? null;
    const status = getStatus?.(o) ?? null;

    const hasAvatar = !!imageUrl;
    const hasStatusDot = !!getStatus && !hasAvatar;

    return (
      <div className="flex items-center">
        {hasAvatar && (
          <img
            src={imageUrl}
            alt=""
            className="size-6 shrink-0 rounded-full bg-gray-100 outline -outline-offset-1 outline-black/5 dark:bg-gray-700 dark:outline-white/10"
          />
        )}

        {hasStatusDot && (
          <span
            className={classNames(
              'inline-block size-2 shrink-0 rounded-full',
              status === 'online'
                ? 'bg-green-400 dark:bg-green-500'
                : 'bg-gray-200 dark:bg-white/20',
            )}
            aria-hidden="true"
          />
        )}

        <span
          className={classNames(
            'block truncate',
            hasAvatar || hasStatusDot ? 'ml-3' : null,
          )}
        >
          {labelText}
        </span>

        {secondary && (
          <span className="ml-2 block truncate text-gray-500 in-data-focus:text-white dark:text-gray-400 dark:in-data-focus:text-white">
            {secondary}
          </span>
        )}
      </div>
    );
  };

  return (
    <Combobox
      as="div"
      value={value}
      onChange={handleChange}
      className={classNames('w-full', className)}
    >
      {label && (
        <Label className="text-xs font-semibold uppercase tracking-wide text-gray-400">
          {label}
        </Label>
      )}

      <div className="relative mt-2">
        <ComboboxInput
          className="block w-full rounded-md bg-white py-1.5 pr-12 pl-3 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-gray-900 dark:text-gray-100 dark:outline-white/10 dark:placeholder:text-gray-500 dark:focus:outline-indigo-500"
          value={query}
          onChange={(event) => setQuery(event.target.value)}
          placeholder={placeholder}
        />
        <ComboboxButton className="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-hidden">
          <ChevronDownIcon
            className="size-5 text-gray-400"
            aria-hidden="true"
          />
        </ComboboxButton>

        <ComboboxOptions
          transition
          className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg outline outline-black/5 data-leave:transition data-leave:duration-100 data-leave:ease-in data-closed:data-leave:opacity-0 sm:text-sm dark:bg-gray-800 dark:shadow-none dark:-outline-offset-1 dark:outline-white/10"
        >
          {/* Freitext-Option */}
          {showCreateRow && (
            <ComboboxOption
              value={{ __isCreateOption: true, query } as any}
              className="cursor-default px-3 py-2 text-gray-900 select-none data-focus:bg-indigo-600 data-focus:text-white data-focus:outline-hidden dark:text-white dark:data-focus:bg-indigo-500"
            >
              {renderOptionInner({
                __isCreateOption: true,
                query,
              } as any)}
            </ComboboxOption>
          )}

          {/* Normale Optionen */}
          {filtered.map((opt) => {
            const baseKey = String(getKey(opt));
            // Position der Option in der *vollen* options-Liste suchen
            const idx = options.indexOf(opt);
            const key = idx === -1 ? baseKey : `${baseKey}__${idx}`;

            return (
              <ComboboxOption
                key={key}
                value={opt}
                className="cursor-default px-3 py-2 text-gray-900 select-none data-focus:bg-indigo-600 data-focus:text-white data-focus:outline-hidden dark:text-gray-300 dark:data-focus:bg-indigo-500"
              >
                {renderOptionInner(opt as any)}
              </ComboboxOption>
            );
          })}

          {/* Keine Treffer */}
          {filtered.length === 0 && !showCreateRow && (
            <div className="px-3 py-2 text-xs text-gray-500 dark:text-gray-400">
              Keine Treffer
            </div>
          )}
        </ComboboxOptions>
      </div>
    </Combobox>
  );
}
