import React, { KeyboardEventHandler, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import classNames from 'classnames';
import { Icon, IconButton, Icons } from 'folds';
import {
  TOPIC_SEARCH_QUALIFIERS,
  collectAuthorHintsFromThreads,
  collectTitleHintsFromThreads,
  insertTopicSearchQualifierAt,
  insertTopicSearchValueAt,
  matchAuthorHints,
  matchTitleHints,
  matchTopicSearchQualifiers,
  parseTopicSearchQuery,
  readActiveQualifierValueAt,
  readQualifierPartialAt,
  type TopicSearchAuthorHint,
  type TopicSearchQualifier,
} from './topicSearch';
import type { ForumThreadSummary } from './types';
import * as t from './forumTheme.css';

type ForumTopicSearchInputProps = {
  value: string;
  onChange: (value: string) => void;
  threads?: ForumThreadSummary[];
};

type SuggestionMode = 'qualifier' | 'author' | 'title';

export function ForumTopicSearchInput({ value, onChange, threads = [] }: ForumTopicSearchInputProps) {
  const inputRef = useRef<HTMLInputElement>(null);
  const mirrorRef = useRef<HTMLSpanElement>(null);
  const pillsRef = useRef<HTMLDivElement>(null);

  const [focused, setFocused] = useState(false);
  const [caret, setCaret] = useState(0);
  const [highlightIndex, setHighlightIndex] = useState(0);
  const [tabPartial, setTabPartial] = useState('');

  const authorHints = useMemo(() => collectAuthorHintsFromThreads(threads), [threads]);
  const titleHints = useMemo(() => collectTitleHintsFromThreads(threads), [threads]);

  const qualifierPartial = readQualifierPartialAt(value, caret);
  const valueContext = readActiveQualifierValueAt(value, caret);
  const qualifierMatches = qualifierPartial
    ? matchTopicSearchQualifiers(qualifierPartial)
    : TOPIC_SEARCH_QUALIFIERS;

  const authorMatches = useMemo(
    () =>
      valueContext?.key === 'from' ? matchAuthorHints(valueContext.partial, authorHints) : [],
    [authorHints, valueContext]
  );

  const titleMatches = useMemo(
    () =>
      valueContext?.key === 'intitle' ? matchTitleHints(valueContext.partial, titleHints) : [],
    [titleHints, valueContext]
  );

  const suggestionMode: SuggestionMode | null = useMemo(() => {
    if (!focused) {
      return null;
    }
    if (valueContext?.key === 'from' && authorHints.length > 0) {
      return 'author';
    }
    if (valueContext?.key === 'intitle' && titleHints.length > 0) {
      return 'title';
    }
    if (!qualifierPartial || qualifierMatches.length > 0) {
      return 'qualifier';
    }
    return null;
  }, [
    focused,
    valueContext,
    authorHints.length,
    titleHints.length,
    qualifierPartial,
    qualifierMatches.length,
  ]);

  const suggestionCount =
    suggestionMode === 'author'
      ? authorMatches.length
      : suggestionMode === 'title'
        ? titleMatches.length
        : suggestionMode === 'qualifier'
          ? qualifierMatches.length
          : 0;

  const showPills = suggestionMode !== null && suggestionCount > 0;
  const parsed = parseTopicSearchQuery(value);

  const positionFloatingPills = useCallback(() => {
    const input = inputRef.current;
    const root = pillsRef.current;
    const mirror = mirrorRef.current;
    if (!input || !root || root.hidden) {
      return;
    }

    const caretPos = input.selectionStart ?? input.value.length;
    const textBefore = input.value.slice(0, caretPos).replace(/\s/g, '\u00a0') || '\u200b';

    if (mirror) {
      const inputStyle = window.getComputedStyle(input);
      mirror.textContent = textBefore;
      mirror.style.font = inputStyle.font;
      mirror.style.letterSpacing = inputStyle.letterSpacing;
    }

    const mirrorWidth = mirror?.offsetWidth ?? 0;
    const maxLeft = Math.max(0, input.clientWidth - root.offsetWidth);
    const left = Math.min(Math.max(0, mirrorWidth - 12), maxLeft);
    root.style.left = `${left}px`;

    const caretLeft = Math.min(Math.max(12, mirrorWidth), input.clientWidth - 12);
    root.style.setProperty('--forum-search-caret', `${caretLeft - left}px`);
  }, []);

  useEffect(() => {
    positionFloatingPills();
  }, [value, caret, showPills, positionFloatingPills]);

  useEffect(() => {
    setHighlightIndex(0);
    setTabPartial('');
  }, [suggestionMode, valueContext?.partial, qualifierPartial]);

  const applyInputUpdate = useCallback(
    (nextValue: string, nextCursor: number) => {
      onChange(nextValue);
      setTabPartial('');
      setHighlightIndex(0);
      window.requestAnimationFrame(() => {
        const input = inputRef.current;
        if (!input) return;
        input.focus();
        input.setSelectionRange(nextCursor, nextCursor);
        setCaret(nextCursor);
        positionFloatingPills();
      });
    },
    [onChange, positionFloatingPills]
  );

  const insertQualifier = useCallback(
    (qualifier: TopicSearchQualifier) => {
      const input = inputRef.current;
      if (!input) return;
      const caretPos = input.selectionStart ?? input.value.length;
      const { value: nextValue, cursor } = insertTopicSearchQualifierAt(value, caretPos, qualifier);
      applyInputUpdate(nextValue, cursor);
    },
    [applyInputUpdate, value]
  );

  const insertValue = useCallback(
    (replacement: string) => {
      const input = inputRef.current;
      if (!input) return;
      const caretPos = input.selectionStart ?? input.value.length;
      const { value: nextValue, cursor } = insertTopicSearchValueAt(value, caretPos, replacement);
      applyInputUpdate(nextValue, cursor);
    },
    [applyInputUpdate, value]
  );

  const selectAtIndex = useCallback(
    (index: number) => {
      if (suggestionMode === 'author') {
        const hint = authorMatches[index];
        if (hint) {
          insertValue(hint.displayName);
        }
        return;
      }
      if (suggestionMode === 'title') {
        const title = titleMatches[index];
        if (title) {
          insertValue(title);
        }
        return;
      }
      if (suggestionMode === 'qualifier') {
        const qualifier = qualifierMatches[index];
        if (qualifier) {
          insertQualifier(qualifier);
        }
      }
    },
    [authorMatches, insertQualifier, insertValue, qualifierMatches, suggestionMode, titleMatches]
  );

  const selectHighlighted = useCallback(() => {
    selectAtIndex(highlightIndex);
  }, [highlightIndex, selectAtIndex]);

  const moveHighlight = useCallback(
    (delta: number) => {
      if (!suggestionCount) return;
      setHighlightIndex((idx) => (idx + delta + suggestionCount) % suggestionCount);
    },
    [suggestionCount]
  );

  const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (event) => {
    const pillsVisible = showPills && suggestionCount > 0;

    if (pillsVisible && (event.key === 'ArrowRight' || event.key === 'ArrowDown')) {
      event.preventDefault();
      moveHighlight(1);
      return;
    }

    if (pillsVisible && (event.key === 'ArrowLeft' || event.key === 'ArrowUp')) {
      event.preventDefault();
      moveHighlight(-1);
      return;
    }

    if (
      pillsVisible &&
      event.key === 'Enter' &&
      (suggestionMode === 'author' ||
        suggestionMode === 'title' ||
        (suggestionMode === 'qualifier' && qualifierPartial))
    ) {
      event.preventDefault();
      selectHighlighted();
      return;
    }

    if (event.key === 'Escape' && pillsVisible) {
      event.preventDefault();
      setTabPartial('');
      setHighlightIndex(0);
      inputRef.current?.blur();
      return;
    }

    if (event.key !== 'Tab' || event.altKey || event.ctrlKey || event.metaKey) {
      return;
    }

    if (!pillsVisible) {
      return;
    }

    event.preventDefault();

    const tabKey =
      suggestionMode === 'author'
        ? `author:${valueContext?.partial ?? ''}`
        : suggestionMode === 'title'
          ? `title:${valueContext?.partial ?? ''}`
          : `qualifier:${qualifierPartial}`;

    const nextIndex =
      tabKey === tabPartial ? (highlightIndex + 1) % suggestionCount : 0;
    setTabPartial(tabKey);
    setHighlightIndex(nextIndex);
    selectAtIndex(nextIndex);
  };

  const syncCaret = () => {
    const input = inputRef.current;
    if (!input) return;
    setCaret(input.selectionStart ?? input.value.length);
  };

  const pillsLabel =
    suggestionMode === 'author'
      ? 'Author suggestions'
      : suggestionMode === 'title'
        ? 'Title suggestions'
        : 'Search qualifiers';

  const matchKeys = new Set(qualifierMatches.map((item) => item.key));

  return (
    <div className={t.ForumSearchField}>
      <div className={classNames(t.ForumSearchRow, t.ForumSearchRowAnchor)}>
        <span ref={mirrorRef} className={t.ForumSearchCaretMirror} aria-hidden />
        <input
          ref={inputRef}
          className={t.ForumSearchInput}
          type="search"
          name="q"
          value={value}
          onChange={(event) => {
            onChange(event.target.value);
            syncCaret();
          }}
          onFocus={() => {
            setFocused(true);
            syncCaret();
          }}
          onBlur={() => {
            window.setTimeout(() => setFocused(false), 120);
          }}
          onClick={syncCaret}
          onKeyUp={syncCaret}
          onKeyDown={handleKeyDown}
          placeholder="Search posts…"
          title="Examples: intitle:release inpost:bug inthread:thanks from:alice"
          aria-label="Search posts"
          aria-controls="forumSearchPills"
          autoComplete="off"
        />
        <div
          ref={pillsRef}
          id="forumSearchPills"
          className={t.ForumSearchPills}
          role="listbox"
          aria-label={pillsLabel}
          hidden={!showPills}
        >
          <div className={t.ForumSearchPillsInner}>
            {suggestionMode === 'author' &&
              authorMatches.map((hint, index) => (
                <button
                  key={hint.userId}
                  type="button"
                  role="option"
                  aria-selected={index === highlightIndex}
                  className={classNames(t.ForumSearchPill, t.ForumSearchPillValue, {
                    [t.ForumSearchPillHighlighted]: index === highlightIndex,
                  })}
                  title={`${hint.displayName} (${hint.userId})`}
                  onMouseDown={(event) => event.preventDefault()}
                  onClick={() => insertValue(hint.displayName)}
                >
                  {hint.displayName}
                  <span style={{ opacity: 0.65 }}> @{hint.localpart}</span>
                </button>
              ))}

            {suggestionMode === 'title' &&
              titleMatches.map((title, index) => (
                <button
                  key={title}
                  type="button"
                  role="option"
                  aria-selected={index === highlightIndex}
                  className={classNames(t.ForumSearchPill, t.ForumSearchPillValue, {
                    [t.ForumSearchPillHighlighted]: index === highlightIndex,
                  })}
                  title={title}
                  onMouseDown={(event) => event.preventDefault()}
                  onClick={() => insertValue(title)}
                >
                  {title}
                </button>
              ))}

            {suggestionMode === 'qualifier' &&
              TOPIC_SEARCH_QUALIFIERS.map((qualifier) => {
                const visible = !qualifierPartial || matchKeys.has(qualifier.key);
                const matchIndex = qualifierMatches.findIndex((item) => item.key === qualifier.key);
                const highlighted = visible && matchIndex === highlightIndex;
                const inUse = Boolean(parsed.qualifiers[qualifier.key]?.length);

                return (
                  <button
                    key={qualifier.key}
                    type="button"
                    role="option"
                    aria-selected={highlighted}
                    className={classNames(t.ForumSearchPill, {
                      [t.ForumSearchPillHighlighted]: highlighted,
                      [t.ForumSearchPillInUse]: inUse,
                    })}
                    data-qualifier={qualifier.key}
                    title={qualifier.label}
                    aria-label={`${qualifier.label} (${qualifier.token})`}
                    hidden={!visible}
                    onMouseDown={(event) => event.preventDefault()}
                    onClick={() => insertQualifier(qualifier)}
                  >
                    {qualifier.token}
                  </button>
                );
              })}
          </div>
        </div>
        <IconButton
          className={t.ForumSearchSubmit}
          type="submit"
          variant="SurfaceVariant"
          fill="None"
          size="300"
          radii="300"
          aria-label="Search"
        >
          <Icon src={Icons.Search} size="200" />
        </IconButton>
      </div>
    </div>
  );
}
