"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.JSON_MODE = exports.CodeEditor = exports.CORNER_PLACEMENTS_TYPES = exports.CORNER_PLACEMENTS = exports.CODE_EDITOR_THEMES = exports.CODE_EDITOR_MODES = void 0;
var _react = _interopRequireDefault(require("react"));
var _classnames = _interopRequireDefault(require("classnames"));
var _hintWrapper = _interopRequireDefault(require("./utils/hint-wrapper"));
var _searchWrapper = require("./utils/search-wrapper");
var _useTranslation = require("../hooks/use-translation");
require("codemirror/mode/sql/sql");
require("codemirror/mode/javascript/javascript");
require("codemirror/mode/shell/shell");
require("codemirror/mode/python/python");
require("codemirror/mode/r/r");
require("codemirror/mode/clike/clike");
require("codemirror/mode/htmlmixed/htmlmixed");
require("codemirror/mode/yaml/yaml");
require("codemirror/mode/xml/xml");
require("codemirror/addon/hint/show-hint");
var _jsxRuntime = require("react/jsx-runtime");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
// Will add more modes with time
const CODE_EDITOR_MODES = exports.CODE_EDITOR_MODES = {
  SQL: 'text/x-sparksql',
  JS: 'javascript',
  SHELL: 'shell',
  HTML_MIXED: 'htmlmixed',
  PLAIN_TEXT: 'text/plain',
  PYTHON: 'python',
  R: 'r',
  JAVA: 'clike',
  YAML: 'yaml',
  XML: 'xml'
};
const CODE_EDITOR_THEMES = exports.CODE_EDITOR_THEMES = {
  DARK: 'dracula',
  LIGHT: 'default'
};
const JSON_MODE = exports.JSON_MODE = {
  name: CODE_EDITOR_MODES.JS,
  json: true
};
const DEFAULT_TAB_SIZE = 2;
let CORNER_PLACEMENTS_TYPES = exports.CORNER_PLACEMENTS_TYPES = /*#__PURE__*/function (CORNER_PLACEMENTS_TYPES) {
  CORNER_PLACEMENTS_TYPES["TOP_RIGHT"] = "top-right";
  CORNER_PLACEMENTS_TYPES["TOP_LEFT"] = "top-left";
  CORNER_PLACEMENTS_TYPES["BOTTOM_RIGHT"] = "bottom-right";
  CORNER_PLACEMENTS_TYPES["BOTTOM_LEFT"] = "bottom-left";
  return CORNER_PLACEMENTS_TYPES;
}({});
const CORNER_PLACEMENTS = exports.CORNER_PLACEMENTS = [CORNER_PLACEMENTS_TYPES.TOP_RIGHT, CORNER_PLACEMENTS_TYPES.TOP_LEFT, CORNER_PLACEMENTS_TYPES.BOTTOM_RIGHT, CORNER_PLACEMENTS_TYPES.BOTTOM_LEFT];

// https://codemirror.net/doc/manual.html
const DefaultOptions = {
  lineWrapping: true,
  tabSize: DEFAULT_TAB_SIZE,
  value: '',
  lineNumbers: true,
  indentWithTabs: true,
  smartIndent: true,
  matchBrackets: true,
  theme: CODE_EDITOR_THEMES.DARK
};

// We need this to ignore an error when defaultProps are defined but the linters throws an error

const readOnlyExtraKeys = {
  Tab: false
};
const readOnlyDefault = {};
const CodeEditor = props => {
  const {
    t
  } = (0, _useTranslation.useTranslation)();
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(CodeEditorBase, {
    textEditorAriaLabel: t('Code editor'),
    ...props
  });
};

/** Codemirror based code editor.
 * There is 'theme' property that forces theme schema e.g. light or dark,
 * Otherwise use CodeEditorThemed component instead that depends on user theme preference
 * */
