<script setup>
import { computed, ref, useSlots } from "vue";
import { useNamespace, useFormItem, useLocale } from "@yh-ui/hooks";
import { useComponentTheme } from "@yh-ui/theme";
import { useConfig } from "@yh-ui/hooks";
defineOptions({
  name: "YhInputTag"
});
const props = defineProps({
  modelValue: { type: Array, required: false, default: () => [] },
  type: { type: null, required: false, default: "primary" },
  size: { type: null, required: false, default: "default" },
  disabled: { type: Boolean, required: false, default: false },
  readonly: { type: Boolean, required: false, default: false },
  max: { type: Number, required: false },
  separator: { type: [String, Array], required: false, default: () => [",", "Enter"] },
  placeholder: { type: String, required: false, default: void 0 },
  clearable: { type: Boolean, required: false, default: false },
  addOnBlur: { type: Boolean, required: false, default: true },
  prefix: { type: String, required: false },
  suffix: { type: String, required: false },
  prefixIcon: { type: null, required: false },
  suffixIcon: { type: null, required: false },
  closable: { type: Boolean, required: false, default: true },
  validateTag: { type: Function, required: false },
  trim: { type: Boolean, required: false, default: true },
  allowDuplicate: { type: Boolean, required: false, default: false },
  collapseTags: { type: Boolean, required: false, default: false },
  collapseTagsTooltip: { type: Boolean, required: false, default: false },
  maxCollapseTags: { type: Number, required: false, default: 1 },
  draggable: { type: Boolean, required: false, default: false },
  tagEffect: { type: String, required: false, default: "light" },
  validateEvent: { type: Boolean, required: false, default: true },
  themeOverrides: { type: null, required: false }
});
const emit = defineEmits(["update:modelValue", "change", "input", "add", "remove", "focus", "blur", "clear", "drag-end"]);
const slots = useSlots();
const ns = useNamespace("input-tag");
const { t } = useLocale();
const { themeStyle } = useComponentTheme(
  "input-tag",
  computed(() => props.themeOverrides)
);
const { globalSize } = useConfig();
const inputRef = ref();
const wrapperRef = ref();
const { validate: triggerValidate } = useFormItem();
const inputValue = ref("");
const focused = ref(false);
const hovering = ref(false);
const showTooltip = ref(false);
const dragIndex = ref(null);
const dragOverIndex = ref(null);
const tags = computed(() => props.modelValue || []);
const displayTags = computed(() => {
  if (!props.collapseTags) {
    return tags.value;
  }
  return tags.value.slice(0, props.maxCollapseTags);
});
const collapsedTags = computed(() => {
  if (!props.collapseTags) {
    return [];
  }
  return tags.value.slice(props.maxCollapseTags);
});
const collapsedCount = computed(() => collapsedTags.value.length);
const isMaxReached = computed(() => {
  return props.max !== void 0 && tags.value.length >= props.max;
});
const showClear = computed(() => {
  return props.clearable && !props.disabled && !props.readonly && tags.value.length > 0 && (focused.value || hovering.value);
});
const hasPrefix = computed(() => {
  return !!props.prefix || !!props.prefixIcon || !!slots.prefix;
});
const hasSuffix = computed(() => {
  return !!props.suffix || !!props.suffixIcon || !!slots.suffix || showClear.value;
});
const separators = computed(() => {
  if (Array.isArray(props.separator)) {
    return props.separator;
  }
  return [props.separator];
});
const inputTagClasses = computed(() => [
  ns.b(),
  ns.m(props.size || globalSize.value || "default"),
  ns.is("disabled", props.disabled),
  ns.is("focused", focused.value),
  ns.is("has-prefix", hasPrefix.value),
  ns.is("has-suffix", hasSuffix.value)
]);
const getTagClasses = (index) => [
  ns.e("tag"),
  `is-${props.type}`,
  `is-${props.tagEffect}`,
  {
    "is-dragging": dragIndex.value === index,
    "is-drag-over": dragOverIndex.value === index
  }
];
const addTag = (value) => {
  let tagValue = value;
  if (props.trim) {
    tagValue = tagValue.trim();
  }
  if (!tagValue) {
    return false;
  }
  if (isMaxReached.value) {
    return false;
  }
  if (!props.allowDuplicate && tags.value.includes(tagValue)) {
    return false;
  }
  if (props.validateTag && !props.validateTag(tagValue)) {
    return false;
  }
  const newTags = [...tags.value, tagValue];
  emit("update:modelValue", newTags);
  emit("change", newTags);
  emit("add", tagValue);
  if (props.validateEvent) {
    triggerValidate("change");
  }
  return true;
};
const removeTag = (index) => {
  if (props.disabled || props.readonly) {
    return;
  }
  const removedTag = tags.value[index];
  const newTags = tags.value.filter((_, i) => i !== index);
  emit("update:modelValue", newTags);
  emit("change", newTags);
  emit("remove", removedTag, index);
  if (props.validateEvent) {
    triggerValidate("change");
  }
};
const clearTags = () => {
  if (props.disabled || props.readonly) {
    return;
  }
  emit("update:modelValue", []);
  emit("change", []);
  emit("clear");
  inputValue.value = "";
};
const handleInput = (event) => {
  const target = event.target;
  inputValue.value = target.value;
  emit("input", target.value);
};
const handleKeydown = (event) => {
  if (props.disabled || props.readonly) {
    return;
  }
  const value = inputValue.value;
  if (separators.value.includes(event.key)) {
    event.preventDefault();
    if (addTag(value)) {
      inputValue.value = "";
    }
    return;
  }
  if (event.key === "Backspace" && !value && tags.value.length > 0) {
    removeTag(tags.value.length - 1);
  }
};
const handleFocus = (event) => {
  focused.value = true;
  emit("focus", event);
};
const handleBlur = (event) => {
  focused.value = false;
  if (props.addOnBlur && inputValue.value) {
    if (addTag(inputValue.value)) {
      inputValue.value = "";
    }
  }
  emit("blur", event);
  if (props.validateEvent) {
    triggerValidate("blur");
  }
};
const handleWrapperClick = () => {
  if (!props.disabled && !props.readonly) {
    inputRef.value?.focus();
  }
};
const handleMouseenter = () => {
  hovering.value = true;
};
const handleMouseleave = () => {
  hovering.value = false;
};
const handleCollapseMouseenter = () => {
  if (props.collapseTagsTooltip && collapsedCount.value > 0) {
    showTooltip.value = true;
  }
};
const handleCollapseMouseleave = () => {
  showTooltip.value = false;
};
const handleDragStart = (event, index) => {
  if (!props.draggable || props.disabled || props.readonly) {
    event.preventDefault();
    return;
  }
  dragIndex.value = index;
  if (event.dataTransfer) {
    event.dataTransfer.effectAllowed = "move";
    event.dataTransfer.setData("text/plain", String(index));
  }
};
const handleDragOver = (event, index) => {
  if (!props.draggable) return;
  event.preventDefault();
  if (event.dataTransfer) {
    event.dataTransfer.dropEffect = "move";
  }
  dragOverIndex.value = index;
};
const handleDragLeave = () => {
  dragOverIndex.value = null;
};
const handleDrop = (event, targetIndex) => {
  if (!props.draggable) return;
  event.preventDefault();
  const sourceIndex = dragIndex.value;
  if (sourceIndex === null || sourceIndex === targetIndex) {
    dragIndex.value = null;
    dragOverIndex.value = null;
    return;
  }
  const newTags = [...tags.value];
  const [removed] = newTags.splice(sourceIndex, 1);
  newTags.splice(targetIndex, 0, removed);
  emit("update:modelValue", newTags);
  emit("change", newTags);
  emit("drag-end", newTags);
  dragIndex.value = null;
  dragOverIndex.value = null;
};
const handleDragEnd = () => {
  dragIndex.value = null;
  dragOverIndex.value = null;
};
const focus = () => {
  inputRef.value?.focus();
};
const blur = () => {
  inputRef.value?.blur();
};
const clear = () => {
  clearTags();
};
defineExpose({
  focus,
  blur,
  clear
});
</script>

