<script setup>
import { ref, computed, provide, watch, onMounted, nextTick } from "vue";
import { useNamespace } from "@yh-ui/hooks";
import { useComponentTheme } from "@yh-ui/theme";
import { tabsProps, tabsEmits, TABS_INJECTION_KEY } from "./tabs";
defineOptions({
  name: "YhTabs"
});
const props = defineProps(tabsProps);
const emit = defineEmits(tabsEmits);
const ns = useNamespace("tabs");
const { themeStyle } = useComponentTheme(
  "tabs",
  computed(() => props.themeOverrides)
);
const panes = ref([]);
const activeTab = ref(props.modelValue);
const navRef = ref();
const indicatorRef = ref();
const rootRef = ref();
watch(
  () => props.modelValue,
  (val) => {
    activeTab.value = val;
  }
);
const registerPane = (pane) => {
  const index = panes.value.findIndex((p) => p.name === pane.name);
  if (index === -1) {
    panes.value.push(pane);
  } else {
    panes.value[index] = pane;
  }
};
const unregisterPane = (name) => {
  const index = panes.value.findIndex((p) => p.name === name);
  if (index > -1) {
    const isCurrentActive = activeTab.value === name;
    panes.value.splice(index, 1);
    if (isCurrentActive) {
      let nextActivePane = panes.value[index];
      if (!nextActivePane || nextActivePane.disabled) {
        for (let i = index - 1; i >= 0; i--) {
          if (!panes.value[i].disabled) {
            nextActivePane = panes.value[i];
            break;
          }
        }
      }
      if (!nextActivePane || nextActivePane.disabled) {
        for (let i = index; i < panes.value.length; i++) {
          if (!panes.value[i].disabled) {
            nextActivePane = panes.value[i];
            break;
          }
        }
      }
      const newName = nextActivePane ? nextActivePane.name : "";
      activeTab.value = newName;
      emit("update:modelValue", newName);
      emit("tab-change", newName);
      nextTick(updateIndicator);
    }
  }
};
const activateTab = async (name) => {
  if (name === activeTab.value) return;
  if (props.beforeLeave) {
    try {
      const result = await props.beforeLeave(name, activeTab.value);
      if (result === false) return;
    } catch {
      return;
    }
  }
  activeTab.value = name;
  emit("update:modelValue", name);
  emit("tab-change", name);
  nextTick(updateIndicator);
};
let hoverTimer = null;
const handleTabClick = (pane, ev) => {
  if (pane.disabled) return;
  emit("tab-click", pane, ev);
  if (props.trigger === "click") {
    activateTab(pane.name);
  }
};
const handleTabHover = (pane) => {
  if (pane.disabled || props.trigger !== "hover") return;
  if (hoverTimer) {
    clearTimeout(hoverTimer);
  }
  hoverTimer = setTimeout(() => {
    activateTab(pane.name);
  }, 100);
};
const handleTabRemove = (pane, ev) => {
  ev.stopPropagation();
  emit("tab-remove", pane.name);
};
const handleTabAdd = () => {
  emit("tab-add");
};
const updateIndicator = () => {
  if (!navRef.value || !indicatorRef.value || props.type !== "line") return;
  const activeEl = navRef.value.querySelector(
    `.${ns.e("item")}.${ns.is("active", true)}`
  );
  if (!activeEl) return;
  const isVertical = props.tabPosition === "left" || props.tabPosition === "right";
  indicatorRef.value.style.width = "";
  indicatorRef.value.style.height = "";
  indicatorRef.value.style.transform = "";
  const rootEl = navRef.value.closest(`.${ns.b()}`) || rootRef.value;
  const computedStyle = rootEl ? getComputedStyle(rootEl) : null;
  const defaultIndicatorWidth = computedStyle?.getPropertyValue("--yh-tabs-indicator-width").trim() || "40px";
  const defaultIndicatorHeight = computedStyle?.getPropertyValue("--yh-tabs-indicator-height").trim() || "20px";
  const navRect = navRef.value.getBoundingClientRect();
  const activeRect = activeEl.getBoundingClientRect();
  if (isVertical) {
    const indicatorHeight = props.indicatorHeight || defaultIndicatorHeight;
    const indicatorWidth = props.indicatorWidth || "2px";
    indicatorRef.value.style.width = indicatorWidth;
    indicatorRef.value.style.height = indicatorHeight === "auto" ? `${activeRect.height}px` : indicatorHeight;
    indicatorRef.value.offsetHeight;
    const indicatorActualHeight = indicatorRef.value.offsetHeight;
    const relativeTop = activeRect.top - navRect.top;
    const offset = indicatorHeight === "auto" ? relativeTop : relativeTop + (activeRect.height - indicatorActualHeight) / 2;
    indicatorRef.value.style.transform = `translateY(${offset}px)`;
  } else {
    const indicatorWidth = props.indicatorWidth || defaultIndicatorWidth;
    const indicatorHeight = props.indicatorHeight || "2px";
    indicatorRef.value.style.height = indicatorHeight;
    indicatorRef.value.style.width = indicatorWidth === "auto" ? `${activeRect.width}px` : indicatorWidth;
    indicatorRef.value.offsetWidth;
    const indicatorActualWidth = indicatorRef.value.offsetWidth;
    const relativeLeft = activeRect.left - navRect.left;
    const offset = indicatorWidth === "auto" ? relativeLeft : relativeLeft + (activeRect.width - indicatorActualWidth) / 2;
    indicatorRef.value.style.transform = `translateX(${offset}px)`;
  }
};
onMounted(() => {
  if (!activeTab.value && panes.value.length > 0) {
    const firstEnabled = panes.value.find((p) => !p.disabled);
    if (firstEnabled) {
      activeTab.value = firstEnabled.name;
      emit("update:modelValue", firstEnabled.name);
    }
  }
  nextTick(updateIndicator);
});
watch(panes, () => nextTick(updateIndicator), { deep: true });
watch(
  () => props.tabPosition,
  () => {
    nextTick(() => {
      requestAnimationFrame(() => {
        updateIndicator();
      });
    });
  }
);
watch(
  () => [props.indicatorWidth, props.indicatorHeight],
  () => nextTick(updateIndicator)
);
provide(TABS_INJECTION_KEY, {
  activeTab,
  registerPane,
  unregisterPane,
  activateTab
});
const isClosable = computed(() => props.closable || props.editable);
const isAddable = computed(() => props.addable || props.editable);
const tabsClass = computed(() => [
  ns.b(),
  ns.m(props.type),
  ns.m(`${props.tabPosition}`),
  ns.is("stretch", props.stretch)
]);
const tabsStyle = computed(() => {
  const style = {
    ...themeStyle.value
  };
  if (props.activeColor) {
    style["--yh-tabs-active-color"] = props.activeColor;
  }
  if (props.inactiveColor) {
    style["--yh-tabs-text-color"] = props.inactiveColor;
  }
  return style;
});
defineExpose({
  /** 触发添加标签页事件 */
  addTab: handleTabAdd,
  /** 激活指定标签 */
  setActiveTab: activateTab,
  /** 当前激活的标签名 */
  activeTab
});
</script>

