import {
  useEffect,
  useId,
  useMemo,
  useRef,
  useState,
  type FormEvent,
  type FocusEvent,
  type KeyboardEvent,
  type ReactNode
} from "react";
import { Plus, Search, Trash2, UserPlus } from "lucide-react";
import { i18nMessage, usePlatformLanguage } from "../../i18n/LanguageContext";
import { isValidEmailAddress } from "../../utils/emailAddresses";
import Button from "../Button";
import FieldLabel from "../help/FieldLabel";
import DataGrid, { DataGridEmptyAction, type DataGridColumn } from "../table/DataGrid";
import TableActionGroup from "../table/TableActionGroup";
import {
  dedupePeoplePickerItems,
  externalPeoplePickerItem,
  peoplePickerDedupeKey,
  selectionFromPeoplePickerCandidate,
  type PeoplePickerItem,
  type PeoplePickerSearch,
  type PeoplePickerSearchCandidate,
  type PeoplePickerSearchGroup
} from "./peoplePickerTypes";

export type PeoplePickerProps = {
  id?: string;
  label?: ReactNode;
  help?: ReactNode;
  selectedLabel?: ReactNode;
  value: readonly PeoplePickerItem[];
  onChange: (value: PeoplePickerItem[]) => void;
  search: PeoplePickerSearch;
  disabled?: boolean;
  required?: boolean;
  allowManualExternal?: boolean;
  manualEmailRequired?: boolean;
  minQueryLength?: number;
  searchLimit?: number;
  debounceMs?: number;
  className?: string;
};

type IndexedCandidate = {
  candidate: PeoplePickerSearchCandidate;
  group: PeoplePickerSearchGroup;
  index: number;
};

const I18N = {
  people: "i18n:govoplan-core.people.b37554f6",
  searchPeople: "i18n:govoplan-core.search_people_and_contacts.8e028a78",
  searchPlaceholder: "i18n:govoplan-core.search_by_name_or_email_address.6283d84d",
  searching: "i18n:govoplan-core.searching.1a6a5ba8",
  noMatches: "i18n:govoplan-core.no_matching_people_or_contacts_found.4c134688",
  addExternal: "i18n:govoplan-core.add_external_person.b19c7abc",
  externalPerson: "i18n:govoplan-core.external_person.35115d8e",
  selectedPeople: "i18n:govoplan-core.selected_people.49a953a1",
  noSelection: "i18n:govoplan-core.no_people_selected_yet.9383cc74",
  source: "i18n:govoplan-core.source.6da13add",
  addPerson: "i18n:govoplan-core.add_person.3b10561b",
  removePerson: "i18n:govoplan-core.remove_person.2ea46758",
  alreadySelected: "i18n:govoplan-core.this_person_is_already_selected.d337154c",
  enterName: "i18n:govoplan-core.enter_a_name.86c810d9",
  searchFailed: "i18n:govoplan-core.unable_to_search_people_right_now.fcf680f7",
  addSomeone: "i18n:govoplan-core.add_someone_to_the_selection.c617dd69"
} as const;

function translatedNode(node: ReactNode, translateText: (value: string) => string): ReactNode {
  return typeof node === "string" ? translateText(node) : node;
}

function flattenGroups(groups: readonly PeoplePickerSearchGroup[]): IndexedCandidate[] {
  const flattened: IndexedCandidate[] = [];
  const seen = new Set<string>();
  for (const group of groups) {
    for (const candidate of group.candidates) {
      if (!candidate.selection_key || seen.has(candidate.selection_key)) continue;
      seen.add(candidate.selection_key);
      flattened.push({ candidate, group, index: flattened.length });
    }
  }
  return flattened;
}