<template>
  <div
    ref="wrapperRef"
    :class="inputTagClasses"
    :style="themeStyle"
    @click="handleWrapperClick"
    @mouseenter="handleMouseenter"
    @mouseleave="handleMouseleave"
  >
    <!-- 前缀 -->
    <span v-if="hasPrefix" :class="ns.e('prefix')">
      <slot name="prefix">
        <span v-if="prefix" :class="ns.e('prefix-text')">{{ prefix }}</span>
        <component v-if="prefixIcon && typeof prefixIcon !== 'string'" :is="prefixIcon" />
      </slot>
    </span>

    <!-- 标签区域 -->
    <div :class="ns.e('wrapper')">
      <!-- 已有标签 -->
      <template v-for="(tag, index) in displayTags" :key="index">
        <slot name="tag" :tag="tag" :index="index" :close="() => removeTag(index)">
          <span
            :class="getTagClasses(index)"
            :draggable="draggable && !disabled && !readonly"
            @dragstart="handleDragStart($event, index)"
            @dragover="handleDragOver($event, index)"
            @dragleave="handleDragLeave"
            @drop="handleDrop($event, index)"
            @dragend="handleDragEnd"
          >
            <span :class="ns.e('tag-content')">{{ tag }}</span>
            <span
              v-if="closable && !disabled && !readonly"
              :class="ns.e('tag-close')"
              @click.stop="removeTag(index)"
            >
              <svg
                viewBox="0 0 1024 1024"
                xmlns="http://www.w3.org/2000/svg"
                width="1em"
                height="1em"
              >
                <path
                  fill="currentColor"
                  d="M576 512l277.333333 277.333333-64 64-277.333333-277.333333L234.666667 853.333333 170.666667 789.333333l277.333333-277.333333L170.666667 234.666667 234.666667 170.666667l277.333333 277.333333L789.333333 170.666667 853.333333 234.666667 576 512z"
                />
              </svg>
            </span>
          </span>
        </slot>
      </template>

      <!-- 折叠标签 -->
      <span
        v-if="collapseTags && collapsedCount > 0"
        :class="[ns.e('collapsed'), `is-${type}`, `is-${tagEffect}`]"
        @mouseenter="handleCollapseMouseenter"
        @mouseleave="handleCollapseMouseleave"
      >
        <slot name="collapseTag" :count="collapsedCount" :tags="collapsedTags">
          + {{ collapsedCount }}
        </slot>

        <!-- Tooltip -->
        <transition name="yh-fade">
          <div v-if="showTooltip && collapseTagsTooltip" :class="ns.e('tooltip')">
            <span
              v-for="(tag, index) in collapsedTags"
              :key="index"
              :class="[ns.e('tooltip-tag'), `is-${type}`, `is-${tagEffect}`]"
            >
              {{ tag }}
            </span>
          </div>
        </transition>
      </span>

      <!-- 输入框 -->
      <input
        ref="inputRef"
        type="text"
        :class="ns.e('inner')"
        :value="inputValue"
        :placeholder="tags.length === 0 ? placeholder ?? t('inputtag.placeholder') : ''"
        :disabled="disabled"
        :readonly="readonly || isMaxReached"
        @input="handleInput"
        @keydown="handleKeydown"
        @focus="handleFocus"
        @blur="handleBlur"
      />
    </div>

    <!-- 后缀/清空按钮 -->
    <span v-if="hasSuffix" :class="ns.e('suffix')">
      <!-- 清空按钮 -->
      <span v-if="showClear" :class="ns.e('clear')" @click.stop="clearTags">
        <slot name="clearIcon">
          <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em">
            <path
              fill="currentColor"
              d="M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896zm0 832a384 384 0 1 0 0-768 384 384 0 0 0 0 768zm-160-448l128 128-128 128 45.248 45.248L525.248 621.248l128 128L698.496 704l-128-128 128-128L653.248 402.752 525.248 530.752l-128-128z"
            />
          </svg>
        </slot>
      </span>
      <slot name="suffix">
        <span v-if="suffix" :class="ns.e('suffix-text')">{{ suffix }}</span>
        <component v-if="suffixIcon && typeof suffixIcon !== 'string'" :is="suffixIcon" />
      </slot>
    </span>
  </div>