<template>
  <div ref="rootRef" :class="tabsClass" :style="tabsStyle">
    <!-- 标签栏 -->
    <div ref="navRef" :class="[ns.e('nav'), navClass]">
      <div :class="ns.e('nav-wrap')">
        <div :class="ns.e('nav-list')">
          <div
            v-for="pane in panes"
            :key="pane.name"
            :class="[ns.e('item'), ns.is('active', activeTab === pane.name), ns.is('disabled', pane.disabled), ns.is('closable', pane.closable !== false && isClosable)]"
            role="tab"
            :tabindex="pane.disabled ? -1 : 0"
            :aria-selected="activeTab === pane.name"
            @click="handleTabClick(pane, $event)"
            @keydown.enter="handleTabClick(pane, $event)"
            @mouseenter="handleTabHover(pane)"
          >
            <!-- 图标 -->
            <span v-if="pane.icon" :class="[ns.e('icon'), pane.icon]"></span>
            <!-- 标签插槽 -->
            <slot name="label" :pane="pane">
              <span :class="ns.e('label')">{{ pane.label }}</span>
            </slot>
            <!-- 关闭按钮 -->
            <span
              v-if="pane.closable !== false && isClosable && !pane.disabled"
              :class="ns.e('close')"
              @click.stop="handleTabRemove(pane, $event)"
            >
              <svg viewBox="0 0 1024 1024" width="14" height="14">
                <path
                  fill="currentColor"
                  d="M764.3 260.3a32 32 0 0 0-45.3 0L512 467.2 305 260.3a32 32 0 0 0-45.3 45.2L466.8 512 259.7 718.5a32 32 0 0 0 45.3 45.3L512 556.7l207 207a32 32 0 0 0 45.3-45.2L557.2 512l207-206.5a32 32 0 0 0 0-45.2z"
                />
              </svg>
            </span>
          </div>
          <!-- 新增按钮 -->
          <div v-if="isAddable" :class="ns.e('add')" @click="handleTabAdd">
            <slot name="add-icon">
              <svg viewBox="0 0 1024 1024" width="16" height="16">
                <path
                  fill="currentColor"
                  d="M544 480V256a32 32 0 0 0-64 0v224H256a32 32 0 0 0 0 64h224v224a32 32 0 0 0 64 0V544h224a32 32 0 0 0 0-64H544z"
                />
              </svg>
            </slot>
          </div>
        </div>
        <!-- Line 类型指示器 -->
        <div v-if="type === 'line'" ref="indicatorRef" :class="ns.e('indicator')"></div>
      </div>
    </div>
    <!-- 内容区 -->
    <div :class="[ns.e('content'), contentClass]">
      <slot></slot>
    </div>
  </div>
