<script setup>
import { useNamespace, useLocale } from "@yh-ui/hooks";
import { ref, computed, watch, nextTick, onBeforeUnmount } from "vue";
import {
  aiEditorSenderProps,
  aiEditorSenderEmits
} from "./ai-editor-sender";
import { YhButton } from "../../button";
import { YhIcon } from "../../icon";
import { useComponentTheme } from "@yh-ui/theme";
defineOptions({
  name: "YhAiEditorSender"
});
const props = defineProps(aiEditorSenderProps);
const emit = defineEmits(aiEditorSenderEmits);
const ns = useNamespace("ai-editor-sender");
const { t } = useLocale();
const { themeStyle } = useComponentTheme("ai-editor-sender", props.themeOverrides);
const textareaRef = ref();
const panelRef = ref(null);
const editorRef = ref(null);
const localValue = ref(props.modelValue);
const isFocused = ref(false);
const commandPanelCoords = ref({ top: 0, left: 0 });
const updateCommandPanelPosition = () => {
  if (!editorRef.value) return;
  const rect = editorRef.value.getBoundingClientRect();
  nextTick(() => {
    const panel = panelRef.value;
    const panelHeight = panel ? panel.getBoundingClientRect().height : 280;
    const panelWidth = panel ? panel.getBoundingClientRect().width : 320;
    let top = 0;
    let left = 0;
    if (props.commandPanelPosition === "top") {
      top = rect.top - panelHeight - 4;
    } else {
      top = rect.bottom + 4;
    }
    if (props.commandPanelAlign === "start") {
      left = rect.left;
    } else if (props.commandPanelAlign === "center") {
      left = rect.left + (rect.width - panelWidth) / 2;
    } else if (props.commandPanelAlign === "end") {
      left = rect.right - panelWidth;
    }
    commandPanelCoords.value = { top, left };
  });
};
const commandWrapperStyle = computed(() => ({
  position: "fixed",
  zIndex: 9999,
  top: `${commandPanelCoords.value.top}px`,
  left: `${commandPanelCoords.value.left}px`
}));
const showCommandPanel = ref(false);
const commandSearchText = ref("");
const filteredCommands = ref([]);
const selectedCommandIndex = ref(0);
const cascadeCommands = ref([]);
const currentCascadeParent = ref(null);
const showCascadePanel = ref(false);
const cascadePosition = ref({ top: 0, left: 0 });
let searchDebounceTimer = null;
watch(showCommandPanel, (val) => {
  if (val) {
    nextTick(() => {
      updateCommandPanelPosition();
    });
    window.addEventListener("scroll", updateCommandPanelPosition, true);
    window.addEventListener("resize", updateCommandPanelPosition, true);
  } else {
    window.removeEventListener("scroll", updateCommandPanelPosition, true);
    window.removeEventListener("resize", updateCommandPanelPosition, true);
  }
});
onBeforeUnmount(() => {
  window.removeEventListener("scroll", updateCommandPanelPosition, true);
  window.removeEventListener("resize", updateCommandPanelPosition, true);
  if (searchDebounceTimer) {
    clearTimeout(searchDebounceTimer);
  }
});
watch(
  () => props.modelValue,
  (val) => {
    localValue.value = val;
  }
);
const isComposing = ref(false);
const handleCompositionStart = () => {
  isComposing.value = true;
};
const handleCompositionEnd = (e) => {
  isComposing.value = false;
  const val = e.target.value;
  innerValue.value = val;
  nextTick(() => {
    autoResize();
    updateCommandPanelPosition();
  });
};
const innerValue = computed({
  get: () => localValue.value,
  set: (val) => {
    localValue.value = val;
    emit("update:modelValue", val);
    emit("change", val);
    if (!isComposing.value) {
      checkCommandTrigger(val);
    }
  }
});
const autoResize = () => {
  const el = textareaRef.value;
  if (!el) return;
  el.style.height = "auto";
  el.style.height = `${Math.min(el.scrollHeight, 200)}px`;
};
const handleInput = (e) => {
  const val = e.target.value;
  if (isComposing.value) {
    localValue.value = val;
    return;
  }
  innerValue.value = val;
  nextTick(() => {
    autoResize();
    updateCommandPanelPosition();
  });
};
const handleKeyDown = (e) => {
  if (e.key === "Enter" && !e.shiftKey) {
    if (!innerValue.value.trim() || props.loading || props.disabled) {
      e.preventDefault();
    } else if (showCommandPanel.value) {
      e.preventDefault();
      const commands = getAllVisibleCommands();
      if (commands.length > 0 && selectedCommandIndex.value >= 0) {
        selectCommand(commands[selectedCommandIndex.value]);
      }
    } else {
      e.preventDefault();
      handleSend();
    }
  } else if (e.key === "ArrowDown" && showCommandPanel.value) {
    e.preventDefault();
    navigateCommands(1);
  } else if (e.key === "ArrowUp" && showCommandPanel.value) {
    e.preventDefault();
    navigateCommands(-1);
  } else if (e.key === "ArrowRight" && showCommandPanel.value && currentCascadeParent.value) {
    openCascadePanel(currentCascadeParent.value);
  } else if (e.key === "Escape") {
    hideCommandPanel();
  } else if (e.key === "Backspace" && !commandSearchText.value) {
    hideCommandPanel();
  }
};
const checkCommandTrigger = (value) => {
  if (!props.enableCommands) return;
  const _cursorPos = value.length;
  const lastSlashIndex = value.lastIndexOf(props.commandTrigger);
  if (lastSlashIndex !== -1) {
    const afterTrigger = value.slice(lastSlashIndex + props.commandTrigger.length);
    if (afterTrigger.includes(" ")) {
      hideCommandPanel();
      return;
    }
    commandSearchText.value = afterTrigger;
    filterCommands(afterTrigger);
    showCommandPanel.value = true;
    emit("command-panel-show");
    selectedCommandIndex.value = 0;
    if (searchDebounceTimer) {
      clearTimeout(searchDebounceTimer);
    }
    searchDebounceTimer = setTimeout(() => {
      emit("command-search", afterTrigger);
    }, props.commandSearchDelay);
  } else {
    hideCommandPanel();
  }
};
const filterCommands = (keyword) => {
  if (!keyword) {
    filteredCommands.value = props.commands.filter((cmd) => !cmd.disabled);
    return;
  }
  const lowerKeyword = keyword.toLowerCase();
  filteredCommands.value = props.commands.filter((cmd) => {
    if (cmd.disabled) return false;
    if (cmd.label.toLowerCase().includes(lowerKeyword)) return true;
    if (cmd.keywords?.some((k) => k.toLowerCase().includes(lowerKeyword))) return true;
    return false;
  });
};
const getAllVisibleCommands = () => {
  const commands = [...filteredCommands.value];
  if (currentCascadeParent.value?.children?.length) {
    commands.push(...currentCascadeParent.value.children);
  }
  return commands;
};
const navigateCommands = (direction) => {
  const commands = getAllVisibleCommands();
  if (commands.length === 0) return;
  selectedCommandIndex.value = (selectedCommandIndex.value + direction + commands.length) % commands.length;
};
const selectCommand = (command) => {
  if (command.disabled) return;
  if (command.children?.length && props.enableCommandCascade) {
    openCascadePanel(command);
    emit("command-cascade", command, currentCascadeParent.value);
    return;
  }
  insertCommandToText(command);
  emit("command-select", command, currentCascadeParent.value || void 0);
  hideCommandPanel();
};
const openCascadePanel = (parent) => {
  currentCascadeParent.value = parent;
  cascadeCommands.value = parent.children || [];
  showCascadePanel.value = true;
  nextTick(() => {
    const panel = panelRef.value;
    if (panel) {
      const rect = panel.getBoundingClientRect();
      cascadePosition.value = {
        left: rect.width + props.cascadeOffset,
        top: 0
      };
    }
  });
};
const insertCommandToText = (command) => {
  const cursorPos = localValue.value.lastIndexOf(props.commandTrigger);
  if (cursorPos !== -1) {
    const before = localValue.value.slice(0, cursorPos);
    const after = localValue.value.slice(cursorPos).replace(props.commandTrigger + commandSearchText.value, "");
    localValue.value = before + command.label + after;
    innerValue.value = localValue.value;
  }
};
const hideCommandPanel = () => {
  if (searchDebounceTimer) {
    clearTimeout(searchDebounceTimer);
    searchDebounceTimer = null;
  }
  showCommandPanel.value = false;
  showCascadePanel.value = false;
  commandSearchText.value = "";
  filteredCommands.value = [];
  currentCascadeParent.value = null;
  emit("command-panel-hide");
};
const handleSend = () => {
  if (!innerValue.value.trim() || props.loading || props.disabled) return;
  emit("send", innerValue.value);
  innerValue.value = "";
  nextTick(() => {
    if (textareaRef.value) {
      textareaRef.value.style.height = "auto";
    }
    hideCommandPanel();
  });
};
const handleRemoveAttachment = (index, item) => {
  emit("remove-attachment", index, item);
};
const commandPanelStyle = computed(() => ({
  width: typeof props.commandPanelWidth === "number" ? props.commandPanelWidth + "px" : props.commandPanelWidth,
  maxHeight: props.commandPanelMaxHeight + "px"
}));
const getCommandIcon = (command) => {
  if (command.icon) return command.icon;
  return "command";
};
const triggerCommand = (keyword) => {
  showCommandPanel.value = true;
  commandSearchText.value = keyword;
  filterCommands(keyword);
  selectedCommandIndex.value = 0;
  emit("command-panel-show");
  emit("command-search", keyword);
};
defineExpose({
  focus: () => textareaRef.value?.focus(),
  blur: () => textareaRef.value?.blur(),
  clear: () => {
    localValue.value = "";
    hideCommandPanel();
  },
  triggerCommand
});
</script>