</template>

<style>
@charset "UTF-8";
/**
 * YH-UI Sass Variables
 * 原具体的 CSS 变量输出规则 (:root, html.dark 等) 已移至 root.scss，以防止各组件样式编译时产生重复的全局变量 CSS 冗余。
 */
.yh-input-tag {
  --yh-input-tag-min-height: var(--yh-component-size-default);
  --yh-input-tag-font-size: var(--yh-font-size-base);
  --yh-input-tag-bg-color: var(--yh-fill-color-blank);
  --yh-input-tag-border-color: var(--yh-border-color);
  --yh-input-tag-text-color: var(--yh-text-color-regular);
  --yh-input-tag-placeholder-color: var(--yh-text-color-placeholder);
  --yh-input-tag-hover-border-color: var(--yh-input-tag-focus-border-color);
  --yh-input-tag-focus-border-color: var(--yh-color-primary);
  --yh-input-tag-tag-height: 22px;
  --yh-input-tag-gap: 4px;
  position: relative;
  display: flex;
  align-items: center;
  min-height: var(--yh-input-tag-min-height);
  padding: 2px 8px;
  font-size: var(--yh-input-tag-font-size);
  background-color: var(--yh-input-tag-bg-color);
  border: var(--yh-border-width) var(--yh-border-style) var(--yh-input-tag-border-color);
  border-radius: var(--yh-radius-base);
  box-sizing: border-box;
  cursor: text;
  transition: var(--yh-transition-fast);
}
.yh-input-tag:hover:not(.is-disabled) {
  border-color: var(--yh-input-tag-hover-border-color);
}
.yh-input-tag__prefix {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  color: var(--yh-text-color-placeholder);
  margin-right: 8px;
}