export default function PeoplePicker({
  id,
  label = I18N.people,
  help,
  selectedLabel = I18N.selectedPeople,
  value,
  onChange,
  search,
  disabled = false,
  required = false,
  allowManualExternal = true,
  manualEmailRequired = true,
  minQueryLength = 2,
  searchLimit = 25,
  debounceMs = 250,
  className = ""
}: PeoplePickerProps) {
  const { translateText } = usePlatformLanguage();
  const generatedId = useId().replace(/[^a-zA-Z0-9_-]/g, "-");
  const pickerId = id || `people-picker-${generatedId}`;
  const labelId = `${pickerId}-label`;
  const inputId = `${pickerId}-search`;
  const listboxId = `${pickerId}-results`;
  const statusId = `${pickerId}-status`;
  const searchInputRef = useRef<HTMLInputElement | null>(null);
  const manualNameRef = useRef<HTMLInputElement | null>(null);
  const manualEmailRef = useRef<HTMLInputElement | null>(null);
  const selectedItems = useMemo(() => dedupePeoplePickerItems(value), [value]);
  const selectedKeys = useMemo(
    () => new Set(selectedItems.map(peoplePickerDedupeKey)),
    [selectedItems]
  );
  const [query, setQuery] = useState("");
  const [groups, setGroups] = useState<readonly PeoplePickerSearchGroup[]>([]);
  const [loading, setLoading] = useState(false);
  const [hasSearched, setHasSearched] = useState(false);
  const [searchError, setSearchError] = useState("");
  const [open, setOpen] = useState(false);
  const [activeIndex, setActiveIndex] = useState(-1);
  const [manualOpen, setManualOpen] = useState(false);
  const [manualName, setManualName] = useState("");
  const [manualEmail, setManualEmail] = useState("");
  const [manualError, setManualError] = useState("");
  const normalizedMinQueryLength = Math.max(0, Math.floor(minQueryLength));
  const normalizedSearchLimit = Math.max(1, Math.min(Math.floor(searchLimit), 100));
  const flattened = useMemo(() => flattenGroups(groups), [groups]);

  function candidateUnavailable(candidate: PeoplePickerSearchCandidate): boolean {
    return Boolean(candidate.disabled || selectedKeys.has(peoplePickerDedupeKey(candidate)));
  }

  useEffect(() => {
    const normalizedQuery = query.trim();
    if (disabled || normalizedQuery.length < normalizedMinQueryLength) {
      setGroups([]);
      setLoading(false);
      setHasSearched(false);
      setSearchError("");
      return;
    }

    const controller = new AbortController();
    const timer = window.setTimeout(() => {
      setLoading(true);
      setSearchError("");
      void search(normalizedQuery, { limit: normalizedSearchLimit, signal: controller.signal })
        .then((nextGroups) => {
          if (controller.signal.aborted) return;
          setGroups(nextGroups);
          setHasSearched(true);
        })
        .catch((error: unknown) => {
          if (controller.signal.aborted || (error instanceof DOMException && error.name === "AbortError")) return;
          setGroups([]);
          setHasSearched(true);
          setSearchError(I18N.searchFailed);
        })
        .finally(() => {
          if (!controller.signal.aborted) setLoading(false);
        });
    }, Math.max(0, Math.floor(debounceMs)));

    return () => {
      controller.abort();
      window.clearTimeout(timer);
    };
  }, [debounceMs, disabled, normalizedMinQueryLength, normalizedSearchLimit, query, search]);

  useEffect(() => {
    const firstAvailable = flattened.find((item) => !candidateUnavailable(item.candidate));
    setActiveIndex(firstAvailable?.index ?? -1);
  }, [flattened, selectedKeys]);

  useEffect(() => {
    if (manualOpen) manualNameRef.current?.focus();
  }, [manualOpen]);

  function focusSearch() {
    if (disabled) return;
    setOpen(true);
    searchInputRef.current?.focus();
  }

  function addCandidate(candidate: PeoplePickerSearchCandidate) {
    if (disabled || candidateUnavailable(candidate)) return;
    onChange(dedupePeoplePickerItems([...selectedItems, selectionFromPeoplePickerCandidate(candidate)]));
    setQuery("");
    setGroups([]);
    setHasSearched(false);
    setOpen(false);
    window.requestAnimationFrame(() => searchInputRef.current?.focus());
  }

  function removeItem(item: PeoplePickerItem) {
    if (disabled) return;
    const removedKey = peoplePickerDedupeKey(item);
    onChange(selectedItems.filter((candidate) => peoplePickerDedupeKey(candidate) !== removedKey));
  }

  function moveActive(delta: 1 | -1) {
    const available = flattened.filter((item) => !candidateUnavailable(item.candidate));
    if (available.length === 0) {
      setActiveIndex(-1);
      return;
    }
    const currentPosition = available.findIndex((item) => item.index === activeIndex);
    const nextPosition = currentPosition < 0
      ? (delta > 0 ? 0 : available.length - 1)
      : (currentPosition + delta + available.length) % available.length;
    setActiveIndex(available[nextPosition].index);
  }

  function onSearchKeyDown(event: KeyboardEvent<HTMLInputElement>) {
    if (event.key === "ArrowDown") {
      event.preventDefault();
      setOpen(true);
      moveActive(1);
      return;
    }
    if (event.key === "ArrowUp") {
      event.preventDefault();
      setOpen(true);
      moveActive(-1);
      return;
    }
    if (event.key === "Enter" && activeIndex >= 0 && open) {
      event.preventDefault();
      const active = flattened.find((item) => item.index === activeIndex);
      if (active) addCandidate(active.candidate);
      return;
    }
    if (event.key === "Escape") {
      event.preventDefault();
      setOpen(false);
      setActiveIndex(-1);
    }
  }

  function addManualPerson(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const name = manualName.trim();
    const email = manualEmail.trim().toLowerCase();
    if (!name) {
      setManualError(I18N.enterName);
      manualNameRef.current?.focus();
      return;
    }
    if ((manualEmailRequired && !email) || (email && !isValidEmailAddress(email))) {
      setManualError("i18n:govoplan-core.enter_a_valid_email_address.2e09edea");
      manualEmailRef.current?.focus();
      return;
    }
    const item = externalPeoplePickerItem(name, email || null);
    if (selectedKeys.has(peoplePickerDedupeKey(item))) {
      setManualError(I18N.alreadySelected);
      return;
    }
    onChange(dedupePeoplePickerItems([...selectedItems, item]));
    setManualName("");
    setManualEmail("");
    setManualError("");
    setManualOpen(false);
  }

  function closeResultsOnFocusLeave(event: FocusEvent<HTMLElement>) {
    const nextTarget = event.relatedTarget;
    if (nextTarget instanceof Node && event.currentTarget.contains(nextTarget)) return;
    setOpen(false);
  }

  const statusMessage = loading
    ? I18N.searching
    : searchError
      ? searchError
      : query.trim() && query.trim().length < normalizedMinQueryLength
        ? i18nMessage("i18n:govoplan-core.type_at_least__value0__characters_to_search.d7d04bad", {
          value0: normalizedMinQueryLength
        })
        : hasSearched && flattened.length === 0
          ? I18N.noMatches
          : "";
  const resultsVisible = open && !disabled && query.trim().length >= normalizedMinQueryLength;
  const columns: DataGridColumn<PeoplePickerItem>[] = [
    {
      id: "name",
      header: "i18n:govoplan-core.name.709a2322",
      minWidth: 180,
      resizable: true,
      render: (item) => (
        <div className="people-picker-selected-person">
          <strong>{item.display_name}</strong>
          {item.description && <small>{item.description}</small>}
        </div>
      )
    },
    {
      id: "email",
      header: "i18n:govoplan-core.email_address.c94d3175",
      minWidth: 220,
      resizable: true,
      value: (item) => item.email || "—"
    },
    {
      id: "source",
      header: I18N.source,
      minWidth: 130,
      resizable: true,
      value: (item) => translateText(item.source_label || kindLabel(item.kind))
    },
    {
      id: "actions",
      header: "i18n:govoplan-core.actions.c3cd636a",
      width: 104,
      sticky: "end",
      align: "right",
      render: (item) => (
        <TableActionGroup
          minimumSlots={2}
          actions={[
            {
              id: "add",
              label: I18N.addPerson,
              icon: <Plus size={16} aria-hidden="true" />,
              disabled,
              onClick: focusSearch
            },
            {
              id: "remove",
              label: I18N.removePerson,
              icon: <Trash2 size={16} aria-hidden="true" />,
              variant: "danger",
              disabled,
              onClick: () => removeItem(item)
            }
          ]}
        />
      )
    }
  ];

  return (
    <section className={`people-picker ${className}`.trim()} aria-labelledby={labelId} onBlur={closeResultsOnFocusLeave}>
      <div className="people-picker-search-field">
        <label htmlFor={inputId} id={labelId}>
          <FieldLabel help={help}>{translatedNode(label, translateText)}</FieldLabel>
        </label>
        <div className="people-picker-search-control">
          <Search size={17} aria-hidden="true" />
          <input
            ref={searchInputRef}
            id={inputId}
            type="search"
            role="combobox"
            value={query}
            disabled={disabled}
            required={required && selectedItems.length === 0}
            placeholder={translateText(I18N.searchPlaceholder)}
            aria-label={translateText(I18N.searchPeople)}
            aria-autocomplete="list"
            aria-expanded={resultsVisible}
            aria-controls={listboxId}
            aria-activedescendant={activeIndex >= 0 ? `${pickerId}-option-${activeIndex}` : undefined}
            aria-describedby={statusId}
            aria-busy={loading}
            onFocus={() => setOpen(true)}
            onChange={(event) => {
              setQuery(event.target.value);
              setOpen(true);
              setSearchError("");
            }}
            onKeyDown={onSearchKeyDown}
          />
        </div>

        {resultsVisible && (
          <div id={listboxId} className="people-picker-results" role="listbox" aria-label={translateText(I18N.searchPeople)}>
            {groups.map((group) => {
              const candidates = flattened.filter((item) => item.group.key === group.key);
              if (candidates.length === 0) return null;
              const groupLabelId = `${pickerId}-group-${group.key.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
              return (
                <div key={group.key} className="people-picker-result-group" role="group" aria-labelledby={groupLabelId}>
                  <div id={groupLabelId} className="people-picker-result-group-label">{translateText(group.label)}</div>
                  {candidates.map(({ candidate, index }) => {
                    const selected = selectedKeys.has(peoplePickerDedupeKey(candidate));
                    const unavailable = candidateUnavailable(candidate);
                    const disabledReason = selected ? I18N.alreadySelected : candidate.disabled_reason;
                    return (
                      <button
                        id={`${pickerId}-option-${index}`}
                        key={candidate.selection_key}
                        type="button"
                        role="option"
                        className={`people-picker-result ${activeIndex === index ? "is-active" : ""}`.trim()}
                        aria-selected={selected}
                        aria-disabled={unavailable}
                        disabled={unavailable}
                        title={disabledReason ? translateText(disabledReason) : undefined}
                        onMouseDown={(event) => event.preventDefault()}
                        onMouseEnter={() => !unavailable && setActiveIndex(index)}
                        onFocus={() => !unavailable && setActiveIndex(index)}
                        onClick={() => addCandidate(candidate)}
                      >
                        <span className="people-picker-result-main">
                          <strong>{candidate.display_name}</strong>
                          {candidate.email && <small>{candidate.email}</small>}
                        </span>
                        <span className="people-picker-result-context">
                          {candidate.description && <small>{candidate.description}</small>}
                          <small>{translateText(candidate.source_label || group.label)}</small>
                        </span>
                      </button>
                    );
                  })}
                </div>
              );
            })}
          </div>
        )}
        <div id={statusId} className={`people-picker-status ${searchError ? "danger-text" : ""}`.trim()} role={searchError ? "alert" : "status"} aria-live="polite">
          {statusMessage && translateText(statusMessage)}
        </div>
      </div>

      {allowManualExternal && (
        <div className="people-picker-manual">
          <Button
            type="button"
            variant="secondary"
            disabled={disabled}
            aria-expanded={manualOpen}
            onClick={() => {
              setManualOpen((current) => !current);
              setManualError("");
            }}
          >
            <UserPlus size={16} aria-hidden="true" />
            {translateText(I18N.addExternal)}
          </Button>
          {manualOpen && (
            <form className="people-picker-manual-form" onSubmit={addManualPerson}>
              <label>
                <FieldLabel>{translateText("i18n:govoplan-core.name.709a2322")}</FieldLabel>
                <input
                  ref={manualNameRef}
                  value={manualName}
                  disabled={disabled}
                  required
                  autoComplete="name"
                  onChange={(event) => {
                    setManualName(event.target.value);
                    setManualError("");
                  }}
                />
              </label>
              <label>
                <FieldLabel>{translateText("i18n:govoplan-core.email_address.c94d3175")}</FieldLabel>
                <input
                  ref={manualEmailRef}
                  type="email"
                  inputMode="email"
                  value={manualEmail}
                  disabled={disabled}
                  required={manualEmailRequired}
                  autoComplete="email"
                  onChange={(event) => {
                    setManualEmail(event.target.value);
                    setManualError("");
                  }}
                />
              </label>
              <div className="people-picker-manual-actions">
                <Button type="button" disabled={disabled} onClick={() => setManualOpen(false)}>
                  {translateText("i18n:govoplan-core.cancel.77dfd213")}
                </Button>
                <Button type="submit" variant="primary" disabled={disabled}>
                  {translateText(I18N.addPerson)}
                </Button>
              </div>
              {manualError && <p className="form-help danger-text" role="alert">{translateText(manualError)}</p>}
            </form>
          )}
        </div>
      )}

      <div className="people-picker-selection" aria-labelledby={`${pickerId}-selection-label`}>
        <span id={`${pickerId}-selection-label`}><FieldLabel>{translatedNode(selectedLabel, translateText)}</FieldLabel></span>
        <DataGrid
          id={`${pickerId}-selection`}
          rows={selectedItems}
          columns={columns}
          getRowKey={peoplePickerDedupeKey}
          emptyText={I18N.noSelection}
          emptyActionColumnId="actions"
          emptyAction={<DataGridEmptyAction disabled={disabled} reorderable={false} onAdd={focusSearch} label={I18N.addSomeone} />}
          initialFit="container"
        />
      </div>
    </section>
  );
}

function kindLabel(kind: string): string {
  if (kind === "account") return "i18n:govoplan-core.accounts.36bae316";
  if (kind === "contact") return "i18n:govoplan-core.contacts.b0dd615c";
  if (kind === "external") return I18N.externalPerson;
  return kind;
}