<template>
  <div
    ref="editorRef"
    :class="[ns.b(), ns.is('disabled', disabled), ns.is('loading', loading), ns.is('focused', isFocused)]"
    :style="themeStyle"
  >
    <!-- Header (Attachments bar) -->
    <div
      v-if="attachments.length > 0 || $slots.header"
      :class="[ns.e('header'), ns.is('empty', attachments.length === 0 && !$slots.header)]"
    >
      <slot name="header">
        <div v-for="(item, index) in attachments" :key="index" :class="ns.e('attachment')">
          <YhIcon :name="item.url ? 'image' : 'document'" :class="ns.e('attachment-icon')" />
          <span>{{ item.name }}</span>
          <YhIcon
            name="close"
            :class="ns.e('attachment-remove')"
            @click="handleRemoveAttachment(index, item)"
          />
        </div>
      </slot>
    </div>

    <!-- Body (Textarea) -->
    <div :class="ns.e('body')">
      <textarea
        ref="textareaRef"
        v-model="innerValue"
        :class="ns.e('textarea')"
        :placeholder="placeholder ?? t('ai.sender.placeholder')"
        :disabled="disabled || loading"
        :maxlength="maxLength"
        :rows="rows"
        @focus="isFocused = true"
        @blur="isFocused = false"
        @input="handleInput"
        @keydown="handleKeyDown"
        @compositionstart="handleCompositionStart"
        @compositionend="handleCompositionEnd"
      ></textarea>
    </div>

    <!-- Footer (Toolbar & Actions) -->
    <div :class="ns.e('footer')">
      <!-- Toolbar (Shortcuts) -->
      <div :class="ns.e('toolbar')">
        <slot name="toolbar"></slot>
      </div>

      <!-- Actions -->
      <div :class="ns.e('actions')">
        <span v-if="showWordLimit && maxLength" :class="ns.e('word-limit')">
          {{ innerValue.length }} / {{ maxLength }}
        </span>

        <slot name="actions"></slot>

        <slot
          name="submit"
          :submit="handleSend"
          :disabled="!innerValue.trim() || disabled || loading"
          :loading="loading"
        >
          <YhButton
            :type="innerValue.trim() && !disabled && !loading ? 'primary' : 'default'"
            :class="ns.e('send-btn')"
            :disabled="!innerValue.trim() || disabled"
            :loading="loading"
            circle
            @click="handleSend"
          >
            <template #icon>
              <YhIcon name="send" />
            </template>
          </YhButton>
        </slot>
      </div>
    </div>

    <!-- Command Panel -->
    <Teleport to="body">
      <Transition name="yh-fade-in-scale-up">
        <div
          v-if="showCommandPanel && enableCommands"
          :class="ns.e('command-panel-wrapper')"
          :style="commandWrapperStyle"
        >
          <div ref="panelRef" :class="ns.e('command-panel')" :style="commandPanelStyle">
            <div v-if="commandSearchText" :class="ns.e('command-search')">
              <YhIcon name="search" />
              <span>{{ commandSearchText }}</span>
            </div>

            <div :class="ns.e('command-list')">
              <!-- 主命令列表 -->
              <template v-for="(command, index) in filteredCommands" :key="command.id">
                <div
                  :class="[ns.e('command-item'), ns.is('selected', selectedCommandIndex === index), ns.is('disabled', command.disabled), ns.is('parent', !!command.children?.length)]"
                  @click="selectCommand(command)"
                  @mouseenter="selectedCommandIndex = index"
                >
                  <YhIcon
                    v-if="showCommandIcon"
                    :name="getCommandIcon(command)"
                    :class="ns.e('command-icon')"
                  />
                  <div :class="ns.e('command-content')">
                    <span :class="ns.e('command-label')">{{ command.label }}</span>
                    <span
                      v-if="showCommandDescription && command.description"
                      :class="ns.e('command-desc')"
                    >
                      {{ command.description }}
                    </span>
                  </div>
                  <YhIcon
                    v-if="command.children?.length"
                    name="chevron-right"
                    :class="ns.e('command-arrow')"
                  />
                </div>

                <!-- 嵌套子命令 -->
                <template v-if="showCascadePanel && currentCascadeParent?.id === command.id">
                  <div
                    v-for="(child, childIndex) in cascadeCommands"
                    :key="`${command.id}-${child.id}`"
                    :class="[ns.e('command-item'), ns.e('command-cascade-item'), ns.is('selected', selectedCommandIndex === filteredCommands.length + childIndex), ns.is('disabled', child.disabled)]"
                    @click="selectCommand(child)"
                    @mouseenter="selectedCommandIndex = filteredCommands.length + childIndex"
                  >
                    <YhIcon
                      v-if="showCommandIcon"
                      :name="getCommandIcon(child)"
                      :class="ns.e('command-icon')"
                    />
                    <div :class="ns.e('command-content')">
                      <span :class="ns.e('command-label')">{{ child.label }}</span>
                      <span
                        v-if="showCommandDescription && child.description"
                        :class="ns.e('command-desc')"
                      >
                        {{ child.description }}
                      </span>
                    </div>
                  </div>
                </template>
              </template>

              <div v-if="filteredCommands.length === 0" :class="ns.e('command-empty')">
                <YhIcon name="search" />
                <span>{{
                  t("ai.sender.noCommands") === "ai.sender.noCommands" ? t("common.close") === "\u5173\u95ED" ? "\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684\u547D\u4EE4" : "No matching commands found" : t("ai.sender.noCommands")
                }}</span>
              </div>
            </div>
          </div>
        </div>
      </Transition>
    </Teleport>
  </div>
