import { useEffect, useRef, type ReactNode, type KeyboardEvent, useState } from "react";

import "./Input.css";

interface InputProps {
  placeholder: string;
  text: string;
  suggestion: string;
  onTextChange: (text: string) => void;

  onEnter: () => void;
  onCancel: () => void;
  extraHandlers: {[key:string]: (e: KeyboardEvent) => void}

  children?: ReactNode | ReactNode[];
}

const autoInputWidth = (ref: React.RefObject<HTMLInputElement| null>, text, emptyWidth=150) => {
  if (ref.current) {
    ref.current.style.width = `${text.length === 0 ? emptyWidth : (text.length*8.0)}px`;
  }
}

function getCaretPosition(ref: React.RefObject<HTMLInputElement| null>): number {
  return ref.current?.selectionStart || 0;
}

// Move caret all the way to the right in the currently focused element
function setRightMostCaretPosition(elem) {
  const range = document.createRange();
  range.selectNode(elem);
  if (elem.lastChild) { // if no text is entered, there is no lastChild
    range.setStart(elem.lastChild, elem.textContent.length);
    range.setEnd(elem.lastChild, elem.textContent.length);
    const selection = window.getSelection();
    selection?.removeAllRanges();
    selection?.addRange(range);
  }
}

function focusNextElement() {
  const editable = Array.from<any>(document.querySelectorAll('input.editable'));
  const index = editable.indexOf(document.activeElement);
  const nextElem = editable[index+1];
  if (nextElem) {
    nextElem.focus();
  }
}

function focusPrevElement() {
  const editable = Array.from<any>(document.querySelectorAll('input.editable'));
  const index = editable.indexOf(document.activeElement);
  const prevElem = editable[index-1]
  if (prevElem) {
    prevElem.focus();
    setRightMostCaretPosition(prevElem);
  }
}

export function Input({placeholder, text, suggestion, onTextChange, onEnter, onCancel, extraHandlers, children}: InputProps) {
  const ref = useRef<HTMLInputElement>(null);
  const [focus, setFocus] = useState<"yes"|"hide"|"no">("no");

  useEffect(() => autoInputWidth(ref, (text+(focus?suggestion:'')) || placeholder), [ref, text, suggestion, focus]);

  const onKeyDown = (e: KeyboardEvent) => {
    setFocus("yes");
    const keys = {
      // auto-complete
      Tab: () => {
        if (e.shiftKey) {
          focusPrevElement();
          e.preventDefault();
        }
        else {
          // not shift key
          if (suggestion.length > 0) {
            // complete with greyed out text
            const newText = text + suggestion;
            onTextChange(newText);
            setRightMostCaretPosition(ref.current);
            e.preventDefault();
          }
          else {
            onEnter();
            e.preventDefault();
          }
        }
      },
      Enter: () => {
        onEnter();
        e.preventDefault();
      },

      // cancel
      Backspace: () => {
        if (text.length === 0) {
          onCancel();
          focusPrevElement();
          e.preventDefault();
        }
      },

      // navigate with arrows
      ArrowLeft: () => {
        if (getCaretPosition(ref) <= 0) {
          focusPrevElement();
          e.preventDefault();
        }
      },
      ArrowRight: () => {
        if (getCaretPosition(ref) === text.length) {
          focusNextElement();
          e.preventDefault();
        }
      },

      Escape: () => {
        if (focus === "yes") {
          setFocus("hide");
          e.preventDefault();
        }
      },

      ...extraHandlers,
    };
    const handler = keys[e.key];
    if (handler) {
      handler(e);
    }
  };

  return <span className="textboxContainer">
    <span className="textbox hint">{text}{focus && suggestion}</span>
    <input ref={ref}
      placeholder={placeholder}
      className="textbox"
      value={text}
      onInput={(e) =>
        // @ts-ignore
        onTextChange(e.target.value)}
      onKeyDown={onKeyDown}
      onFocus={() => setFocus("yes")}
      onBlur={() => setFocus("no")}
      spellCheck={false}
      />
    {(focus === "yes") && children}
    </span>;
}
