<template>
  <div
    class="pro-code-editor"
    :class="{
      'is-disabled': disabled,
      'is-readonly': readonly,
      'is-dark': computedTheme === 'dark',
    }"
  >
    <div v-if="$slots.toolbar || showDefaultToolbar" class="editor-toolbar">
      <slot name="toolbar">
        <a-space v-if="showDefaultToolbar" size="small">
          <a-select
            v-if="showLanguageSelect"
            :value="language"
            size="small"
            style="width: 120px"
            :options="languageOptions"
            @change="handleLanguageChange"
          />
          <a-button
            v-if="language === 'json' && !readonly"
            size="small"
            @click="formatJson"
          >
            {{ $t("codeEditor.format") }}
          </a-button>
          <a-button
            v-if="language === 'json' && !readonly"
            size="small"
            @click="minifyJson"
          >
            {{ $t("codeEditor.minify") }}
          </a-button>
          <a-button size="small" @click="copyToClipboard">
            {{ $t("codeEditor.copy") }}
          </a-button>
        </a-space>
      </slot>
    </div>
    <div class="editor-container" :style="containerStyle">
      <codemirror
        :model-value="modelValue"
        :style="editorStyle"
        :placeholder="placeholder"
        :disabled="disabled"
        :extensions="extensions"
        @update="handleUpdate"
        @focus="handleFocus"
        @blur="handleBlur"
      />
    </div>
  </div>
</template>

<script setup lang="ts">
import { Codemirror } from "vue-codemirror";
import type { Extension } from "@codemirror/state";
import {
  keymap,
  lineNumbers,
  highlightActiveLineGutter,
  highlightSpecialChars,
  drawSelection,
  dropCursor,
  rectangularSelection,
  crosshairCursor,
  highlightActiveLine,
} from "@codemirror/view";
import {
  defaultKeymap,
  history,
  historyKeymap,
  indentWithTab,
} from "@codemirror/commands";
import {
  indentOnInput,
  syntaxHighlighting,
  defaultHighlightStyle,
  bracketMatching,
  foldGutter,
} from "@codemirror/language";
import { closeBrackets, closeBracketsKeymap } from "@codemirror/autocomplete";
import { oneDark } from "@codemirror/theme-one-dark";
import { githubLight, githubDark } from "@uiw/codemirror-theme-github";
import { dracula } from "@uiw/codemirror-theme-dracula";
import { material, materialDark } from "@uiw/codemirror-theme-material";
import { monokai } from "@uiw/codemirror-theme-monokai";
import { nord } from "@uiw/codemirror-theme-nord";
import { tokyoNight } from "@uiw/codemirror-theme-tokyo-night";
import { solarizedLight, solarizedDark } from "@uiw/codemirror-theme-solarized";
import { json, jsonParseLinter } from "@codemirror/lang-json";
import { javascript } from "@codemirror/lang-javascript";
import { html } from "@codemirror/lang-html";
import { css } from "@codemirror/lang-css";
import { markdown } from "@codemirror/lang-markdown";
import { sql } from "@codemirror/lang-sql";
import { yaml } from "@codemirror/lang-yaml";
import { xml } from "@codemirror/lang-xml";
import { python } from "@codemirror/lang-python";
import { java } from "@codemirror/lang-java";
import { php } from "@codemirror/lang-php";
import { rust } from "@codemirror/lang-rust";
import { go } from "@codemirror/lang-go";
import { linter } from "@codemirror/lint";

import { message } from "antdv-next";
import { computed, ref, watch, type CSSProperties, type PropType } from "vue";

import { $t } from "@/locales";
import { useThemeStore } from "@/stores/theme";

defineOptions({
  name: "ProCodeEditor",
});

export type SupportedLanguage =
  | "json"
  | "javascript"
  | "typescript"
  | "html"
  | "css"
  | "markdown"
  | "sql"
  | "yaml"
  | "xml"
  | "python"
  | "java"
  | "php"
  | "rust"
  | "go";

export type EditorTheme =
  | "auto"
  | "light"
  | "dark"
  | "github"
  | "githubDark"
  | "dracula"
  | "material"
  | "materialDark"
  | "monokai"
  | "nord"
  | "tokyoNight"
  | "solarized"
  | "solarizedDark";

