/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable import/first */
/* eslint-disable import/extensions */
/* eslint no-use-before-define: 0 no-unused-vars: 1 */
import React, { Component } from 'react';
import { Controlled as CodeMirror } from 'react-codemirror2';
import PropTypes from 'prop-types';
import _ from 'lodash';

import { getOptions } from './codeMirror/helpers';

/******** File extensions necessary *********/
import 'codemirror/lib/codemirror.css';
import 'codemirror/theme/material.css';
import 'codemirror/mode/javascript/javascript.js';
import 'codemirror/mode/xml/xml.js';
import 'codemirror/mode/markdown/markdown.js';
import 'codemirror/addon/lint/lint.js';
import 'codemirror/addon/comment/comment.js';
import 'codemirror/addon/edit/closebrackets.js';
import 'codemirror/keymap/sublime.js';

import './codeMirror/addon/lint/eslint/eslint-lint.js';
import './Editor.css'; // Order Important


class Editor extends Component {
  constructor(props) {
    super(props);
    this.state = {
      code: this.props.code,
    };
    this.handleReset = this.handleReset.bind(this);
    this.handlePaste = this.handlePaste.bind(this);
    this.updateCode = this.updateCode.bind(this);
    this.init = this.init.bind(this);
  }

  // Using CWM because options must be defined before component mounts
  componentWillMount() {
    this.init();
  }

  componentWillReceiveProps({ code: newCode }) {
    const { code: prevCode } = this.props;
    if (prevCode !== newCode) {
      this.setState({ code: newCode });
    }
  }

  init() {
    const debounceWait = 300;
    const { onValidCode, onLintErrors } = this.props;
    const handleValidCode = (annotationsNotSorted, annotations, cmInstance) => {
      const cleanCode = cmInstance.getValue();
      onValidCode(cleanCode);
      onLintErrors([]);
    };
    const handleLintErrors = errors => onLintErrors(_.uniq(_.map(errors, e => e.from.line + 1)));

    this.options = getOptions(
      {},
      _.debounce(handleValidCode, debounceWait),
      _.debounce(handleLintErrors, debounceWait)
    );
  }

  handleReset() {
    this.props.handleReset();
    this.setState({ code: this.props.code });
  }

  handlePaste(_cm, event) {
    this.props.onPaste(event);
  }

  // Wrapper to minimize breaking onChange API change on move to react-codemirror2
  updateCode(editor, metadata, newCode) {
    const { code: currCode } = this.state;
    if (newCode !== currCode) {
      this.props.onAnyCode(newCode);
    }
    this.setState({ code: newCode });
  }

  render() {
    const { showLints } = this.props;
    const { code } = this.state;

    if (showLints) {
      require('codemirror/addon/lint/lint.css');
    }

    return (
      <div id="code-tester-editor">
        <CodeMirror
          value={code}
          className="ReactCodeMirror"
          onBeforeChange={this.updateCode}
          onPaste={this.handlePaste}
          options={this.options}
        />
      </div>
    );
  }
}

Editor.propTypes = {
  code: PropTypes.string,
  handleReset: PropTypes.func,
  onValidCode: PropTypes.func,
  onLintErrors: PropTypes.func,
  onAnyCode: PropTypes.func,
  onPaste: PropTypes.func,
  showLints: PropTypes.bool,
};

Editor.defaultProps = {
  code: '// Your code here:',
  showLints: true,
  onValidCode: (lintedCode) => { },  // eslint-disable-line no-unused-vars
  onAnyCode: (code) => { }, // eslint-disable-line no-unused-vars
  onLintErrors: () => { },
  handleReset: () => { console.warn('No handleRest function provided.'); },
  onPaste: () => console.warn('No onPaste function provided.'),
};

export default Editor;
