import { Double, getType, Int, newDynamic, trie } from "dope2";
import { autoInputWidth, focusNextElement, focusPrevElement, setRightMostCaretPosition } from "./util/dom_trickery";
import { parseDouble, parseInt } from "./util/parse";

import { useContext, useEffect, useMemo, useRef, useState } from "react";
import { Type } from "./Type";

import "./InputBlock.css";
import type { Dynamic, State2Props } from "./util/extra";
import { EnvContext } from "./EnvContext";

export interface InputBlockState {
  kind: "input";
  text: string;
  resolved: undefined | Dynamic;
  focus: boolean
}

interface InputBlockProps extends State2Props<InputBlockState> {
  filter: (suggestion: [string, Dynamic]) => boolean;
  onCancel: () => void;
}

const computeSuggestions = (text, env, filter) => {
  const asDouble = parseDouble(text);
  const asInt = parseInt(text);

  const ls = [
    ... (asDouble ? [[asDouble.toString(), newDynamic(asDouble)(Double)]] : []),
    ... (asInt ? [[asInt.toString(), newDynamic(BigInt(asInt))(Int)]] : []),
    ... trie.suggest(env.name2dyn)(text)(Infinity),
  ]
  return ls;
  return [
    ...ls.filter(filter), // ones that match filter come first
    ...ls.filter(s => !filter(s)),
  ]
  // .slice(0,30);
}

export function InputBlock({ state, setState, filter, onCancel }: InputBlockProps) {
  const {text, resolved, focus} = state;
  const env = useContext(EnvContext);
  const inputRef = useRef<HTMLInputElement>(null);
  const [i, setI] = useState(0); // selected suggestion idx
  const [haveFocus, setHaveFocus] = useState(false); // whether to render suggestions or not

  const singleSuggestion = trie.growPrefix(env.name2dyn)(text);
  const suggestions = useMemo(() => computeSuggestions(text, env, filter), [text]);

  useEffect(() => autoInputWidth(inputRef, text), [inputRef, text]);

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

  useEffect(() => {
    if (suggestions.length >= i) {
      setI(0);
    }
  }, [suggestions.length]);

  const getCaretPosition = () => {
    return inputRef.current?.selectionStart || -1;
  }

  const onTextChange = newText => {
    const found = trie.get(env.name2dyn)(newText);
    setState(state => ({...state, text: newText, resolved: found}));
  }

  // fired before onInput
  const onKeyDown = (e: React.KeyboardEvent) => {
    const fns = {
      Tab: () => {
        if (e.shiftKey) {
          focusPrevElement();
          e.preventDefault();
        }
        else {
          // not shift key
          if (singleSuggestion.length > 0) {
            const newText = text + singleSuggestion;
            onTextChange(newText);
            setRightMostCaretPosition(inputRef.current);
            e.preventDefault();  
          }
          else {
            onSelectSuggestion(suggestions[i]);
            e.preventDefault();
          }
        }
      },
      ArrowDown: () => {
        setI((i + 1) % suggestions.length);
        e.preventDefault();
      },
      ArrowUp: () => {
        setI((suggestions.length + i - 1) % suggestions.length);
        e.preventDefault();  
      },
      ArrowLeft: () => {
        if (getCaretPosition() <= 0) {
          focusPrevElement();
          e.preventDefault();
        }
      },
      ArrowRight: () => {
        if (getCaretPosition() === text.length) {
          focusNextElement();
          e.preventDefault();
        }
      },
      Enter: () => {
        onSelectSuggestion(suggestions[i]);
        e.preventDefault();
      },
      Backspace: () => {
        if (text.length === 0) {
          onCancel();
          e.preventDefault();
        }
      }
    };
    fns[e.key]?.();
  };

  const onInput = e => {
    onTextChange(e.target.value);
  };

  const onSelectSuggestion = ([name, dynamic]) => {
    setState(state => ({...state, text: name, resolved: dynamic}));
  };

  return <span>
    <span className="">
      {/* Dropdown suggestions */}
      {haveFocus &&
        <span style={{display:'inline-block'}}>
        <Suggestions
          suggestions={suggestions}
          onSelect={onSelectSuggestion}
          i={i} setI={setI} />
        </span>
      }
      {/* Input box */}
      <input ref={inputRef}
        placeholder="<name or literal>"
        className="editable"
        value={text}
        onInput={onInput}
        onKeyDown={onKeyDown}
        onFocus={() => setHaveFocus(true)}
        onBlur={() => setHaveFocus(false)}
        spellCheck={false}/>
      {/* Single 'grey' suggestion */}
      <span className="text-block suggest">{singleSuggestion}</span>
      { resolved && <>&#x2611;</>}
    </span>
  </span>;
}

function Suggestions({ suggestions, onSelect, i, setI }) {
  const onMouseEnter = j => () => {
    setI(j);
  };
  const onMouseDown = j => () => {
    setI(j);
    onSelect(suggestions[i]);
  };
  return <>{(suggestions.length > 0) &&
    <div className={"suggestions"}>
      {suggestions.map(([name, dynamic], j) =>
        <div
          key={`${j}_${name}`}
          className={(i === j ? " selected" : "")}
          onMouseEnter={onMouseEnter(j)}
          onMouseDown={onMouseDown(j)}>
            {name} :: <Type type={getType(dynamic)} />
        </div>)}
    </div>
  }</>;
}