<template>
  <v-dialog v-model="visible" fullscreen transition="dialog-bottom-transition" @after-enter="onDialogShown">
    <v-card class="monaco-editor-card d-flex flex-column">
      <v-toolbar density="compact" color="surface" class="flex-grow-0">
        <v-btn icon="mdi-close" @click="requestClose" />
        <v-toolbar-title class="text-body-2 text-truncate" style="max-width: 50vw;">
          {{ fileName }}
          <span class="text-medium-emphasis ml-1">{{ shortPath }}</span>
          <span v-if="modified" class="text-error ml-1">●</span>
        </v-toolbar-title>
        <v-spacer />
        <v-chip size="x-small" variant="tonal" label class="mr-2">{{ language }}</v-chip>
        <span v-if="lineCount" class="text-caption text-medium-emphasis mr-3">{{ lineCount }} 行</span>
        <v-btn
          size="small"
          variant="tonal"
          prepend-icon="mdi-content-save"
          :loading="saving"
          :disabled="!modified || !ready"
          @click="save"
        >
          保存
        </v-btn>
        <v-btn size="small" variant="text" icon="mdi-theme-light-dark" @click="toggleTheme" />
      </v-toolbar>

      <div class="monaco-editor-body flex-grow-1 position-relative">
        <v-progress-linear v-if="!ready" indeterminate absolute class="monaco-editor-progress" />
        <div ref="editorContainer" class="monaco-editor-host" />
      </div>

      <v-dialog v-model="showCloseConfirm" max-width="400">
        <v-card border>
          <v-card-title>未保存的更改</v-card-title>
          <v-card-text>文件已修改但未保存，确定要关闭吗？</v-card-text>
          <v-card-actions>
            <v-spacer />
            <v-btn variant="text" @click="showCloseConfirm = false">取消</v-btn>
            <v-btn color="error" variant="flat" @click="forceClose">不保存关闭</v-btn>
            <v-btn color="primary" variant="flat" @click="saveThenClose">保存并关闭</v-btn>
          </v-card-actions>
        </v-card>
      </v-dialog>
    </v-card>
  </v-dialog>
</template>

<script setup lang="ts">
import { ref, computed, watch, nextTick, onBeforeUnmount } from 'vue'
import { http } from '@/api'
import { useSnackbar } from '@/composables/useSnackbar'
import { buildMonacoEditorOptions, initMonaco, toggleNexusEditorTheme } from '@/monaco/initMonaco'
import { detectEditorLanguage } from '@/utils/editorLanguage'

import type * as Monaco from 'monaco-editor'

const monaco = initMonaco()

const snackbar = useSnackbar()

const props = defineProps<{
  modelValue: boolean
  filePath: string
  fileName: string
  fileContent: string
  serverId: number
}>()

const emit = defineEmits<{
  'update:modelValue': [value: boolean]
  saved: []
}>()

const visible = computed({
  get: () => props.modelValue,
  set: (v) => emit('update:modelValue', v),
})

const editorContainer = ref<HTMLElement>()
const saving = ref(false)
const modified = ref(false)
const ready = ref(false)
const showCloseConfirm = ref(false)
const language = ref('plaintext')
const lineCount = ref(0)

let editor: Monaco.editor.IStandaloneCodeEditor | null = null
let model: Monaco.editor.ITextModel | null = null
let originalContent = ''
let layoutObserver: ResizeObserver | null = null

const shortPath = computed(() => {
  const p = props.filePath || ''
  return p.length > 48 ? `…${p.slice(-44)}` : p
})

function normalizeEol(text: string): string {
  return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
}

function updateLineCount() {
  if (!editor) {
    lineCount.value = 0
    return
  }
  lineCount.value = editor.getModel()?.getLineCount() ?? 0
}

function destroyEditor() {
  layoutObserver?.disconnect()
  layoutObserver = null
  editor?.dispose()
  editor = null
  model?.dispose()
  model = null
  ready.value = false
}

function mountEditor() {
  if (!editorContainer.value || !visible.value) return

  destroyEditor()
  ready.value = false

  language.value = detectEditorLanguage(props.fileName, props.filePath)
  originalContent = normalizeEol(props.fileContent ?? '')
  modified.value = false

  model = monaco.editor.createModel(originalContent, language.value, monaco.Uri.file(props.filePath || props.fileName))

  editor = monaco.editor.create(editorContainer.value, {
    ...buildMonacoEditorOptions(),
    model,
  })

  editor.onDidChangeModelContent(() => {
    if (!editor) return
    modified.value = normalizeEol(editor.getValue()) !== originalContent
    updateLineCount()
  })

  editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
    save()
  })

  editor.addCommand(monaco.KeyCode.Escape, () => {
    requestClose()
  })

  layoutObserver = new ResizeObserver(() => {
    editor?.layout()
  })
  layoutObserver.observe(editorContainer.value)

  ready.value = true
  updateLineCount()
  requestAnimationFrame(() => editor?.layout())
}

function onDialogShown() {
  mountEditor()
}

function toggleTheme() {
  toggleNexusEditorTheme(monaco)
}

async function save() {
  if (!editor || !modified.value || !props.serverId) return
  saving.value = true
  try {
    await http.post('/sync/write-file', {
      server_id: props.serverId,
      path: props.filePath,
      content: editor.getValue(),
    })
    originalContent = normalizeEol(editor.getValue())
    modified.value = false
    snackbar('文件已保存')
    emit('saved')
  } catch (e: unknown) {
    const msg = e instanceof Error ? e.message : '保存失败'
    snackbar(msg, 'error')
  } finally {
    saving.value = false
  }
}

function requestClose() {
  if (modified.value) {
    showCloseConfirm.value = true
  } else {
    close()
  }
}

function forceClose() {
  showCloseConfirm.value = false
  close()
}

async function saveThenClose() {
  await save()
  if (!modified.value) {
    showCloseConfirm.value = false
    close()
  }
}

function close() {
  destroyEditor()
  visible.value = false
}

watch(
  () => [props.modelValue, props.filePath, props.fileContent] as const,
  async ([open]) => {
    if (!open) {
      destroyEditor()
      return
    }
    await nextTick()
    if (editorContainer.value?.offsetParent !== null) {
      mountEditor()
    }
  },
)

onBeforeUnmount(() => {
  destroyEditor()
})
</script>

<style scoped>
.monaco-editor-card {
  height: 100vh;
  overflow: hidden;
}
.monaco-editor-body {
  min-height: 0;
}
.monaco-editor-host {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.monaco-editor-host :deep(.monaco-editor .view-line span) {
  font-family: inherit;
}
.monaco-editor-progress {
  z-index: 2;
}
</style>
