import { useContext } from "react";

import { EnvContext } from "./EnvContext";
// import { addFocusRightMost, evalCallBlock2, evalExprBlock, recomputeTypeVarsForEnv, scoreResolved, type Environment, type ResolvedType } from "./eval";
import { ExprBlock, type ExprBlockState, type SetStateFn, type State2Props } from "./ExprBlock";
import { GlobalContext } from "./GlobalContext";
import { Value } from "./Value";

import { getActions } from "./actions";
import "./CallBlock.css";
import { CallContext } from "./CallContext";
import { inferType, inferTypeCall, type Environment } from "./infer_type";
import { Type } from "./Type";

export interface CallBlockState {
  kind: "call";
  fn: ExprBlockState;
  input: ExprBlockState;
}

export interface CallBlockProps<
  FnState=ExprBlockState,
  InputState=ExprBlockState,
> extends State2Props<CallBlockState,ExprBlockState> {}

function nestedFnProperties({state, setState, score}: CallBlockProps, env: Environment) {
  const setFn = (callback: SetStateFn) => {
    setState(state => ({...state, fn: callback(state.fn)}));
  };
  const onFnCancel = () => {
    setState(state => state.input); // we become our input
  };
  const scoreFn = (fnSuggestion: ExprBlockState) => {
    return score({
      kind: "call",
      fn: fnSuggestion,
      input: state.input,
    });
  };
  return {state: state.fn, setState: setFn, onCancel: onFnCancel, score: scoreFn};
}

function nestedInputProperties({state, setState, score}: CallBlockProps, env: Environment) {
  const setInput = (callback: SetStateFn) => {
    setState(state => ({...state, input: callback(state.input)}));
  };
  const onInputCancel = () => {
    setState(state => /*addFocusRightMost*/(state.fn)); // we become our function
  };
  const scoreInput = (inputSuggestion: ExprBlockState) => {
    return score({
      kind: "call",
      fn: state.fn,
      input: inputSuggestion,
    });
  };
  return {state: state.input, setState: setInput, onCancel: onInputCancel, score: scoreInput};
}

// function computePriority(fn: ResolvedType, input: ResolvedType, outPriority: (s: ResolvedType) => number, env) {
//   // dirty, but works:
//   const [fnR, env2] = recomputeTypeVarsForEnv('<fn>', fn, env);
//   const [inR, env3] = recomputeTypeVarsForEnv('<in>', input, env2);
//   const [resolved] = evalCallBlock2(fnR, inR, env3);
//   const score = scoreResolved(resolved, outPriority);
//   return score;
// }

export function CallBlock(props: CallBlockProps) {
  const env = useContext(EnvContext);
  const globalContext = useContext(GlobalContext);
  const addParam = getActions(globalContext, props.setState).c;
  // const [resolved] = evalExprBlock(props.state, env);
  // return <span className={"functionBlock" + ((resolved.kind === "error") ? " unifyError" : "")}>
  const typeInfo = inferTypeCall(props.state, env);
  return <span className={"functionBlock"}>
    <CallContext value={{addParam}}>
      <FunctionHeader {...props} addParam={addParam} />
      <div className="functionParams">
        <div className="outputParam">
          {/* Sequence of input parameters */}
          <InputParams
            {...props}
            depth={0}
            // errorDepth={(resolved.kind === "error") ? (resolved.depth) : -1}
            errorDepth={-1}
            addParam={addParam}
            />
          {/* {  (resolved.kind === "error") && resolved.e.toString()
          || (resolved.kind === "value") && <Value dynamic={resolved} />
          || "unknown" } */}
          :: <Type type={typeInfo.type} />
        </div>
      </div>
    </CallContext>
  </span>;
}

function FunctionHeader(props) {
  const env = useContext(EnvContext);
  const globalContext = useContext(GlobalContext);
  const nestedProperties = nestedFnProperties(props, env);
  if (props.state.fn.kind === "call" && globalContext?.syntacticSugar) {
    // if the function we're calling is itself the result of a function call,
    // then we are anonymous, and so we don't draw a function name
    return <FunctionHeader {...nestedProperties} />;
  }
  else {
    //  end of recursion - draw function name
    return <span className="functionName">
      &nbsp;&#119891;&#119899;&nbsp;
      <ExprBlock {...nestedProperties} />
    </span>;
  }
}

function InputParams({ depth, errorDepth, ...rest }) {
  const env = useContext(EnvContext);
  const globalContext = useContext(GlobalContext);
  const isOffending = depth === errorDepth;
  return <div className={"inputParam" + (isOffending ? " offending" : "")}>
    {rest.state.fn.kind === "call"
      && globalContext?.syntacticSugar
      && <InputParams
        {...nestedFnProperties(rest as CallBlockProps, env)}
        depth={depth+1}
        errorDepth={errorDepth}
        />}
    {/* Our own input param */}
    <EnvContext value={inferTypeCall(rest.state, env).inputEnv}>
      <ExprBlock
        {...nestedInputProperties(rest as CallBlockProps, env)}
      />
    </EnvContext>
  </div>;
}
