<template>
  <div className="editor" ref="monacoCodeEditor"></div>
</template>

<script>
import { defineComponent, ref, toRefs, watch, onMounted, onUnmounted } from 'vue';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.main.js';

// 用于存储编辑器实例的hints 提示信息
const global = {};

// 获取自定义提示
const getHints = model => {
  let id = model.id.substring(6);
  return (global[id] && global[id].hints) || [];
};

// 获取所有语言并加上自定义的自动补全
let languages = monaco.languages.getLanguages();
languages.forEach(lang => {
  // 自动补全
  monaco.languages.registerCompletionItemProvider(lang.id, {
    provideCompletionItems: model => {
      let suggestions = [];
      let hints = getHints(model);
      if (hints) {
        hints.map(item => {
          suggestions.push({
            label: item.label ? item.label : item,
            kind: monaco.languages.CompletionItemKind[item.kind ? item.kind : 'Field'], // 用于定义提示的icon，可参照官网进行选择
            quickSuggestions: true,
            insertText: item.insertText ? item.insertText : item, //选择后粘贴到编辑器中的文字
            detail: item.detail ? item.detail : item, // 描述
            documentation: item.documentation ? item.documentation : item // 文档
          });
        });
      }
      return { suggestions: suggestions };
    }
  });
});

export default defineComponent({
  name: 'MonacoCodeEditor',
  props: {
    modelValue: {
      type: String,
      default: '',
      required: true
    },
    language: {
      type: String,
      default: ''
    },
    hints: {
      type: Array,
      default: () => []
    },
    // 主要配置
    options: {
      type: Object,
      default: () => ({
        wordWrap: 'on', // 自动换行
        selectOnLineNumbers: false,
        roundedSelection: false,
        readOnly: false, // 只读
        cursorStyle: 'line', // 光标样式
        automaticLayout: true, // 自动布局
        glyphMargin: false, // 字形边缘
        useTabStops: false,
        fontSize: 16, // 字体大小
        autoIndent: true, // 自动布局
        minimap: {
          enabled: false // 是否开启小地图
        },
        glyphMargin: false, // 隐藏符号边栏（断点等）
        lineNumbers: 'off', // 隐藏行号
        renderLineHighlight: 'none', // 隐藏当前行高亮
        folding: false, // 隐藏折叠控件
        lineDecorationsWidth: 0, // 设置装饰宽度为0
        scrollBeyondLastLine: false,
        quickSuggestions: true, // 开启提示
        quickSuggestionsDelay: 500, //代码提示延时
        placeholder: '请输入代码...',
        contextmenu: false, // 完全禁用右键菜单
        occurrencesHighlight: false, // 禁用自动匹配高亮
        unicodeHighlight: {
          allowedLocales: {
            'zh-hans': true
          },
          allowedCharacters: {
            '：': true, // 明确允许中文冒号
            '（': true, // 明确允许中文冒号
            '）': true, // 明确允许中文冒号
            '。': true, // 明确允许中文冒号
            '，': true, // 明确允许中文冒号
          }
        }
      })
    },
    readOnly: {
      type: Boolean,
      default: false
    },
    scrolling: {
      type: Boolean,
      default: false
    }
  },
  emits: ['update:modelValue'],
  setup(props, { emit }) {
    const { modelValue, language, options, hints } = toRefs(props);
    // 编辑器对象
    let editorInstance = null;
    // 编辑器实例
    const monacoCodeEditor = ref(null);

    // 监听值的变化
    watch(
      () => modelValue.value,
      newValue => {
        if (newValue !== editorInstance.getValue()) {
          editorInstance.setValue(newValue);
          scrollToLatestLine();
        }
      }
    );

    const layout = () => {
      editorInstance.layout();
    };

    const undo = () => {
      editorInstance.trigger('anyString', 'undo');
      onValueChange();
    };

    const redo = () => {
      editorInstance.trigger('anyString', 'redo');
      onValueChange();
    };

    const languageHandle = languageValue => {
      // 指定后缀文件格式映射语言
      const languageMap = new Map();
      languageMap.set('conf', 'redis');

      let res = undefined;

      //判断是否包含.
      if (languageValue.indexOf('.') !== -1) {
        // 处理语言 支持后缀形式
        const langs = monaco.languages.getLanguages();
        for (let i = 0; i < langs.length; i++) {
          let lang = langs[i];
          // 获取后缀
          const ext = languageValue.substr(languageValue.lastIndexOf('.') + 1);
          const strings = lang.extensions.filter(item => item === '.' + ext);
          if (strings.length > 0) {
            res = lang.id;
            break;
          } else {
            let newVar = languageMap.get(ext);
            if (newVar) {
              res = newVar;
              break;
            }
          }
        }
      }

      if (!res) {
        res = options.value.language ? options.value.language : languageValue;
      }
      return res;
    };

    const getOptions = () => {
      const newOptions = options.value;
      newOptions.language = languageHandle(language.value);

      if (props.readOnly) {
        newOptions.readOnly = true;
      }
      return newOptions;
    };

    // value改变
    const onValueChange = () => {
      // 设置编辑器语言
      monaco.editor.setModelLanguage(editorInstance.getModel(), languageHandle(language.value));
      emit('update:modelValue', editorInstance.getValue());
    };

    // 组件实例化
    onMounted(() => {
      editorInstance = monaco.editor.create(monacoCodeEditor.value, getOptions());

      // 编辑器赋值
      if (modelValue.value) {
        editorInstance.setValue(modelValue.value);
        scrollToLatestLine();
      }

      // editorInstance.onContextMenu((e) => {
      //   emit('contextmenu', e)
      // })

      editorInstance.onDidChangeModelContent(() => {
        onValueChange();
      });

      // 添加快捷键
      editorInstance.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S, () => {
        emit('update:modelValue', editorInstance.getValue());
      });

      global[editorInstance._id] = Object.assign({}, editorInstance);
      global[editorInstance._id].hints = hints.value;
      window.addEventListener('resize', layout);
    });

    const scrollToLatestLine = () => {
      if (props.scrolling) {
        const lineCount = editorInstance.getModel().getLineCount(); // 获取行数
        editorInstance.setPosition({ lineNumber: lineCount, column: 1 }); // 设置光标位置
        editorInstance.revealLine(lineCount); // 显示最后一行
      }
    };

    // 组件销毁
    onUnmounted(() => {
      // 销毁编辑器实例
      editorInstance.dispose();
      global[editorInstance._id] = null;
      window.removeEventListener('resize', layout);
    });

    return {
      monacoCodeEditor,
      layout,
      undo,
      redo,
      getOptions,
      onValueChange,
      languageHandle,
      scrollToLatestLine
    };
  }
});
</script>

<style scoped>
.editor {
  border: 1px solid #eee;
}
</style>