const props = defineProps({
  modelValue: {
    type: String,
    default: "",
  },
  language: {
    type: String as PropType<SupportedLanguage>,
    default: "json",
  },
  theme: {
    type: String as PropType<EditorTheme>,
    default: "auto",
  },
  readonly: {
    type: Boolean,
    default: false,
  },
  disabled: {
    type: Boolean,
    default: false,
  },
  height: {
    type: [Number, String] as PropType<number | "auto">,
    default: 300,
  },
  placeholder: {
    type: String,
    default: "",
  },
  lineNumbers: {
    type: Boolean,
    default: true,
  },
  foldGutter: {
    type: Boolean,
    default: true,
  },
  showToolbar: {
    type: Boolean,
    default: false,
  },
  showLanguageSelect: {
    type: Boolean,
    default: false,
  },
  formatOnBlur: {
    type: Boolean,
    default: false,
  },
});

const emit = defineEmits<{
  (e: "update:modelValue", value: string): void;
  (e: "change", value: string): void;
  (e: "focus"): void;
  (e: "blur"): void;
  (e: "languageChange", language: SupportedLanguage): void;
}>();

const themeStore = useThemeStore();
const emitLanguage = ref<SupportedLanguage>(props.language);

const languageOptions: Array<{ label: string; value: SupportedLanguage }> = [
  { label: "JSON", value: "json" },
  { label: "JavaScript", value: "javascript" },
  { label: "TypeScript", value: "typescript" },
  { label: "HTML", value: "html" },
  { label: "CSS", value: "css" },
  { label: "Markdown", value: "markdown" },
  { label: "SQL", value: "sql" },
  { label: "YAML", value: "yaml" },
  { label: "XML", value: "xml" },
  { label: "Python", value: "python" },
  { label: "Java", value: "java" },
  { label: "PHP", value: "php" },
  { label: "Rust", value: "rust" },
  { label: "Go", value: "go" },
];

const computedTheme = computed<"light" | "dark">(() => {
  if (props.theme === "auto") {
    return themeStore.isDark ? "dark" : "light";
  }
  const lightThemes = ["light", "github", "material", "solarized"];
  return lightThemes.includes(props.theme) ? "light" : "dark";
});

const showDefaultToolbar = computed(() => {
  return (
    props.showToolbar || props.showLanguageSelect || props.language === "json"
  );
});

const containerStyle = computed<CSSProperties>(() => {
  if (props.height === "auto") {
    return {
      height: "auto",
      minHeight: "100px",
    };
  }
  return {
    height:
      typeof props.height === "number" ? `${props.height}px` : props.height,
  };
});

const editorStyle = computed<CSSProperties>(() => ({
  height: "100%",
  fontSize: "13px",
}));

function getLanguageExtension(lang: SupportedLanguage) {
  switch (lang) {
    case "json":
      return json();
    case "javascript":
      return javascript();
    case "typescript":
      return javascript({ typescript: true });
    case "html":
      return html();
    case "css":
      return css();
    case "markdown":
      return markdown();
    case "sql":
      return sql();
    case "yaml":
      return yaml();
    case "xml":
      return xml();
    case "python":
      return python();
    case "java":
      return java();
    case "php":
      return php();
    case "rust":
      return rust();
    case "go":
      return go();
    default:
      return json();
  }
}

function getThemeExtension(theme: EditorTheme): Extension[] {
  const themeMap: Record<string, Extension | undefined> = {
    github: githubLight,
    githubDark,
    dracula,
    material,
    materialDark,
    monokai,
    nord,
    tokyoNight,
    solarized: solarizedLight,
    solarizedDark,
  };

  if (theme === "light") return [];
  if (theme === "dark") return [oneDark];

  const ext = themeMap[theme];
  return ext ? [ext] : [];
}