.yh-input-tag__suffix {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  color: var(--yh-text-color-placeholder);
  margin-left: 8px;
}

.yh-input-tag__prefix-text {
  font-size: inherit;
  white-space: nowrap;
}

.yh-input-tag__suffix-text {
  font-size: inherit;
  white-space: nowrap;
}

.yh-input-tag__wrapper {
  flex: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--yh-input-tag-gap);
  min-width: 0;
}

.yh-input-tag__tag {
  display: inline-flex;
  align-items: center;
  height: var(--yh-input-tag-tag-height);
  padding: 0 6px;
  font-size: var(--yh-font-size-xs);
  line-height: 1;
  border-radius: var(--yh-radius-sm);
  box-sizing: border-box;
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
  cursor: default;
  transition: var(--yh-transition-fast);
}
.yh-input-tag__tag.is-light.is-primary {
  color: var(--yh-color-primary);
  background-color: var(--yh-color-primary-light-9);
  border: 1px solid var(--yh-color-primary-light-7);
}

.yh-input-tag__tag.is-light.is-success {
  color: var(--yh-color-success);
  background-color: var(--yh-color-success-light-9);
  border: 1px solid var(--yh-color-success-light-7);
}

.yh-input-tag__tag.is-light.is-warning {
  color: var(--yh-color-warning);
  background-color: var(--yh-color-warning-light-9);
  border: 1px solid var(--yh-color-warning-light-7);
}

.yh-input-tag__tag.is-light.is-danger {
  color: var(--yh-color-danger);
  background-color: var(--yh-color-danger-light-9);
  border: 1px solid var(--yh-color-danger-light-7);
}

.yh-input-tag__tag.is-light.is-info {
  color: var(--yh-color-info);
  background-color: var(--yh-color-info-light-9);
  border: 1px solid var(--yh-color-info-light-7);
}

.yh-input-tag__tag.is-dark {
  color: #fff;
}
.yh-input-tag__tag.is-dark.is-primary {
  background-color: var(--yh-color-primary);
  border: 1px solid var(--yh-color-primary);
}

.yh-input-tag__tag.is-dark.is-success {
  background-color: var(--yh-color-success);
  border: 1px solid var(--yh-color-success);
}

