<script setup lang="ts">
import type { editor } from 'monaco-editor'

import { nextTick, onMounted, ref, watch } from 'vue'
import { useAppStore } from '@/store/modules/app'
import * as monaco from 'monaco-editor'
import { LuFullscreen, LuShrink } from 'vue-icons-plus/lu'

const props = withDefaults(
  defineProps<{
    allowFullscreen?: boolean
    autoToggleTheme?: boolean
    bordered?: boolean
    config?: editor.IStandaloneEditorConstructionOptions
    language?: string
    lineNumbers?: 'off' | 'on'
    modelValue?: any
    readOnly?: boolean
    theme?: 'hc-black' | 'vs-dark' | 'vs-light'
    valueFormat?: string
  }>(),
  {
    allowFullscreen: true,
    config: () => ({
      minimap: {
        enabled: false
      },
      selectOnLineNumbers: true
    }),
    autoToggleTheme: true,
    language: 'json',
    lineNumbers: 'on',
    readOnly: false,
    theme: 'vs-light',
    valueFormat: 'string'
  }
)

const { theme } = useAppStore()

const emit = defineEmits(['update:modelValue'])

const isFullScreen = ref(false)

const fullScreenStyle = `position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2999;`

const editContainer = ref<HTMLElement | null>(null)

let monacoEditor: monaco.editor.IStandaloneCodeEditor | null = null

/**
 * 设置文本
 * @param text
 */
function setValue(text: string) {
  monacoEditor?.setValue(text || '')
}

/**
 * 光标处插入文本
 * @param text
 */
function insertText(text: string) {
  // 获取光标位置
  const position = monacoEditor?.getPosition()
  // 未获取到光标位置信息
  if (!position) {
    return
  }
  // 插入
  monacoEditor?.executeEdits('', [
    {
      range: new monaco.Range(
        position.lineNumber,
        position.column,
        position.lineNumber,
        position.column
      ),
      text
    }
  ])
  // 设置新的光标位置
  monacoEditor?.setPosition({
    ...position,
    column: position.column + text.length
  })
  // 重新聚焦
  monacoEditor?.focus()
}

let h = 0
const onChangeFullScreen = () => {
  if (!isFullScreen.value) {
    h = monacoEditor?.getLayoutInfo().height || 0
  } else {
    monacoEditor?.layout({
      width: editContainer.value?.clientWidth || 0,
      height: h
    })
  }
  isFullScreen.value = !isFullScreen.value
}

onMounted(() => {
  monacoEditor = monaco.editor.create(editContainer.value as HTMLElement, {
    value: getValue(),
    ...props.config,
    automaticLayout: true,
    language: props.language,
    lineNumbers: props.lineNumbers,
    readOnly: props.readOnly,
    scrollBeyondLastLine: false,
    theme: props.theme
  })

  function handleToggleTheme() {
    if (theme === 'dark') {
      monaco.editor.setTheme('vs-dark')
    } else {
      monaco.editor.setTheme('vs-light')
    }
  }

  // 自动切换主题
  if (props.autoToggleTheme) {
    watch(
      () => theme,
      () => {
        nextTick(() => handleToggleTheme())
      },
      {
        immediate: true
      }
    )
  }

  // 获取值
  function getValue() {
    // valueFormat 为json 格式，需要转换处理
    if (props.valueFormat === 'json' && props.modelValue) {
      return JSON.stringify(props.modelValue, null, 2)
    }
    return props.modelValue ?? ''
  }

  // 监听值变化
  monacoEditor.onDidChangeModelContent(() => {
    const currenValue = monacoEditor?.getValue()

    // valueFormat 为json 格式，需要转换处理
    if (props.valueFormat === 'json' && currenValue) {
      emit('update:modelValue', JSON.parse(currenValue))
      return
    }

    emit('update:modelValue', currenValue ?? '')
  })
})

defineExpose({
  insertText,
  setValue
})
</script>
<template>
  <div
    ref="editContainer"
    :class="{ bordered: props.bordered }"
    :style="isFullScreen ? fullScreenStyle : ''"
    class="code-editor w-full h-full relative"
  >
    <div
      class="z-999 text-$epic-text-helper absolute right-4 top-2 cursor-pointer text-xl"
      @click="onChangeFullScreen"
      v-if="props.allowFullscreen"
    >
      <LuFullscreen v-if="!isFullScreen" :size="16" />
      <LuShrink v-else :size="16" />
    </div>
  </div>
</template>
<style lang="less" scoped>
.code-editor {
  width: 100%;
  min-height: 150px;
  :deep(.monaco-editor) {
    height: 100%;
  }

  &.bordered {
    border: 1px solid var(--epic-border-color);
  }
}
</style>
