import { AppState, ContentRef, selectors, actions } from "@nteract/core";
import CodeMirrorEditor from "@nteract/editor";
import { createConfigCollection, createDeprecatedConfigOption, defineConfigOption, HasPrivateConfigurationState } from "@nteract/mythic-configuration";
import { connect } from "react-redux";
import { Dispatch } from "redux";

const codeMirrorConfig = createConfigCollection({
  key: "codeMirror",
});

createDeprecatedConfigOption({
  key: "cursorBlinkRate",
  changeTo: (value: number) => ({
    "codeMirror.cursorBlinkRate": value,
  }),
});

const BOOLEAN = [
  { label: "Yes", value: true },
  { label: "No", value: false },
];

defineConfigOption({
  label: "Blink Editor Cursor",
  key: "codeMirror.cursorBlinkRate",
  values: [
    { label: "Yes", value: 530 },
    { label: "No", value: 0 },
  ],
  defaultValue: 0,
});

defineConfigOption({
  label: "Show Cursor When Selecting",
  key: "codeMirror.showCursorWhenSelecting",
  values: BOOLEAN,
  defaultValue: false,
});

defineConfigOption({
  label: "Close Brackets Automatically",
  key: "codeMirror.autoCloseBrackets",
  values: BOOLEAN,
  defaultValue: false,
});

defineConfigOption({
  label: "Show Matching Brackets",
  key: "codeMirror.matchBrackets",
  values: BOOLEAN,
  defaultValue: true,
});

defineConfigOption({
  label: "Use Smart Indent",
  key: "codeMirror.smartIndent",
  values: BOOLEAN,
  defaultValue: true,
});

defineConfigOption({
  label: "Tab Size",
  key: "codeMirror.tabSize",
  values: [
    { label: "2 Spaces", value: 2 },
    { label: "3 Spaces", value: 3 },
    { label: "4 Spaces", value: 4 },
  ],
  defaultValue: 4,
});

defineConfigOption({
  label: "Show Line Numbers",
  key: "codeMirror.lineNumbers",
  values: BOOLEAN,
  defaultValue: false,
});

const markdownMode = {
  name: "gfm",
  tokenTypeOverrides: {
    emoji: "emoji",
  },
};

const rawMode = {
  name: "text/plain",
  tokenTypeOverrides: {
    emoji: "emoji",
  },
};

interface ComponentProps {
  id: string;
  contentRef: ContentRef;
  editorType: string;
}

interface DispatchProps {
  focusAbove: () => void;
  focusBelow: () => void;
}

const makeMapStateToProps = (state: AppState & HasPrivateConfigurationState, ownProps: ComponentProps) => {
  const { id, contentRef } = ownProps;
  const mapStateToProps = (state: AppState & HasPrivateConfigurationState) => {
    let mode = rawMode;
    let lineWrapping = true;

    const model = selectors.model(state, { contentRef });
    const kernel = selectors.kernelByContentRef(state, { contentRef });

    if (model && model.type === "notebook") {
      const cell = selectors.notebook.cellById(model, { id });
      if (cell) {
        switch (cell.cell_type) {
          case "markdown":
            mode = markdownMode;
            break;
          case "code":
            lineWrapping = false;
            mode =
              kernel?.info?.codemirrorMode ||
              selectors.notebook.codeMirrorMode(model);
            break;
          default:
            mode = rawMode;
            break;
        }
      }
    }

    // FIXME: The type for mode is wrong; it can also be a string or a Map at
    //        this point! Hence:
    // tslint:disable-next-line:strict-type-predicates
    mode = typeof mode === "object" && "toJS" in mode
      ? (mode as any).toJS()
      : mode;

    const codeMirror = {
      mode,
      ...codeMirrorConfig(state as any),
    };

    return {
      mode,
      codeMirror,
      lineWrapping,
      tip: true,
      completion: true,
    };
  };
  return mapStateToProps;
};

export const makeMapDispatchToProps = (
  initialDispatch: Dispatch,
  ownProps: ComponentProps
) => {
  const { id, contentRef } = ownProps;
  const mapDispatchToProps = (dispatch: Dispatch) => {
    return {
      focusBelow: () => {
        dispatch(actions.focusNextCell({ id, contentRef, createCellIfUndefined: true }));
        dispatch(actions.focusNextCellEditor({ id, contentRef }));
      },
      focusAbove: () => {
        dispatch(actions.focusPreviousCell({ id, contentRef }));
        dispatch(actions.focusPreviousCellEditor({ id, contentRef }));
      }
    }
  };
  return mapDispatchToProps;
};

export default connect(makeMapStateToProps, makeMapDispatchToProps)(CodeMirrorEditor);