.yh-input-tag__tag.is-dark.is-warning {
  background-color: var(--yh-color-warning);
  border: 1px solid var(--yh-color-warning);
}

.yh-input-tag__tag.is-dark.is-danger {
  background-color: var(--yh-color-danger);
  border: 1px solid var(--yh-color-danger);
}

.yh-input-tag__tag.is-dark.is-info {
  background-color: var(--yh-color-info);
  border: 1px solid var(--yh-color-info);
}

.yh-input-tag__tag.is-plain {
  background-color: transparent;
}
.yh-input-tag__tag.is-plain.is-primary {
  color: var(--yh-color-primary);
  border: 1px solid var(--yh-color-primary-light-5);
}

.yh-input-tag__tag.is-plain.is-success {
  color: var(--yh-color-success);
  border: 1px solid var(--yh-color-success-light-5);
}

.yh-input-tag__tag.is-plain.is-warning {
  color: var(--yh-color-warning);
  border: 1px solid var(--yh-color-warning-light-5);
}

.yh-input-tag__tag.is-plain.is-danger {
  color: var(--yh-color-danger);
  border: 1px solid var(--yh-color-danger-light-5);
}

.yh-input-tag__tag.is-plain.is-info {
  color: var(--yh-color-info);
  border: 1px solid var(--yh-color-info-light-5);
}

.yh-input-tag__tag.is-dragging {
  opacity: 0.5;
}

.yh-input-tag__tag.is-drag-over {
  transform: translateX(4px);
}

.yh-input-tag__tag[draggable=true] {
  cursor: grab;
}
.yh-input-tag__tag[draggable=true]:active {
  cursor: grabbing;
}

.yh-input-tag__tag-content {
  overflow: hidden;
  text-overflow: ellipsis;
}

.yh-input-tag__tag-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  margin-left: 2px;
  color: inherit;
  cursor: pointer;
  border-radius: 50%;
  transition: var(--yh-transition-fast);
}
.yh-input-tag__tag-close svg {
  width: 10px;
  height: 10px;
}
.yh-input-tag__tag-close:hover {
  background-color: currentColor;
}
.yh-input-tag__tag-close:hover svg {
  color: var(--yh-fill-color-blank);
}

.yh-input-tag__collapsed {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--yh-input-tag-tag-height);
  padding: 0 6px;
  font-size: var(--yh-font-size-xs);
  line-height: 1;
  border-radius: var(--yh-radius-sm);
  cursor: pointer;
  transition: var(--yh-transition-fast);
}
.yh-input-tag__collapsed.is-light.is-primary {
  color: var(--yh-color-primary);
  background-color: var(--yh-color-primary-light-9);
  border: 1px solid var(--yh-color-primary-light-7);
}

.yh-input-tag__collapsed.is-light.is-success {
  color: var(--yh-color-success);
  background-color: var(--yh-color-success-light-9);
  border: 1px solid var(--yh-color-success-light-7);
}

.yh-input-tag__collapsed.is-light.is-warning {
  color: var(--yh-color-warning);
  background-color: var(--yh-color-warning-light-9);
  border: 1px solid var(--yh-color-warning-light-7);
}

.yh-input-tag__collapsed.is-light.is-danger {
  color: var(--yh-color-danger);
  background-color: var(--yh-color-danger-light-9);
  border: 1px solid var(--yh-color-danger-light-7);
}

.yh-input-tag__collapsed.is-light.is-info {
  color: var(--yh-text-color-secondary);
  background-color: var(--yh-fill-color-light);
  border: 1px solid var(--yh-border-color-light);
}

.yh-input-tag__collapsed:hover {
  background-color: var(--yh-fill-color);
}

.yh-input-tag__tooltip {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  max-width: 300px;
  padding: 8px 12px;
  background-color: var(--yh-bg-color-overlay);
  border: 1px solid var(--yh-border-color-light);
  border-radius: var(--yh-radius-base);
  box-shadow: var(--yh-box-shadow-light);
  z-index: 2000;
}
.yh-input-tag__tooltip::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 6px solid transparent;
  border-top-color: var(--yh-bg-color-overlay);
}