exports.CodeEditor = CodeEditor;
class CodeEditorBase extends _react.default.Component {
  static defaultProps = {
    value: '',
    initialValue: '',
    onChange: () => {},
    onDestroy: () => {},
    testId: 'codemirror-textarea',
    className: '',
    hintList: [],
    autocomplete: false,
    options: {
      mode: CODE_EDITOR_MODES.JS,
      readOnly: false
    },
    enableSearch: false,
    getSearchQuery: () => {},
    quoteChar: '`'
  };
  constructor(props) {
    super(props);
    this.state = {
      value: props.value
    };
    this.isReadOnly = !!this.props.options?.readOnly;
    this.textareaNode = /*#__PURE__*/_react.default.createRef();
    this.cursorIndex = 0;
    this.codemirrorValueChanged = this.codemirrorValueChanged.bind(this);
    this.autocomplete = this.autocomplete.bind(this);
    this.insertLine = this.insertLine.bind(this);
    this.search = this.search.bind(this);
  }
  search() {
    return this.props.getSearchQuery?.();
  }
  async componentDidMount() {
    const {
      default: codeMirrorModule
    } = await Promise.resolve().then(() => _interopRequireWildcard(require('codemirror')));
    this.CodeMirrorModule = codeMirrorModule;
    if (!this.textareaNode.current) {
      return;
    }
    this.codeMirror = this.CodeMirrorModule.fromTextArea(this.textareaNode.current,
    // The types for CodeMirror seem to be wrong here
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    {
      ...DefaultOptions,
      ...this.props.options,
      readOnly: this.isReadOnly,
      screenReaderLabel: this.props.name || this.props.textEditorAriaLabel,
      extraKeys: this.isReadOnly ? readOnlyExtraKeys : readOnlyDefault
    });
    this.codeMirror.getDoc().setValue(this.props.value || this.props.initialValue || ''); // Set default value
    // set cursor to the end of initial text
    if (this.props.initialValue) {
      this.codeMirror.execCommand('goDocEnd');
    }
    this.codeMirror.on('change', this.codemirrorValueChanged);
    if (this.props.autocomplete) {
      (0, _hintWrapper.default)(this.CodeMirrorModule, this.props.options?.mode, this.props.hintList, this.props.enquoteDangerousHintNames, this.props.quoteChar);
      this.codeMirror.on('keyup', (cm, event) => this.autocomplete(cm, event));
    }
    if (this.props.enableSearch) {
      (0, _searchWrapper.InjectSearch)(this.CodeMirrorModule, this.search);
    }
    if (this.props.createApi) {
      this.props.createApi({
        cm: this.codeMirror,
        insertLine: this.insertLine
      });
    }
  }
  componentDidUpdate(prevProps) {
    if (prevProps.value !== this.props.value && this.state.value !== this.props.value) {
      this.codeMirror.getDoc().setValue(this.props.value || '');
      this.setState({
        value: this.props.value || ''
      });
      // This timeout is needed to refresh the CodeMirror content after the layout has
      // finished adjusting to the new value, otherwise it might only render part of it.
      setTimeout(() => {
        this.codeMirror.refresh();
      }, 1);
    }
    if (this.props.hintList !== prevProps.hintList && this.CodeMirrorModule) {
      (0, _hintWrapper.default)(this.CodeMirrorModule, this.props.options?.mode, this.props.hintList, this.props.enquoteDangerousHintNames, this.props.quoteChar);
    }
  }
  insertLine(cm, text) {
    const doc = cm.getDoc();
    const cursor = doc.getCursor();
    doc.replaceRange(text, cursor);
    cm.focus();
  }
  codemirrorValueChanged(doc, change) {
    if (change.origin !== 'setValue') {
      this.setState({
        value: doc.getValue()
      });
      this.props.onChange?.(doc.getValue());
    }
  }
  componentWillUnmount() {
    this.props.onDestroy?.();
    if (this.codeMirror) {
      this.codeMirror.toTextArea();
    }
  }
  autocomplete(cm, {
    key
  }) {
    if (!cm.state.completionActive && key.length === 1 && (!!this.props.delimiterCallback && /\./.test(key) || /[a-z0-9]/i.test(key))) {
      if (!!this.props.delimiterCallback) {
        this.props.delimiterCallback();
      }
      // The types for CodeMirror seem to be wrong here. "combined" is not defined
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      this.CodeMirrorModule.showHint(cm, this.CodeMirrorModule.hint.combined, {
        completeSingle: false
      });
    }
  }
  render() {
    const readOnlyAccessibilityAttr = {
      'aria-label': this.props.name || this.props.textEditorAriaLabel,
      'aria-readonly': true,
      tabIndex: 0,
      role: 'textbox'
    };
    return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
      className: "aligner-container",
      children: /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
        className: (0, _classnames.default)(`CodeMirrorWrapper ${this.props.className}`, {
          readonly: this.isReadOnly
        }),
        "test-id": "code-editor-wrapper",
        ...(this.isReadOnly ? readOnlyAccessibilityAttr : {}),
        children: [this.props.controls && this.props.controls.map(({
          element,
          placement,
          hide
        }, i) => !hide && /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
          className: (0, _classnames.default)('code-editor-control', placement),
          children: element
        }, i)), /*#__PURE__*/(0, _jsxRuntime.jsx)("textarea", {
          "aria-label": this.props.name || this.props.textEditorAriaLabel,
          tabIndex: this.isReadOnly ? -1 : 0,
          "test-id": this.props.testId,
          ref: this.textareaNode,
          name: this.props.name,
          defaultValue: this.props.value,
          autoComplete: "off",
          autoFocus: this.props.autoFocus
        })]
      })
    });
  }
}