<script setup>
import { useNamespace, useLocale } from "@yh-ui/hooks";
import {
  ref,
  computed,
  watch,
  useSlots,
  Comment,
  Text,
  onMounted
} from "vue";
import { YhButton } from "../../button";
import { YhIcon } from "../../icon";
import hljs, { loadHighlightStyle } from "../../highlight";
import { aiCodeBlockProps, aiCodeBlockEmits } from "./ai-code-block";
import { useComponentTheme } from "@yh-ui/theme";
import YhAiCodeEditor from "../../ai-code-editor";
import { sanitizeHighlightedHtml } from "../../sanitize";
defineOptions({
  name: "YhAiCodeBlock"
});
const props = defineProps(aiCodeBlockProps);
const emit = defineEmits(aiCodeBlockEmits);
const ns = useNamespace("ai-code-block");
const { t } = useLocale();
const { themeStyle } = useComponentTheme("ai-code-block", props.themeOverrides);
const slots = useSlots();
function normalizeCodeLines(code) {
  if (!code) return "";
  return code.replace(/\\n/g, "\n").trim();
}
function escapeHtml(value) {
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
}
const copied = ref(false);
const collapsed = ref(props.defaultCollapsed);
const isEditing = ref(false);
function getVNodeText(node) {
  if (node == null) return "";
  if (typeof node === "string" || typeof node === "number") return String(node);
  if (Array.isArray(node)) return node.map(getVNodeText).join("");
  const vnode = node;
  if (vnode.type === Comment) return "";
  if (vnode.type === Text) return typeof vnode.children === "string" ? vnode.children : "";
  const children = vnode.children;
  if (typeof children === "string" || typeof children === "number") return String(children);
  if (Array.isArray(children)) return children.map(getVNodeText).join("");
  return "";
}
const slotCode = computed(() => {
  const vnodes = slots.default?.() ?? [];
  return normalizeCodeLines(vnodes.map(getVNodeText).join(""));
});
const normalizedCode = computed(() => {
  const fromProp = normalizeCodeLines(props.code);
  return fromProp || slotCode.value;
});
const editCode = ref(normalizedCode.value);
const lineCount = computed(() => {
  return normalizedCode.value ? normalizedCode.value.split("\n").length : 0;
});
const highlightedCodeLines = computed(() => {
  if (!normalizedCode.value) return [];
  let html = escapeHtml(normalizedCode.value);
  try {
    if (props.highlight) {
      if (props.language && hljs.getLanguage(props.language)) {
        html = hljs.highlight(normalizedCode.value, {
          language: props.language,
          ignoreIllegals: true
        }).value;
      } else {
        html = hljs.highlightAuto(normalizedCode.value).value;
      }
    }
  } catch (err) {
    console.error("Highlight error:", err);
  }
  return html.split("\n").map((line, idx) => {
    const isHighlighted = props.highlightLines.includes(idx + 1);
    return sanitizeHighlightedHtml(
      `<div class="${ns.e("line")} ${isHighlighted ? "is-active" : ""}">${line === "" ? " " : line}</div>`
    );
  });
});
const handleCopy = async () => {
  if (!normalizedCode.value) return;
  try {
    await navigator.clipboard.writeText(normalizedCode.value);
    copied.value = true;
    emit("copy", normalizedCode.value);
    setTimeout(() => {
      copied.value = false;
    }, 2e3);
  } catch (err) {
    console.error("Failed to copy text: ", err);
  }
};
const handleRun = () => {
  emit("run", normalizedCode.value);
};
const handleEdit = () => {
  const code = normalizedCode.value;
  editCode.value = code;
  isEditing.value = true;
  emit("edit", code);
};
const handleSaveEdit = () => {
  emit("update", editCode.value);
  isEditing.value = false;
};
const handleCancelEdit = () => {
  editCode.value = normalizedCode.value;
  isEditing.value = false;
};
watch(normalizedCode, (val) => {
  if (!isEditing.value) {
    editCode.value = val;
  }
});
const toggleCollapse = () => {
  if (props.collapsible) {
    collapsed.value = !collapsed.value;
  }
};
onMounted(() => {
  loadHighlightStyle();
});
</script>