.yh-input-tag__tooltip-tag {
  display: inline-block;
  padding: 2px 6px;
  font-size: var(--yh-font-size-xs);
  border-radius: var(--yh-radius-sm);
}
.yh-input-tag__tooltip-tag.is-light.is-primary {
  color: var(--yh-color-primary);
  background-color: var(--yh-color-primary-light-9);
}

.yh-input-tag__tooltip-tag.is-light.is-success {
  color: var(--yh-color-success);
  background-color: var(--yh-color-success-light-9);
}

.yh-input-tag__tooltip-tag.is-light.is-warning {
  color: var(--yh-color-warning);
  background-color: var(--yh-color-warning-light-9);
}

.yh-input-tag__tooltip-tag.is-light.is-danger {
  color: var(--yh-color-danger);
  background-color: var(--yh-color-danger-light-9);
}

.yh-input-tag__tooltip-tag.is-light.is-info {
  color: var(--yh-color-info);
  background-color: var(--yh-color-info-light-9);
}

.yh-input-tag__inner {
  flex: 1;
  min-width: 50px;
  height: var(--yh-input-tag-tag-height);
  padding: 0;
  font-size: inherit;
  font-family: inherit;
  color: var(--yh-input-tag-text-color);
  background: transparent;
  border: none;
  outline: none;
}
.yh-input-tag__inner::placeholder {
  color: var(--yh-input-tag-placeholder-color);
}

.yh-input-tag__clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  color: var(--yh-text-color-placeholder);
  cursor: pointer;
  transition: var(--yh-transition-fast);
}
.yh-input-tag__clear svg {
  width: 14px;
  height: 14px;
}
.yh-input-tag__clear:hover {
  color: var(--yh-text-color-secondary);
}

.yh-input-tag.is-focused {
  border-color: var(--yh-input-tag-focus-border-color);
  box-shadow: 0 0 0 2px var(--yh-color-primary-light-8);
}

.yh-input-tag.is-disabled {
  background-color: var(--yh-fill-color-light);
  cursor: not-allowed;
}
.yh-input-tag.is-disabled .yh-input-tag__inner {
  cursor: not-allowed;
}

.yh-input-tag.is-disabled .yh-input-tag__tag {
  opacity: 0.6;
}

.yh-input-tag--large {
  --yh-input-tag-min-height: var(--yh-component-size-large);
  --yh-input-tag-font-size: var(--yh-font-size-md);
  --yh-input-tag-tag-height: 26px;
  --yh-input-tag-gap: 6px;
  padding: 4px 12px;
}
.yh-input-tag--large .yh-input-tag__tag {
  padding: 0 8px;
  font-size: var(--yh-font-size-sm);
}

.yh-input-tag--large .yh-input-tag__collapsed {
  padding: 0 8px;
  font-size: var(--yh-font-size-sm);
}

.yh-input-tag--large .yh-input-tag__tag-close {
  width: 16px;
  height: 16px;
}
.yh-input-tag--large .yh-input-tag__tag-close svg {
  width: 12px;
  height: 12px;
}

.yh-input-tag--small {
  --yh-input-tag-min-height: var(--yh-component-size-small);
  --yh-input-tag-font-size: var(--yh-font-size-xs);
  --yh-input-tag-tag-height: 18px;
  --yh-input-tag-gap: 2px;
  padding: 1px 6px;
}
.yh-input-tag--small .yh-input-tag__tag {
  padding: 0 4px;
  font-size: 11px;
}

.yh-input-tag--small .yh-input-tag__collapsed {
  padding: 0 4px;
  font-size: 11px;
}

.yh-input-tag--small .yh-input-tag__tag-close {
  width: 12px;
  height: 12px;
  margin-left: 1px;
}
.yh-input-tag--small .yh-input-tag__tag-close svg {
  width: 8px;
  height: 8px;
}

.yh-fade-enter-active,
.yh-fade-leave-active {
  transition: opacity var(--yh-duration-fast) var(--yh-timing-ease-in-out);
}

.yh-fade-enter-from,
.yh-fade-leave-to {
  opacity: 0;
}
</style>
