import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { js_beautify, css_beautify } from 'js-beautify';
import MonacoEditor from 'react-monaco-editor';
import classNames from 'classnames';

import { Icon, Message } from '@alife/next';
import ObjectButton from '@ali/iceluna-comp-object-button';
import FormItem from '@ali/iceluna-comp-form/lib/item';
import { serialize, jsonuri, generateI18n } from '@ali/iceluna-sdk/lib/utils';
import localeConfig from '@ali/iceluna-sdk/lib/hoc/localeConfig';

import Snippets from './locale/snippets';
import zhCN from './locale/zh-CN';
import './index.scss';

let registerApiAndSnippetStatus = false; //判断注册api机制

window.bt = js_beautify;
class MonacoEditorView extends PureComponent {
  static displayName = 'MonacoEditor';
  render() {
    const { type, ...restProps } = this.props;
    let Node = type == 'button' ? MonacoEditorButtonView : MonacoEditorDefaultView;
    return <Node {...restProps} registerApi={apis => Object.assign(this, apis)} />;
  }
}

localeConfig('MonacoEditor', MonacoEditorView);

//monaco编辑器存在3种主题：vs、vs-dark、hc-black
class MonacoEditorDefaultView extends PureComponent {
  static displayName = 'MonacoEditorDefault';
  static propTypes = {
    locale: PropTypes.string,
    messages: PropTypes.object
  };
  static defaultProps = {
    locale: 'zh-CN',
    messages: zhCN,
    width: '100%',
    height: '300px',
    language: 'javascript',
    autoFocus: false, //自动获得焦点
    autoSubmit: true, //自动提交
    placeholder: '', //默认占位内容
    btnText: '提交',
    btnSize: 'small',
    rules: [], //校验规则
    options: {
      readOnly: false,
      automaticLayout: true,
      folding: true, //默认开启折叠代码功能
      lineNumbers: 'on',
      wordWrap: 'off',
      formatOnPaste: true,
      fontSize: 12,
      tabSize: 2,
      scrollBeyondLastLine: false,
      fixedOverflowWidgets: false,
      snippetSuggestions: 'top',
      minimap: {
        enabled: true
      },
      scrollbar: {
        vertical: 'hidden',
        horizontal: 'hidden',
        verticalScrollbarSize: 0
      }
    }
  };
  strValue: string;
  i18n: any;
  editorRef: React.RefObject<unknown>;
  options: any;
  fullScreenOptions: any;
  position: any;
  editor: any;
  editorNode: unknown;
  editorParentNode: any;
  constructor(props: Readonly<{}>) {
    super(props);
    this.strValue = '';
    this.i18n = generateI18n(props.locale, props.messages);
    this.editorRef = React.createRef();
    this.options = Object.assign({}, MonacoEditorDefaultView.defaultProps.options, props.options);
    this.fullScreenOptions = {
      ...this.options,
      lineNumbers: 'on',
      folding: true,
      scrollBeyondLastLine: true,
      minimap: {
        enabled: true
      }
    };
    this.state = {
      isFullScreen: false
    };
    this.onChange = this.onChange.bind(this);
    this.onSubmit = this.onSubmit.bind(this);
    this.fullScreen = this.fullScreen.bind(this);
    this.format = this.format.bind(this);
  }

  componentDidUpdate() {
    //如果是全屏操作，获得焦点，光标保留在原来位置;
    if (this.position) {
      this.editor.focus();
      this.editor.setPosition(this.position);
      delete this.position;
    }
  }
  componentDidMount() {
    this.editorNode = this.editorRef.current; //记录当前dom节点；
    this.editorParentNode = this.editorNode.parentNode; //记录父节点;
    //自动获得焦点, 格式化需要时间
    if (this.props.autoFocus) {
      setTimeout(() => {
        this.editor.setPosition({
          column: 4,
          lineNumber: 2
        });
        this.editor.focus();
      }, 100);
    }
    //快捷键编码
    let CtrlCmd = 2048;
    let KEY_S = 49;
    let Shift = 1024;
    let KEY_F = 36;
    let KEY_B = 32;
    let Escape = 9;

    this.editor.addCommand(CtrlCmd | KEY_S, () => {
      this.onSubmit(); //保存快捷键
    });
    this.editor.addCommand(CtrlCmd | Shift | KEY_F, () => {
      this.fullScreen(); //全屏快捷键
    });
    this.editor.addCommand(CtrlCmd | KEY_B, () => {
      this.format(); //美化快捷键
    });
    this.editor.addCommand(Escape, () => {
      this.props.onEscape && this.props.onEscape();
    });
    //注册api
    this.editor.submit = this.onSubmit;
    this.editor.format = this.format;
    this.editor.fullScreen = this.fullScreen;
    this.editor.toJson = this.toJson;
    this.editor.toObject = this.toObject;
    this.editor.toFunction = this.toFunction;
    //针对object情况，改写setValue和getValue api
    if (this.props.language === 'object') {
      let getValue = this.editor.getValue;
      let setValue = this.editor.setValue;
      this.editor.getValue = () => {
        return getValue.call(this.editor).substring(this.valuePrefix.length);
      };
      this.editor.setValue = value => {
        return setValue.call(this.editor, [this.valuePrefix + value]);
      };
    }
  }

