<template>
  <default-field
    :field="field"
    :errors="errors"
    :full-width-content="true"
    :show-help-text="showHelpText"
  >
    <template slot="field">
      <div
        class="bg-white rounded-lg overflow-hidden"
        :class="{
          'markdown-fullscreen fixed pin z-50': isFullScreen,
          'form-input form-input-bordered px-0': !isFullScreen,
          'form-control-focus': isFocused,
          'border-danger': errors.has('body'),
        }"
      >
        <header
          class="flex items-center content-center justify-between border-b border-60"
          :class="{ 'bg-30': isReadonly }"
        >
          <ul class="w-full flex items-center content-center list-reset">
            <button
              :class="{
                'text-primary font-bold': this.mode == 'write',
              }"
              @click.prevent="write"
              class="ml-1 text-90 px-3 py-2"
            >
              {{ __('Write') }}
            </button>
            <button
              :class="{
                'text-primary font-bold': this.mode == 'preview',
              }"
              @click.prevent="preview"
              class="text-90 px-3 py-2"
            >
              {{ __('Preview') }}
            </button>
          </ul>

          <ul v-if="!isReadonly" class="flex items-center list-reset">
            <button
              :key="tool.action"
              @click.prevent="callAction(tool.action)"
              v-for="tool in tools"
              class="rounded-none ico-button inline-flex items-center justify-center px-2 text-sm text-80 border-l border-60"
            >
              <component
                :is="tool.icon"
                class="fill-80 w-editor-icon h-editor-icon"
              />
            </button>
          </ul>
        </header>

        <div
          v-show="mode == 'write'"
          class="flex markdown-content relative p-4"
          :class="{ 'readonly bg-30': isReadonly }"
        >
          <textarea ref="theTextarea" :class="{ 'bg-30': isReadonly }" />
        </div>

        <div
          v-if="mode == 'preview'"
          class="markdown overflow-scroll p-4"
          v-html="previewContent"
        ></div>
      </div>
    </template>
  </default-field>
</template>

<script>
import _ from 'lodash'
const md = require('markdown-it')()
import CodeMirror from 'codemirror'
import 'codemirror/mode/markdown/markdown'
import { FormField, HandlesValidationErrors } from 'laravel-nova'

const actions = {
  bold() {
    if (!this.isEditable) return

    this.insertAround('**', '**')
  },

  italicize() {
    if (!this.isEditable) return

    this.insertAround('*', '*')
  },

  image() {
    if (!this.isEditable) return

    this.insertBefore('![](url)', 2)
  },

  link() {
    if (!this.isEditable) return

    this.insertAround('[', '](url)')
  },

  toggleFullScreen() {
    this.fullScreen = !this.fullScreen
    this.$nextTick(() => this.codemirror.refresh())
  },

  fullScreen() {
    this.fullScreen = true
  },

  exitFullScreen() {
    this.fullScreen = false
  },
}

const keyMaps = {
  'Cmd-B': 'bold',
  'Cmd-I': 'italicize',
  'Cmd-Alt-I': 'image',
  'Cmd-K': 'link',
  F11: 'fullScreen',
  Esc: 'exitFullScreen',
}