</template>

<style>
@charset "UTF-8";
/**
 * YH-UI Sass Variables
 * 原具体的 CSS 变量输出规则 (:root, html.dark 等) 已移至 root.scss，以防止各组件样式编译时产生重复的全局变量 CSS 冗余。
 */
.yh-ai-editor-sender {
  --yh-ai-editor-sender-bg: var(--yh-bg-color-overlay);
  --yh-ai-editor-sender-border: 1px solid var(--yh-border-color);
  --yh-ai-editor-sender-radius: 12px;
  --yh-ai-editor-sender-focus-border: 1px solid var(--yh-color-primary);
  --yh-ai-editor-sender-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
  width: 100%;
  background-color: var(--yh-ai-editor-sender-bg);
  border: var(--yh-ai-editor-sender-border);
  border-radius: var(--yh-ai-editor-sender-radius);
  transition: all 0.3s;
  overflow: hidden;
  box-shadow: var(--yh-ai-editor-sender-shadow);
}
.yh-ai-editor-sender.is-focused {
  border-color: var(--yh-color-primary);
  box-shadow: 0 6px 20px rgba(var(--yh-color-primary-rgb), 0.15);
}

.yh-ai-editor-sender__header {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 8px 12px;
  min-height: 20px;
}
.yh-ai-editor-sender__header.is-empty {
  display: none;
}

