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

import { Double, getType, Int, newDynamic, prettyT, trie } from "dope2";

import { EnvContext } from "./EnvContext";
import type { Dynamic } from "./eval";
import "./InputBlock.css";
import { Type } from "./Type";
import type { State2Props } from "./Editor";
import { autoInputWidth, focusNextElement, focusPrevElement, setRightMostCaretPosition } from "./util/dom_trickery";
import { parseDouble, parseInt } from "./util/parse";

interface Literal {
  kind: "literal";
  type: string; // todo: store (and serialize) real type
};
interface Name {
  kind: "name";
}
interface Text {
  kind: "text";
}
export type InputValueType = Literal | Name | Text;

export interface InputBlockState {
  kind: "input";
  text: string;
  value: InputValueType;
  focus: boolean
}

export type SuggestionType = ['literal'|'name', string, Dynamic];
export type PrioritizedSuggestionType = [number, ...SuggestionType];

interface InputBlockProps extends State2Props<InputBlockState> {
  suggestionPriority: (suggestion: SuggestionType) => number;
  onCancel: () => void;
}

const computeSuggestions = (text, env, suggestionPriority: (s: SuggestionType) => number): PrioritizedSuggestionType[] => {
  const asDouble = parseDouble(text);
  const asInt = parseInt(text);

  const ls = [
    ... (asDouble ? [["literal", asDouble.toString(), newDynamic(asDouble)(Double)]] : []),
    ... (asInt ? [["literal", asInt.toString(), newDynamic(BigInt(asInt))(Int)]] : []),
    ... trie.suggest(env.name2dyn)(text)(Infinity).map(([name,type]) => ["name", name, type]),
  ]
  // return ls;
  return ls
    .map(suggestion => [suggestionPriority(suggestion), ...suggestion] as PrioritizedSuggestionType)
    .sort(([priorityA], [priorityB]) => priorityB - priorityA)
}

export function InputBlock({ state, setState, suggestionPriority, onCancel }: InputBlockProps) {
  const {text, 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, suggestionPriority), [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}));
  }

  // 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 = ([priority, kind, name, dynamic]: PrioritizedSuggestionType) => {
    if (kind === "literal") {
      setState(state => ({
        ...state,
        text: name,
        value: {kind, type: prettyT(getType(dynamic))},
      }));
    }
    else {
      setState(state => ({
        ...state,
        text: name,
        value: {kind},
      }))
    }
  };

  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>
    </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(([priority, kind, name, dynamic], j) =>
        <div
          key={`${j}_${name}`}
          className={(i === j ? " selected" : "")}
          onMouseEnter={onMouseEnter(j)}
          onMouseDown={onMouseDown(j)}>
            ({priority}) ({kind}) {name} :: <Type type={getType(dynamic)} />
        </div>)}
    </div>
  }</>;
}