import {
  CompletionContext,
  CompletionResult,
  useEffect,
  useRef,
  useState,
} from "../deps.ts";
import { FilterOption } from "../types.ts";
import { FunctionalComponent } from "https://esm.sh/v99/preact@10.11.3/src/index";
import { FeatherProps } from "https://esm.sh/v99/preact-feather@4.2.1/dist/types";
import { MiniEditor } from "./mini_editor.tsx";
import { fuzzySearchAndSort } from "./fuse_search.ts";
import { deepEqual } from "$sb/lib/json.ts";

export function FilterList({
  placeholder,
  options,
  label,
  onSelect,
  onKeyPress,
  completer,
  vimMode,
  darkMode,
  allowNew = false,
  helpText = "",
  completePrefix,
  icon: Icon,
  newHint,
}: {
  placeholder: string;
  options: FilterOption[];
  label: string;
  onKeyPress?: (key: string, currentText: string) => void;
  onSelect: (option: FilterOption | undefined) => void;
  vimMode: boolean;
  darkMode: boolean;
  completer: (context: CompletionContext) => Promise<CompletionResult | null>;
  allowNew?: boolean;
  completePrefix?: string;
  helpText: string;
  newHint?: string;
  icon?: FunctionalComponent<FeatherProps>;
}) {
  const [text, setText] = useState("");
  const [matchingOptions, setMatchingOptions] = useState(
    fuzzySearchAndSort(options, ""),
  );
  const [selectedOption, setSelectionOption] = useState(0);

  const selectedElementRef = useRef<HTMLDivElement>(null);

  function updateFilter(originalPhrase: string) {
    const results = fuzzySearchAndSort(options, originalPhrase);
    const foundExactMatch = !!results.find((result) =>
      result.name === originalPhrase
    );
    if (allowNew && !foundExactMatch && originalPhrase) {
      results.splice(1, 0, {
        name: originalPhrase,
        hint: newHint,
      });
    }

    if (!deepEqual(matchingOptions, results)) {
      // Only do this (=> rerender of UI) if the results have changed
      setMatchingOptions(results);
      setSelectionOption(0);
    }
  }

  useEffect(() => {
    updateFilter(text);
  }, [options, text]);

  useEffect(() => {
    function closer() {
      // console.log("Invoking closer");
      onSelect(undefined);
    }

    document.addEventListener("click", closer);

    return () => {
      document.removeEventListener("click", closer);
    };
  }, []);

  const returnEl = (
    <div className="sb-modal-wrapper">
      <div className="sb-modal-box">
        <div
          className="sb-header"
          onClick={(e) => {
            // Allow tapping/clicking the header without closing it
            e.stopPropagation();
          }}
        >
          <label>{label}</label>
          <MiniEditor
            text={text}
            vimMode={vimMode}
            vimStartInInsertMode={true}
            focus={true}
            darkMode={darkMode}
            completer={completer}
            placeholderText={placeholder}
            onEnter={(_newText, shiftDown) => {
              onSelect(
                shiftDown ? { name: text } : matchingOptions[selectedOption],
              );
              return true;
            }}
            onEscape={() => {
              onSelect(undefined);
            }}
            onChange={(text) => {
              setText(text);
            }}
            onKeyUp={(view, e) => {
              // This event is triggered after the key has been processed by CM already
              if (onKeyPress) {
                onKeyPress(e.key, view.state.sliceDoc());
              }
              return false;
            }}
            onKeyDown={(view, e) => {
              switch (e.key) {
                case "ArrowUp":
                  setSelectionOption(Math.max(0, selectedOption - 1));
                  return true;
                case "ArrowDown":
                  setSelectionOption(
                    Math.min(matchingOptions.length - 1, selectedOption + 1),
                  );
                  return true;
                case "PageUp":
                  setSelectionOption(Math.max(0, selectedOption - 5));
                  return true;
                case "PageDown":
                  setSelectionOption(Math.max(0, selectedOption + 5));
                  return true;
                case "Home":
                  setSelectionOption(0);
                  return true;
                case "End":
                  setSelectionOption(matchingOptions.length - 1);
                  return true;
                case " ": {
                  const text = view.state.sliceDoc();
                  if (completePrefix && text === "") {
                    setText(completePrefix);
                    // updateFilter(completePrefix);
                    return true;
                  }
                  break;
                }
              }
              return false;
            }}
          />
        </div>
        <div
          className="sb-help-text"
          dangerouslySetInnerHTML={{ __html: helpText }}
        >
        </div>
        <div className="sb-result-list">
          {matchingOptions && matchingOptions.length > 0
            ? matchingOptions.map((option, idx) => (
              <div
                key={"" + idx}
                ref={selectedOption === idx ? selectedElementRef : undefined}
                className={selectedOption === idx
                  ? "sb-selected-option"
                  : "sb-option"}
                onMouseOver={(e) => {
                  setSelectionOption(idx);
                }}
                onClick={(e) => {
                  e.stopPropagation();
                  onSelect(option);
                }}
              >
                {Icon && (
                  <span className="sb-icon">
                    <Icon width={16} height={16} />
                  </span>
                )}
                <span className="sb-name" // dangerouslySetInnerHTML={{
                  //   __html: option?.result?.indexes
                  //     ? fuzzysort.highlight(option.result, "<b>", "</b>")!
                  //     : escapeHtml(option.name),
                  // }}
                >
                  {option.name}
                </span>
                {option.hint && <span className="sb-hint">{option.hint}</span>}
              </div>
            ))
            : null}
        </div>
      </div>
    </div>
  );

  useEffect(() => {
    selectedElementRef.current?.scrollIntoView({
      block: "nearest",
    });
  });

  return returnEl;
}