const baseExtensions = computed<Extension[]>(() => {
  const ext: Extension[] = [
    highlightSpecialChars(),
    history(),
    drawSelection(),
    dropCursor(),
    rectangularSelection(),
    crosshairCursor(),
    highlightActiveLine(),
    keymap.of([
      ...closeBracketsKeymap,
      ...defaultKeymap,
      ...historyKeymap,
      indentWithTab,
    ]),
    indentOnInput(),
    syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
    bracketMatching(),
    closeBrackets(),
  ];

  if (props.lineNumbers) {
    ext.push(lineNumbers());
    ext.push(highlightActiveLineGutter());
  }

  if (props.foldGutter) {
    ext.push(foldGutter());
  }

  if (props.theme === "auto") {
    if (themeStore.isDark) ext.push(oneDark);
  } else if (props.theme === "dark") {
    ext.push(oneDark);
  } else {
    ext.push(...getThemeExtension(props.theme));
  }

  ext.push(getLanguageExtension(emitLanguage.value));

  if (emitLanguage.value === "json") {
    ext.push(linter(jsonParseLinter()));
  }

  return ext;
});

const extensions = computed(() => baseExtensions.value);

function handleUpdate(viewUpdate: {
  state: { doc: { toString: () => string } };
}) {
  if (props.readonly || props.disabled) return;
  const value = viewUpdate.state.doc.toString();
  emit("update:modelValue", value);
  emit("change", value);
}

function handleFocus() {
  emit("focus");
}

function handleBlur() {
  if (props.formatOnBlur && emitLanguage.value === "json" && props.modelValue) {
    try {
      const parsed = JSON.parse(props.modelValue);
      const formatted = JSON.stringify(parsed, null, 2);
      emit("update:modelValue", formatted);
      emit("change", formatted);
    } catch (error) {
      console.warn("JSON format on blur failed:", error);
    }
  }
  emit("blur");
}

function handleLanguageChange(lang: SupportedLanguage) {
  emitLanguage.value = lang;
  emit("languageChange", lang);
}

function formatJson() {
  if (emitLanguage.value !== "json" || !props.modelValue) return;
  try {
    const parsed = JSON.parse(props.modelValue);
    const formatted = JSON.stringify(parsed, null, 2);
    emit("update:modelValue", formatted);
    emit("change", formatted);
    message.success($t("codeEditor.formatSuccess"));
  } catch (error) {
    console.warn("JSON format failed:", error);
    message.error($t("codeEditor.jsonError"));
  }
}

function minifyJson() {
  if (emitLanguage.value !== "json" || !props.modelValue) return;
  try {
    const parsed = JSON.parse(props.modelValue);
    const minified = JSON.stringify(parsed);
    emit("update:modelValue", minified);
    emit("change", minified);
    message.success($t("codeEditor.minifySuccess"));
  } catch (error) {
    console.warn("JSON minify failed:", error);
    message.error($t("codeEditor.jsonError"));
  }
}

async function copyToClipboard() {
  if (!props.modelValue) return;
  try {
    await navigator.clipboard.writeText(props.modelValue);
    message.success($t("codeEditor.copySuccess"));
  } catch (error) {
    console.warn("Copy to clipboard failed:", error);
    message.error($t("codeEditor.copyFailed"));
  }
}

watch(
  () => props.language,
  (newLang) => {
    emitLanguage.value = newLang;
  },
);
</script>

<style scoped lang="scss">
.pro-code-editor {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  overflow: hidden;
  background: var(--color-bg-container);

  &.is-dark {
    background: #1e1e1e;
    border-color: #3c3c3c;
  }

  &.is-disabled {
    opacity: 0.6;
    cursor: not-allowed;
  }
}

.editor-toolbar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 8px 12px;
  border-bottom: 1px solid var(--color-border-secondary);
  background: var(--color-bg-layout);

  .is-dark & {
    background: #252526;
    border-bottom-color: #3c3c3c;
  }
}

.editor-container {
  overflow: auto;

  :deep(.cm-editor) {
    height: 100%;
    outline: none;
  }

  :deep(.cm-scroller) {
    font-family: "Monaco", "Menlo", "Consolas", monospace;
  }

  :deep(.cm-content) {
    padding: 8px 0;
  }

  :deep(.cm-line) {
    padding: 0 8px;
  }

  :deep(.cm-placeholder) {
    color: var(--color-text-quaternary);
    font-style: italic;
  }
}

.is-readonly {
  .editor-container {
    :deep(.cm-content) {
      cursor: default;
    }

    :deep(.cm-cursor) {
      display: none;
    }
  }
}
</style>