export default {
  mixins: [HandlesValidationErrors, FormField],

  data: () => ({
    fullScreen: false,
    isFocused: false,
    codemirror: null,
    mode: 'write',
    tools: [
      {
        name: 'bold',
        action: 'bold',
        className: 'fa fa-bold',
        icon: 'editor-bold',
      },
      {
        name: 'italicize',
        action: 'italicize',
        className: 'fa fa-italic',
        icon: 'editor-italic',
      },
      {
        name: 'link',
        action: 'link',
        className: 'fa fa-link',
        icon: 'editor-link',
      },
      {
        name: 'image',
        action: 'image',
        className: 'fa fa-image',
        icon: 'editor-image',
      },
      {
        name: 'fullScreen',
        action: 'toggleFullScreen',
        className: 'fa fa-expand',
        icon: 'editor-fullscreen',
      },
    ],
  }),

  mounted() {
    this.codemirror = CodeMirror.fromTextArea(this.$refs.theTextarea, {
      tabSize: 4,
      indentWithTabs: true,
      lineWrapping: true,
      mode: 'markdown',
      viewportMargin: Infinity,
      extraKeys: {
        Enter: 'newlineAndIndentContinueMarkdownList',
        ..._.map(this.tools, tool => {
          return tool.action
        }),
      },
      ...{ readOnly: this.isReadonly },
    })

    _.each(keyMaps, (action, map) => {
      const realMap = map.replace(
        'Cmd-',
        CodeMirror.keyMap['default'] == CodeMirror.keyMap.macDefault
          ? 'Cmd-'
          : 'Ctrl-'
      )
      this.codemirror.options.extraKeys[realMap] = actions[keyMaps[map]].bind(
        this
      )
    })

    this.doc.on('change', (cm, changeObj) => {
      this.value = cm.getValue()
    })

    this.codemirror.on('focus', () => (this.isFocused = true))
    this.codemirror.on('blur', () => (this.isFocused = false))

    if (this.field.value) {
      this.doc.setValue(this.field.value)
    }

    Nova.$on(this.field.attribute + '-value', value => {
      this.doc.setValue(value)
      this.$nextTick(() => this.codemirror.refresh())
    })

    this.$nextTick(() => this.codemirror.refresh())
  },

  methods: {
    focus() {
      this.codemirror.focus()
    },

    write() {
      this.mode = 'write'
      this.$nextTick(() => {
        this.codemirror.refresh()
      })
    },

    preview() {
      this.mode = 'preview'
    },

    insert(insertion) {
      this.doc.replaceRange(insertion, {
        line: this.cursor.line,
        ch: this.cursor.ch,
      })
    },

    insertAround(start, end) {
      if (this.doc.somethingSelected()) {
        const selection = this.doc.getSelection()
        this.doc.replaceSelection(start + selection + end)
      } else {
        this.doc.replaceRange(start + end, {
          line: this.cursor.line,
          ch: this.cursor.ch,
        })
        this.doc.setCursor({
          line: this.cursor.line,
          ch: this.cursor.ch - end.length,
        })
      }
    },

    insertBefore(insertion, cursorOffset) {
      if (this.doc.somethingSelected()) {
        const selects = this.doc.listSelections()
        selects.forEach(selection => {
          const pos = [selection.head.line, selection.anchor.line].sort()

          for (let i = pos[0]; i <= pos[1]; i++) {
            this.doc.replaceRange(insertion, { line: i, ch: 0 })
          }

          this.doc.setCursor({ line: pos[0], ch: cursorOffset || 0 })
        })
      } else {
        this.doc.replaceRange(insertion, {
          line: this.cursor.line,
          ch: 0,
        })
        this.doc.setCursor({
          line: this.cursor.line,
          ch: cursorOffset || 0,
        })
      }
    },

    callAction(action) {
      if (!this.isReadonly) {
        this.focus()
        actions[action].call(this)
      }
    },
  },

  computed: {
    doc() {
      return this.codemirror.getDoc()
    },

    isFullScreen() {
      return this.fullScreen == true
    },

    cursor() {
      return this.doc.getCursor()
    },

    rawContent() {
      return this.codemirror.getValue()
    },

    previewContent() {
      return md.render(this.rawContent || '')
    },

    isEditable() {
      return !this.isReadonly && this.mode == 'write'
    },
  },
}
</script>

<style src="codemirror/lib/codemirror.css" />

<style>
.ico-button {
  width: 35px;
  height: 35px;
}

.ico-button:hover {
  color: var(--primary);
}

.ico-button:active {
  color: var(--brand-80);
}

.cm-fat-cursor .CodeMirror-cursor {
  background: #000;
}

.cm-s-default .cm-header {
  color: black;
}
.cm-s-default .cm-link {
  color: var(--primary);
}
.CodeMirror-line {
  color: var(--gray-60);
}
.cm-s-default .cm-variable-2 {
  color: var(--gray-60);
}
.cm-s-default .cm-quote {
  color: var(--gray-60);
}
.cm-s-default .cm-comment {
  color: var(--gray-60);
}
.cm-s-default .cm-string {
  color: var(--gray-40);
}
.cm-s-default .cm-url {
  color: var(--gray-40);
}

.CodeMirror {
  height: auto;
  font: 14px/1.5 Menlo, Consolas, Monaco, 'Andale Mono', monospace;
  box-sizing: border-box;
  width: 100%;
}

.readonly > .CodeMirror {
  background-color: var(--30) !important;
}

.markdown-fullscreen .markdown-content {
  height: calc(100vh - 30px);
}

.markdown-fullscreen .CodeMirror {
  height: 100%;
}
</style>