  render() {
    const {
      value,
      placeholder,
      style,
      className,
      width,
      height,
      language,
      theme,
      editorWillMount,
      editorDidMount,
      registerApi
    } = this.props;

    const { isFullScreen } = this.state;
    this.valuePrefix = ''; //值前缀
    if (language === 'object') this.valuePrefix = 'export default ';
    if (!this.isFullScreenAction) {
      //将值转换成目标值
      let nowValue = this.valueHandler(value || placeholder, language);
      let curValue = this.valueHandler(this.strValue, language);
      if (nowValue !== curValue) this.strValue = nowValue;
      if (language === 'object') this.strValue = this.strValue || placeholder || '{\n\t\n}'; //设置初始化值
      if (language === 'json' && this.strValue === '{}') this.strValue = '{\n\t\n}';
    }
    this.isFullScreenAction = false;
    //真实高亮语言
    let tarLanguage = language;
    if (language === 'object' || language === 'function') {
      tarLanguage = 'javascript';
    }
    let classes = classNames('monaco-editor-wrap', {
      ['monaco-fullscreen']: !!isFullScreen,
      ['monaco-nofullscreen']: !isFullScreen
    });
    let tarStyle = Object.assign({ minHeight: 60, width, height }, style);
    return (
      <div className={className} style={tarStyle}>
        <div ref={this.editorRef} style={{ height: '100%' }} className={classes}>
          <MonacoEditor
            value={this.valuePrefix + this.strValue}
            width="100%"
            height="300"
            language={tarLanguage}
            theme={theme || window.__monacoTheme || 'vs-dark'}
            options={isFullScreen ? this.fullScreenOptions : this.options}
            onChange={this.onChange}
            editorWillMount={editorWillMount}
            editorDidMount={(editor, monaco) => {
              this.editor = editor;
              registerApi({ editor });
              this.registerApiAndSnippet(monaco);
              editorDidMount && editorDidMount.call(this, arguments);
            }}
          />
          <a
            onClick={this.fullScreen}
            className="monaco_fullscreen_icon"
            title={
              isFullScreen ? `${this.i18n('cancelFullScreen')} cmd+shift+f` : `${this.i18n('fullScreen')} cmd+shift+f`
            }
          >
            <Icon type={isFullScreen ? 'quxiaoquanping' : 'quanping'} />
          </a>
        </div>
      </div>
    );
  }

  //值变化
  onChange(curValue) {
    if (curValue === this.valuePrefix + this.strValue) return;
    const { onAfterChange, language, autoSubmit, onChange } = this.props;
    this.strValue = curValue; //记录当前格式
    if (this.ct) clearTimeout(this.ct);
    this.ct = setTimeout(() => {
      this.position = this.editor.getPosition();
      let ret = this.resultHandler(curValue, language);
      if (autoSubmit) onChange && onChange(ret.value);
      onAfterChange && onAfterChange(ret.value, ret.error, this.editor);
    }, 300);
  }

  //提交动作
  onSubmit() {
    const { onSubmit, onChange, language } = this.props;
    let curValue = this.editor.getValue();
    let ret = this.resultHandler(curValue, language);
    if (!ret.error) onChange && onChange(ret.value);
    onSubmit && onSubmit(ret.value, ret.error, this.editor);
  }

  //值类型转换处理
  valueHandler(value, language) {
    let tarValue = value || '';
    if (language === 'json') {
      if (value && typeof value === 'object') {
        tarValue = JSON.stringify(value, null, 2);
      } else if (value && typeof value === 'string') {
        try {
          let ret = this.toJson(value);
          if (!ret.error) tarValue = JSON.stringify(ret.value, null, 2);
        } catch (err) {}
      }
    } else if (language === 'function') {
      if (typeof value === 'function') {
        tarValue = value.toString();
      }
      if (tarValue && typeof tarValue === 'string') {
        tarValue = js_beautify(tarValue, { indent_size: 2, indent_empty_lines: true });
      }
    } else if (language === 'object') {
      //先转成对象，在进行序列化和格式化；
      value = value || {};
      if (value && typeof value === 'object') {
        try {
          tarValue = serialize(value, { unsafe: true });
          tarValue = js_beautify(tarValue, { indent_size: 2, indent_empty_lines: true });
        } catch (err) {}
      } else if (typeof value === 'string') {
        try {
          let ret = this.resultHandler(value, 'object');
          tarValue = ret.error ? ret.value : serialize(ret.value, { unsafe: true });
          tarValue = js_beautify(tarValue, { indent_size: 2, indent_empty_lines: true });
        } catch (err) {}
      }
    }
    return tarValue;
  }