.yh-ai-editor-sender__attachment {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 8px;
  background-color: var(--yh-fill-color-light);
  border-radius: 6px;
  font-size: 12px;
  color: var(--yh-text-color-primary);
  border: 1px solid var(--yh-border-color-lighter);
  position: relative;
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.yh-ai-editor-sender__attachment:hover .yh-ai-editor-sender__attachment-remove {
  opacity: 1;
}

.yh-ai-editor-sender__attachment-icon {
  font-size: 14px;
  color: var(--yh-color-primary);
}

.yh-ai-editor-sender__attachment-remove {
  cursor: pointer;
  font-size: 12px;
  opacity: 0.6;
  transition: opacity 0.2s;
}
.yh-ai-editor-sender__attachment-remove:hover {
  color: var(--yh-color-danger);
}

.yh-ai-editor-sender__body {
  display: flex;
  flex-direction: column;
  padding: 0 12px;
}

.yh-ai-editor-sender__textarea {
  width: 100%;
  min-height: 32px;
  max-height: 300px;
  border: none;
  outline: none;
  background-color: transparent;
  font-family: inherit;
  font-size: 14px;
  color: var(--yh-text-color-primary);
  resize: none;
  padding: 8px 0;
  line-height: 1.5;
}
.yh-ai-editor-sender__textarea::placeholder {
  color: var(--yh-text-color-placeholder);
}

.yh-ai-editor-sender__footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px;
  border-top: 1px solid var(--yh-border-color-lighter);
  gap: 12px;
}

