<script lang="ts" setup>
import { sql } from '@codemirror/lang-sql'
import { basicSetup } from 'codemirror'
import { indentWithTab } from '@codemirror/commands'
import {
  Decoration,
  EditorView,
  ViewPlugin,
  WidgetType,
  MatchDecorator,
  keymap
} from '@codemirror/view'
import { propTypes } from '@/utils/propTypes'

const props = defineProps({
  domId: propTypes.string.def('editor'),
  height: propTypes.string.def('250px'),
  quotaMap: propTypes.arrayOf(String).def(() => []),
  dimensionMap: propTypes.arrayOf(String).def(() => []),
  regexp: {
    type: RegExp,
    default: /\[(.*?)\]/g
  }
})
const emits = defineEmits(['change'])

const codeComInit = (doc: string, sqlMode?: boolean) => {
  function _optionalChain(ops) {
    let lastAccessLHS = undefined
    let value = ops[0]
    let i = 1
    while (i < ops.length) {
      const op = ops[i]
      const fn = ops[i + 1]
      i += 2
      if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) {
        return undefined
      }
      if (op === 'access' || op === 'optionalAccess') {
        lastAccessLHS = value
        value = fn(value)
      } else if (op === 'call' || op === 'optionalCall') {
        value = fn((...args) => value.call(lastAccessLHS, ...args))
        lastAccessLHS = undefined
      }
    }
    return value
  } //!placeholderMatcher

  const placeholderMatcher = new MatchDecorator({
    regexp: new RegExp(props.regexp),
    decoration: match =>
      Decoration.replace({
        widget: new PlaceholderWidget(match[1])
      })
  })

  //!placeholderPlugin

  const placeholders = ViewPlugin.fromClass(
    class {
      placeholders
      constructor(view) {
        this.placeholders = placeholderMatcher.createDeco(view)
      }
      update(update) {
        this.placeholders = placeholderMatcher.updateDeco(update, this.placeholders)
      }
    },
    {
      decorations: instance => instance.placeholders,
      provide: plugin =>
        EditorView.atomicRanges.of(view => {
          return (
            _optionalChain([
              view,
              'access',
              _ => _.plugin,
              'call',
              _2 => _2(plugin),
              'optionalAccess',
              _3 => _3.placeholders
            ]) || Decoration.none
          )
        })
    }
  )

  //!placeholderWidget

  class PlaceholderWidget extends WidgetType {
    name: string
    constructor(name: string) {
      super()
      this.name = name
    }
    eq(other) {
      return this.name == other.name
    }
    toDOM() {
      let elt = document.createElement('span')
      elt.textContent = `[${this.name}]`
      const { dimensionMap, quotaMap } = props
      if (!dimensionMap?.length && !quotaMap?.length) {
        return elt
      }
      const isQuota = quotaMap.includes(this.name)
      elt.style.borderRadius = '2px'
      elt.style.margin = '0 4px'
      elt.style.padding = '0 6px'
      elt.style.background = isQuota ? 'rgba(0, 214, 185, 0.20)' : 'rgba(51, 112, 255, 0.20)'
      elt.style.color = isQuota ? '#04B49C' : '#2B5FD9'
      return elt
    }
    ignoreEvent() {
      return false
    }
  }
  const extensionsAttach = sqlMode
    ? [
        basicSetup,
        sql(),
        placeholders,
        keymap.of([indentWithTab]),
        EditorView.updateListener.of(v => {
          if (v.docChanged) {
            emits('change')
          }
        })
      ]
    : [basicSetup, placeholders, keymap.of([indentWithTab])]
  return new EditorView({
    doc,
    extensions: extensionsAttach,
    parent: document.querySelector(`#${props.domId}`)
  })
}

defineExpose({
  codeComInit
})
</script>

<template>
  <div :style="{ height: height }" class="editor-placeholder" :id="domId"></div>
</template>

<style lang="less" scoped>
.editor-placeholder {
  width: 100%;
}
</style>
<style lang="less">
.cm-editor,
.cm-scroller {
  height: 100% !important;
  background: #eff0f1;
}
</style>