  //结果处理
  resultHandler(value, language) {
    let ret = { value };
    if (language === 'json') {
      ret = this.toJson(value);
    } else if (language === 'object') {
      ret = this.toObject(value);
    } else if (language === 'function') {
      ret = this.toFunction(value);
    }
    return ret;
  }

  //设置全屏时的动作
  fullScreen() {
    if (!this.editorRef) return;
    //还原到原来位置；
    this.position = this.editor.getPosition();
    if (this.state.isFullScreen) {
      if (this.editorParentNode) {
        if (this.editorParentNode.firstChild) {
          this.editorParentNode.insertBefore(this.editorNode, this.editorParentNode.firstChild);
        } else {
          this.editorParentNode.appendChild(this.editorNode);
        }
      }
    } else {
      document.body.appendChild(this.editorNode);
    }
    let nextFs = !this.state.isFullScreen;
    this.isFullScreenAction = true; //记录是全屏幕操作
    this.setState(
      {
        isFullScreen: nextFs
      },
      () => {
        this.editor.updateOptions(nextFs ? this.fullScreenOptions : this.options);
      }
    );
  }

  //美化代码
  format() {
    if (!this.editor) return;
    if (/^\$_obj?\{.*?\}$/m.test(this.editor.getValue())) return;
    if (this.props.language === 'json' || this.props.language === 'object' || this.props.language === 'function') {
      let tarValue = js_beautify(this.editor.getValue(), { indent_size: 2 });
      this.editor.setValue(tarValue);
    } else if (this.props.language === 'less' || this.props.language === 'css' || this.props.language === 'scss') {
      let tarValue = css_beautify(this.editor.getValue(), { indent_size: 2 });
      this.editor.setValue(tarValue);
    } else {
      this.editor.getAction('editor.action.formatDocument').run();
    }
  }

  //校验是否是json
  toJson(value) {
    try {
      let obj = new Function(`'use strict'; return ${value.replace(/[\r\n\t]/g, '')}`)();
      if (typeof obj === 'object' && obj) {
        let tarValue = new Function(`'use strict'; return ${value}`)();
        return { value: JSON.parse(JSON.stringify(tarValue)) };
      }
      return { error: this.i18n('jsonIllegal'), value };
    } catch (err) {
      return { error: err, value };
    }
  }

  //校验是否为object对象
  toObject(value) {
    try {
      let obj = new Function(`'use strict';return ${value}`)();
      if (obj && typeof obj === 'object') {
        if (jsonuri.isCircular(obj)) return { error: this.i18n('circularRef'), value };
        return { value: obj };
      } else {
        return { error: this.i18n('objectIllegal'), value };
      }
    } catch (err) {
      return { error: err, value };
    }
  }

  //校验是否为function
  toFunction(value) {
    try {
      let fun = new Function(`'use strict';return ${value}`)();
      if (fun && typeof fun === 'function') {
        return { value: fun };
      } else {
        return { error: this.i18n('functionIllegal'), value };
      }
    } catch (err) {
      return { error: err, value };
    }
  }

  //注册api和代码片段
  registerApiAndSnippet(monaco) {
    if (registerApiAndSnippetStatus) return;
    registerApiAndSnippetStatus = true;
    //注册this.提示的方法;
    let thisSuggestions = [];
    Snippets.map(item => {
      if (!item.label || !item.kind || !item.insertText) return;
      let tarItem = Object.assign(item, {
        label: item.label,
        kind: monaco.languages.CompletionItemKind[item.kind],
        insertText: item.insertText
      });
      if (item.insertTextRules)
        tarItem.insertTextRules = monaco.languages.CompletionItemInsertTextRule[item.insertTextRules];
      thisSuggestions.push(tarItem);
    });
    monaco.languages.registerCompletionItemProvider('javascript', {
      provideCompletionItems: (model, position) => {
        let textUntilPosition = model.getValueInRange({
          startLineNumber: position.lineNumber,
          startColumn: 1,
          endLineNumber: position.lineNumber,
          endColumn: position.column
        });
        let match = textUntilPosition.match(/(^this\.)|(\sthis\.)/);
        let suggestions = match ? thisSuggestions : [];
        return { suggestions: suggestions };
      },
      triggerCharacters: ['.']
    });
  }
}
const prefix = 'data:text/javascript;charset=utf-8,';
const baseUrl = 'https://g.alicdn.com/iceluna/iceluna-vendor/0.0.1/';
window.MonacoEnvironment = {
  getWorkerUrl: function(label: string) {
    if (label === 'json') {
      return `${prefix}${encodeURIComponent(`
        importScripts('${baseUrl}json.worker.js');`)}`;
    }
    if (['css', 'less', 'scss'].includes(label)) {
      return `${prefix}${encodeURIComponent(`
        importScripts('${baseUrl}css.worker.js');`)}`;
    }
    if (label === 'html') {
      return `${prefix}${encodeURIComponent(`
        importScripts('${baseUrl}html.worker.js');`)}`;
    }
    if (['typescript', 'javascript'].includes(label)) {
      return `${prefix}${encodeURIComponent(`
        importScripts('${baseUrl}typescript.worker.js');`)}`;
    }
    return `${prefix}${encodeURIComponent(`
      importScripts('${baseUrl}editor.worker.js');`)}`;
  }
};