<template>
  <div
    :class="[ns.b(), ns.is('collapsible', collapsible), ns.is('collapsed', collapsed), ns.is('editing', isEditing)]"
    :style="themeStyle"
  >
    <div :class="ns.e('header')" @click="toggleCollapse">
      <div :class="ns.e('header-left')">
        <YhIcon
          v-if="collapsible"
          :name="collapsed ? 'arrow-right' : 'arrow-down'"
          :class="ns.e('collapse-icon')"
        />
        <span :class="ns.e('lang')">{{ filename || language }}</span>
      </div>
      <div :class="ns.e('actions')" @click.stop>
        <slot name="actions"></slot>
        <YhButton
          v-if="(editable || showEdit) && !isEditing"
          size="small"
          text
          @click="handleEdit"
          style="margin-right: 8px"
        >
          <template #icon>
            <YhIcon name="edit" />
          </template>
          {{ t("ai.codeBlock.edit") }}
        </YhButton>
        <YhButton v-if="showRun" size="small" text @click="handleRun" style="margin-right: 8px">
          <template #icon>
            <YhIcon name="video-play" />
          </template>
          {{ t("ai.codeBlock.run") }}
        </YhButton>
        <YhButton size="small" text @click="handleCopy">
          <template #icon>
            <YhIcon :name="copied ? 'check' : 'copy'" />
          </template>
          {{ copied ? t("ai.codeBlock.copied") : t("ai.codeBlock.copyCode") }}
        </YhButton>
      </div>
    </div>
    <div :class="ns.e('body')" v-show="!collapsed">
      <template v-if="isEditing">
        <div :class="ns.e('editor-wrapper')">
          <YhAiCodeEditor
            :initial-value="editCode"
            v-model="editCode"
            :language="language"
            :height="300"
          />
          <div :class="ns.e('editor-actions')">
            <YhButton type="primary" size="small" @click="handleSaveEdit">
              {{ t("ai.codeBlock.save") }}
            </YhButton>
            <YhButton size="small" @click="handleCancelEdit" style="margin-left: 8px">
              {{ t("ai.codeBlock.cancel") }}
            </YhButton>
          </div>
        </div>
      </template>
      <template v-else>
        <div v-if="showLineNumbers" :class="ns.e('line-numbers')">
          <span
            v-for="n in lineCount"
            :key="n"
            :class="[ns.e('line-number'), ns.is('active', highlightLines.includes(n))]"
          >
            {{ n }}
          </span>
        </div>
        <div :class="ns.e('content')">
          <pre v-if="!$slots.default"><!-- eslint-disable-next-line vue/no-v-html --><code
          :class="['hljs', language ? 'language-' + language : '']" v-html="highlightedCodeLines.join('\n')"></code></pre>
          <pre v-else><code :class="['hljs', language ? 'language-' + language : '']">
          <slot></slot>
        </code></pre>
        </div>
      </template>
    </div>
  </div>
</template>

<style>
@charset "UTF-8";
/**
 * YH-UI Sass Variables
 * 原具体的 CSS 变量输出规则 (:root, html.dark 等) 已移至 root.scss，以防止各组件样式编译时产生重复的全局变量 CSS 冗余。
 */
.yh-ai-code-block {
  --yh-ai-code-block-bg: #282c34;
  --yh-ai-code-block-header-bg: #21252b;
  --yh-ai-code-block-border-color: #181a1f;
  --yh-ai-code-block-color: #abb2bf;
  --yh-ai-code-block-lang-color: #828997;
  --yh-ai-code-block-border-radius: 8px;
  --yh-ai-code-block-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  margin: 16px 0;
  border-radius: var(--yh-ai-code-block-border-radius);
  overflow: hidden;
  background-color: var(--yh-ai-code-block-bg);
  color: var(--yh-ai-code-block-color);
  box-shadow: var(--yh-ai-code-block-shadow);
  font-family: var(--yh-font-family-mono);
  font-size: 14px;
  container-type: inline-size;
  container-name: ai-code-block;
}
.yh-ai-code-block__header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 16px;
  background-color: var(--yh-ai-code-block-header-bg);
  border-bottom: 1px solid var(--yh-ai-code-block-border-color);
  user-select: none;
}

.yh-ai-code-block.is-collapsible .yh-ai-code-block__header {
  cursor: pointer;
}
.yh-ai-code-block.is-collapsible .yh-ai-code-block__header:hover {
  background-color: var(--yh-ai-code-block-border-color);
}