.yh-ai-editor-sender__toolbar {
  display: flex;
  align-items: center;
  gap: 6px;
}

.yh-ai-editor-sender__actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.yh-ai-editor-sender__word-limit {
  font-size: 12px;
  color: var(--yh-text-color-placeholder);
  margin-right: 8px;
}

.yh-ai-editor-sender__command-panel-wrapper {
  position: fixed;
  z-index: 9999;
  top: 200px;
  left: 100px;
}

.yh-ai-editor-sender__command-panel {
  background: var(--yh-bg-color-overlay);
  border: 1px solid var(--yh-border-color-lighter);
  border-radius: 12px;
  box-shadow: var(--yh-shadow-lg);
  overflow: hidden;
}

.yh-ai-editor-sender__command-search {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  background: var(--yh-fill-color-light);
  border-bottom: 1px solid var(--yh-border-color-lighter);
  font-size: 13px;
  color: var(--yh-text-color-secondary);
}

.yh-ai-editor-sender__command-list {
  max-height: 280px;
  overflow: auto;
}

.yh-ai-editor-sender__command-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  cursor: pointer;
  transition: background-color 0.2s;
}
.yh-ai-editor-sender__command-item:hover {
  background: var(--yh-fill-color-light);
}
.yh-ai-editor-sender__command-item.is-selected {
  background: var(--yh-fill-color-light);
}
.yh-ai-editor-sender__command-item.is-disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.yh-ai-editor-sender__command-item.is-parent {
  font-weight: 500;
}

.yh-ai-editor-sender__command-cascade-item {
  padding-left: 28px;
  background: var(--yh-fill-color-extra-light);
}

.yh-ai-editor-sender__command-icon {
  font-size: 16px;
  color: var(--yh-color-primary);
}

.yh-ai-editor-sender__command-content {
  flex: 1;
  min-width: 0;
}

.yh-ai-editor-sender__command-label {
  font-size: 14px;
  font-weight: 500;
  color: var(--yh-text-color-primary);
}

.yh-ai-editor-sender__command-desc {
  display: block;
  font-size: 12px;
  color: var(--yh-text-color-secondary);
  margin-top: 2px;
}

.yh-ai-editor-sender__command-arrow {
  font-size: 14px;
  color: var(--yh-text-color-placeholder);
}

.yh-ai-editor-sender__command-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 24px;
  color: var(--yh-text-color-placeholder);
  font-size: 14px;
}
</style>