</template>

<style>
@charset "UTF-8";
/**
 * YH-UI Sass Variables
 * 原具体的 CSS 变量输出规则 (:root, html.dark 等) 已移至 root.scss，以防止各组件样式编译时产生重复的全局变量 CSS 冗余。
 */
.yh-tabs {
  --yh-tabs-header-height: 40px;
  --yh-tabs-active-color: var(--yh-color-primary);
  --yh-tabs-text-color: var(--yh-text-color-primary);
  --yh-tabs-disabled-color: var(--yh-text-color-disabled);
  --yh-tabs-border-color: var(--yh-border-color-light);
  --yh-tabs-indicator-width: 40px;
  --yh-tabs-indicator-height: 20px;
  display: flex;
  flex-direction: column;
}
.yh-tabs__nav {
  position: relative;
  display: flex;
  background-color: transparent;
}

.yh-tabs__nav-wrap {
  position: relative;
  overflow: hidden;
  flex: 1;
}

.yh-tabs__nav-list {
  display: flex;
  position: relative;
  transition: transform 0.3s;
  white-space: nowrap;
}

.yh-tabs__item {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: var(--yh-tabs-header-height);
  padding: 0 20px;
  box-sizing: border-box;
  font-size: var(--yh-font-size-base);
  color: var(--yh-tabs-text-color);
  cursor: pointer;
  user-select: none;
  transition: color var(--yh-duration-fast), background-color var(--yh-duration-fast);
}
.yh-tabs__item:hover:not(.is-disabled) {
  color: var(--yh-tabs-active-color);
}
.yh-tabs__item.is-active {
  color: var(--yh-tabs-active-color);
  font-weight: 500;
}
.yh-tabs__item.is-disabled {
  color: var(--yh-tabs-disabled-color);
  cursor: not-allowed;
}

.yh-tabs__icon {
  font-size: 16px;
}

.yh-tabs__label {
  line-height: 1;
}

.yh-tabs__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  color: var(--yh-text-color-secondary);
  transition: all var(--yh-duration-fast);
  margin-left: 4px;
}
.yh-tabs__close:hover {
  background-color: var(--yh-color-danger-light-9);
  color: var(--yh-color-danger);
}

.yh-tabs__add {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--yh-tabs-header-height);
  height: var(--yh-tabs-header-height);
  color: var(--yh-text-color-secondary);
  cursor: pointer;
  transition: color var(--yh-duration-fast);
}
.yh-tabs__add:hover {
  color: var(--yh-tabs-active-color);
}

.yh-tabs__indicator {
  position: absolute;
  bottom: 0;
  left: 0;
  height: 2px;
  background-color: var(--yh-tabs-active-color);
  transition: transform var(--yh-duration-base), width var(--yh-duration-base);
  border-radius: 1px;
}