export default class MonacoEditorButtonView extends PureComponent {
  static displayName = 'MonacoEditorButton';
  static propTypes = {
    locale: PropTypes.string,
    messages: PropTypes.object
  };
  static defaultProps = {
    locale: 'zh-CN',
    messages: zhCN
  };
  i18n: any;
  objectButtonRef: React.RefObject<unknown>;
  constructor(props: Readonly<{}>) {
    super(props);
    this.i18n = generateI18n(props.locale, props.messages);
    this.objectButtonRef = React.createRef();
    // 兼容代码，待去除
    window.__ctx.appHelper.constants = window.__ctx.appHelper.constants || {};
  }
  afterHandler(value: { nrs_temp_field: any; }) {
    if (!value) return;
    return value.nrs_temp_field;
  }
  beforeHandler(value: any) {
    if (!value) return;
    return { nrs_temp_field: value };
  }
  message(type: string, title: any, dom: Element | null) {
    Message.show({
      type,
      title,
      duration: 1000,
      align: 'cc cc',
      overlayProps: {
        target: dom
      }
    });
  }
  componentDidMount() {
    const { registerApi } = this.props;
    let objectButtonThis = this.objectButtonRef;
    registerApi &&
      registerApi({
        show: objectButtonThis.showModal,
        hide: objectButtonThis.hideModal,
        submit: objectButtonThis.submitHandler,
        setValues: objectButtonThis.setValues
      });
  }
  render() {
    const self = this;
    const { locale, messages, value, onChange, field, ...restProps } = this.props;
    const { id } = field;
    let tarRestProps = { ...restProps };
    tarRestProps.autoSubmit = true;
    tarRestProps.autoFocus = true;
    let tarOnSubmit = tarRestProps.onSubmit;
    //确保monaco快捷键保存，能出发最外层的保存
    tarRestProps.onSubmit = (value, error) => {
      let msgDom = document.querySelector('.object-button-overlay .next-dialog-body');
      if (error) return this.message('error', this.i18n('formatError'), msgDom);
      this.objectButtonRef &&
        this.objectButtonRef.current &&
        this.objectButtonRef.current.submitHandler(() => {
          this.message('success', this.i18n('saved'), msgDom);
        });
    };
    let tarObjProps = { };
    tarObjProps.className = 'luna-monaco-button';
    if (tarRestProps['data-meta']) {
      delete tarRestProps['data-meta'];
      tarObjProps['data-meta'] = 'Field';
    }
    tarObjProps.id = id;
    tarObjProps.value = value || '';
    tarObjProps.onChange = onChange;
    let tarRule = [];
    //判断，如果是json，function, object等类型，自动追加校验规则；
    if (tarRestProps.language && ['json', 'function', 'object'].includes(tarRestProps.language)) {
      if (['json', 'object'].includes(tarRestProps.language)) {
        tarRule.push({
          validator: function(value: any, callback: (arg0: undefined) => void) {
            if (typeof value !== 'object') {
              callback(self.i18n('formatError'));
            } else {
              callback();
            }
          }
        });
      } else {
        tarRule.push({
          validator: function(value: any, callback: (arg0: undefined) => void) {
            if (typeof value !== 'function') {
              callback(self.i18n('formatError'));
            } else {
              callback();
            }
          }
        });
      }
    }
    return (
      <div>
        <ObjectButton
          locale={locale}
          messages={messages}
          {...tarObjProps}
          ref={this.objectButtonRef}
          beforeHandler={this.beforeHandler.bind(this)}
          afterHandler={this.afterHandler.bind(this)}
          onSubmit={tarOnSubmit}
        >
          <FormItem name="nrs_temp_field" rules={tarRule}>
            <MonacoEditorDefaultView {...tarRestProps} registerApi={(apis: any) => Object.assign(this, apis)}  />
          </FormItem>
        </ObjectButton>
      </div>
    );
  }
}