.yh-ai-code-block__header-left {
  display: flex;
  align-items: center;
  gap: 8px;
}

.yh-ai-code-block__collapse-icon {
  font-size: 14px;
  color: var(--yh-ai-code-block-lang-color);
  transition: transform 0.3s;
}

.yh-ai-code-block__lang {
  font-size: 12px;
  color: var(--yh-ai-code-block-lang-color);
  text-transform: uppercase;
  font-weight: 600;
}

.yh-ai-code-block__actions {
  display: flex;
  align-items: center;
}
.yh-ai-code-block__actions .yh-button {
  --yh-button-text-color: #828997;
  --yh-button-hover-text-color: #c8cdd4;
  --yh-button-active-text-color: #fff;
  --yh-button-bg-color: transparent;
  --yh-button-hover-bg-color: rgba(255, 255, 255, 0.08);
  --yh-button-active-bg-color: rgba(255, 255, 255, 0.12);
}
.yh-ai-code-block__actions .yh-button .yh-icon-video-play {
  color: var(--yh-color-primary);
  margin-right: 4px;
}

.yh-ai-code-block__body {
  display: flex;
  position: relative;
  overflow-x: auto;
  background-color: var(--yh-ai-code-block-bg);
}
.yh-ai-code-block__body::-webkit-scrollbar {
  height: 8px;
  width: 8px;
}
.yh-ai-code-block__body::-webkit-scrollbar-thumb {
  background-color: #4b5263;
  border-radius: 4px;
}

.yh-ai-code-block__line-numbers {
  display: flex;
  flex-direction: column;
  padding: 16px 0;
  background-color: var(--yh-ai-code-block-header-bg);
  border-right: 1px solid var(--yh-ai-code-block-border-color);
  user-select: none;
  text-align: right;
  min-width: 40px;
  flex-shrink: 0;
}

.yh-ai-code-block__line-number {
  padding: 0 10px;
  color: #4b5263;
  font-size: 12px;
  line-height: 1.6;
}
.yh-ai-code-block__line-number.is-active {
  color: #abb2bf;
  background-color: rgba(255, 255, 255, 0.05);
}

.yh-ai-code-block__content {
  flex: 1;
  min-width: 0;
  padding: 16px 0;
}
.yh-ai-code-block__content pre {
  margin: 0;
  white-space: pre;
  word-break: normal;
  word-wrap: normal;
  line-height: 1.6;
}
.yh-ai-code-block__content code {
  display: block;
  font-family: inherit;
  color: inherit;
}
.yh-ai-code-block__content .hljs {
  padding: 0;
  background: transparent;
}
.yh-ai-code-block__line {
  padding: 0 16px;
  min-height: 1.6em;
}
.yh-ai-code-block__line.is-active {
  background-color: rgba(255, 255, 255, 0.05);
  border-left: 2px solid var(--yh-color-primary);
  padding-left: 14px;
}

.yh-ai-code-block__editor-wrapper {
  width: 100%;
  min-height: 300px;
  padding: 12px;
}

.yh-ai-code-block__editor-actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--yh-ai-code-block-border-color);
}

.yh-ai-code-block.is-editing .yh-ai-code-block__body {
  display: block;
  background-color: #1e1e1e;
}

.yh-ai-code-block.is-editing .yh-ai-code-block__header {
  background-color: #2d2d2d;
}

@container ai-code-block (max-width: 560px) {
  .yh-ai-code-block__header {
    flex-wrap: wrap;
    gap: 8px;
    align-items: flex-start;
  }
  .yh-ai-code-block__actions {
    width: 100%;
    justify-content: flex-end;
    flex-wrap: wrap;
    row-gap: 4px;
  }
  .yh-ai-code-block__line-numbers {
    min-width: 32px;
    padding: 12px 0;
  }
  .yh-ai-code-block__content {
    padding: 12px 0;
  }
  .yh-ai-code-block__line {
    padding: 0 12px;
  }
  .yh-ai-code-block__line.is-active {
    padding-left: 10px;
  }
}
@container ai-code-block (max-width: 420px) {
  .yh-ai-code-block__header {
    padding: 8px 12px;
  }
  .yh-ai-code-block__body {
    font-size: 13px;
  }
  .yh-ai-code-block__editor-wrapper {
    padding: 8px;
  }
}
</style>