.yh-tabs__content {
  padding: 16px 0;
  flex: 1;
  overflow: hidden;
}

.yh-tabs--line .yh-tabs__nav {
  border-bottom: 1px solid var(--yh-tabs-border-color);
}

.yh-tabs--card .yh-tabs__nav {
  border-bottom: 1px solid var(--yh-tabs-border-color);
}

.yh-tabs--card .yh-tabs__item {
  border: 1px solid transparent;
  border-bottom: none;
  border-radius: var(--yh-radius-base) var(--yh-radius-base) 0 0;
  margin-bottom: -1px;
}
.yh-tabs--card .yh-tabs__item.is-active {
  border-color: var(--yh-tabs-border-color);
  background-color: var(--yh-bg-color);
}

.yh-tabs--card .yh-tabs__indicator {
  display: none;
}

.yh-tabs--border-card {
  border: 1px solid var(--yh-tabs-border-color);
  border-radius: var(--yh-radius-base);
  box-shadow: var(--yh-shadow-sm);
}
.yh-tabs--border-card .yh-tabs__nav {
  background-color: var(--yh-fill-color-light);
  border-bottom: 1px solid var(--yh-tabs-border-color);
}

.yh-tabs--border-card .yh-tabs__item {
  margin: -1px 0 0 -1px;
  border: 1px solid transparent;
}
.yh-tabs--border-card .yh-tabs__item.is-active {
  background-color: var(--yh-bg-color);
  border-color: var(--yh-tabs-border-color);
  border-bottom-color: transparent;
}

.yh-tabs--border-card .yh-tabs__indicator {
  display: none;
}

.yh-tabs--border-card .yh-tabs__content {
  padding: 16px;
}

.yh-tabs--segment .yh-tabs__nav {
  background-color: var(--yh-fill-color-light);
  border-radius: var(--yh-radius-md);
  padding: 4px;
}

.yh-tabs--segment .yh-tabs__item {
  border-radius: var(--yh-radius-base);
  height: calc(var(--yh-tabs-header-height) - 8px);
}
.yh-tabs--segment .yh-tabs__item.is-active {
  background-color: var(--yh-bg-color);
  box-shadow: var(--yh-shadow-sm);
}

.yh-tabs--segment .yh-tabs__indicator {
  display: none;
}

.yh-tabs--left {
  flex-direction: row;
}
.yh-tabs--left .yh-tabs__nav {
  flex-direction: column;
  border-right: 1px solid var(--yh-tabs-border-color);
  border-bottom: none;
}

.yh-tabs--left .yh-tabs__nav-list {
  flex-direction: column;
}

.yh-tabs--left .yh-tabs__item {
  justify-content: flex-start;
}

.yh-tabs--left .yh-tabs__indicator {
  top: 0;
  right: 0;
  left: auto;
  bottom: auto;
  width: 2px;
  height: auto;
}

.yh-tabs--left .yh-tabs__content {
  padding: 0 16px;
}

.yh-tabs--right {
  flex-direction: row-reverse;
}
.yh-tabs--right .yh-tabs__nav {
  flex-direction: column;
  border-left: 1px solid var(--yh-tabs-border-color);
  border-bottom: none;
}

.yh-tabs--right .yh-tabs__nav-list {
  flex-direction: column;
}

.yh-tabs--right .yh-tabs__item {
  justify-content: flex-start;
}

.yh-tabs--right .yh-tabs__indicator {
  top: 0;
  left: 0;
  bottom: auto;
  width: 2px;
  height: auto;
}

.yh-tabs--right .yh-tabs__content {
  padding: 0 16px;
}

.yh-tabs--bottom {
  flex-direction: column-reverse;
}
.yh-tabs--bottom .yh-tabs__nav {
  border-top: 1px solid var(--yh-tabs-border-color);
  border-bottom: none;
}

.yh-tabs--bottom .yh-tabs__indicator {
  top: 0;
  bottom: auto;
}

.yh-tabs.is-stretch .yh-tabs__nav-list {
  display: flex;
  width: 100%;
}

.yh-tabs.is-stretch .yh-tabs__item {
  flex: 1;
}
</style>
