<script setup>
import { computed, ref, nextTick, watch, onMounted, onBeforeUnmount } from "vue";
import { useNamespace, useFormItem, useId, useLocale } from "@yh-ui/hooks";
import { useComponentTheme } from "@yh-ui/theme";
import { useConfig } from "@yh-ui/hooks";
import { generateTimeOptions, parseTimeToMinutes, isTimeInRange } from "./time-select";
defineOptions({
  name: "YhTimeSelect"
});
const props = defineProps({
  modelValue: { type: String, required: false },
  disabled: { type: Boolean, required: false, default: false },
  editable: { type: Boolean, required: false, default: true },
  clearable: { type: Boolean, required: false, default: true },
  size: { type: String, required: false, default: void 0 },
  placeholder: { type: String, required: false, default: void 0 },
  name: { type: String, required: false },
  effect: { type: String, required: false, default: "light" },
  prefixIcon: { type: null, required: false },
  clearIcon: { type: null, required: false },
  start: { type: String, required: false, default: "09:00" },
  end: { type: String, required: false, default: "18:00" },
  step: { type: String, required: false, default: "00:30" },
  minTime: { type: String, required: false },
  maxTime: { type: String, required: false },
  valueOnClear: { type: String, required: false },
  format: { type: String, required: false, default: "HH:mm" },
  popperClass: { type: String, required: false },
  popperStyle: { type: [String, Object], required: false },
  teleported: { type: Boolean, required: false, default: true },
  includeEndTime: { type: Boolean, required: false, default: false },
  validateEvent: { type: Boolean, required: false, default: true },
  options: { type: Array, required: false },
  disabledHours: { type: Array, required: false },
  themeOverrides: { type: null, required: false }
});
const emit = defineEmits(["update:modelValue", "change", "focus", "blur", "clear", "visible-change"]);
const ns = useNamespace("time-select");
const { t } = useLocale();
const inputId = useId();
const { themeStyle } = useComponentTheme(
  "time-select",
  computed(() => props.themeOverrides)
);
const { form, formItem, validate: triggerValidate } = useFormItem();
const { globalSize } = useConfig();
const selectSize = computed(
  () => props.size || formItem?.size || form?.size || globalSize.value || "default"
);
const wrapperRef = ref();
const inputRef = ref();
const optionsRef = ref();
const visible = ref(false);
const focused = ref(false);
const hovering = ref(false);
const query = ref("");
const hoveredIndex = ref(-1);
const isClickingDropdown = ref(false);
const dropdownStyle = ref({});
const timeOptions = computed(() => {
  if (props.options && props.options.length > 0) {
    return props.options;
  }
  return generateTimeOptions(props.start, props.end, props.step, props.format, props.includeEndTime);
});
const filteredOptions = computed(() => {
  let options = timeOptions.value;
  if (props.disabledHours && props.disabledHours.length > 0) {
    options = options.map((opt) => {
      const isDisabled = props.disabledHours.some((range) => {
        if (range.length >= 2) {
          return isTimeInRange(opt.value, range[0], range[1]);
        }
        return false;
      });
      return { ...opt, disabled: opt.disabled || isDisabled };
    });
  }
  if (props.minTime) {
    const minMinutes = parseTimeToMinutes(props.minTime);
    options = options.map((opt) => ({
      ...opt,
      disabled: opt.disabled || parseTimeToMinutes(opt.value) < minMinutes
    }));
  }
  if (props.maxTime) {
    const maxMinutes = parseTimeToMinutes(props.maxTime);
    options = options.map((opt) => ({
      ...opt,
      disabled: opt.disabled || parseTimeToMinutes(opt.value) > maxMinutes
    }));
  }
  if (query.value && props.editable) {
    const q = query.value.toLowerCase();
    return options.filter(
      (opt) => opt.label.toLowerCase().includes(q) || opt.value.toLowerCase().includes(q)
    );
  }
  return options;
});
const displayLabel = computed(() => {
  if (!props.modelValue) return "";
  const opt = timeOptions.value.find((o) => o.value === props.modelValue);
  return opt ? opt.label : props.modelValue;
});
const showClear = computed(
  () => props.clearable && !props.disabled && props.modelValue !== void 0 && props.modelValue !== "" && (focused.value || hovering.value)
);
const hasValue = computed(() => props.modelValue !== void 0 && props.modelValue !== "");
const wrapperClasses = computed(() => [
  ns.b(),
  ns.m(selectSize.value),
  ns.is("disabled", props.disabled),
  ns.is("focused", focused.value || visible.value)
]);
const updateDropdownPosition = () => {
  if (!wrapperRef.value || !props.teleported) return;
  const rect = wrapperRef.value.getBoundingClientRect();
  const spaceBelow = window.innerHeight - rect.bottom;
  const spaceAbove = rect.top;
  const dropdownHeight = 274;
  const showAbove = spaceBelow < dropdownHeight && spaceAbove > spaceBelow;
  const styles = window.getComputedStyle(wrapperRef.value);
  const primary = styles.getPropertyValue("--yh-color-primary").trim();
  const primaryRgb = styles.getPropertyValue("--yh-color-primary-rgb").trim();
  dropdownStyle.value = {
    position: "fixed",
    left: `${rect.left}px`,
    width: `${rect.width}px`,
    zIndex: "2000",
    "--yh-color-primary": primary,
    "--yh-color-primary-rgb": primaryRgb,
    ...showAbove ? { bottom: `${window.innerHeight - rect.top + 4}px` } : { top: `${rect.bottom + 4}px` }
  };
};
const scrollToSelected = () => {
  if (!optionsRef.value || !props.modelValue) return;
  nextTick(() => {
    const selectedEl = optionsRef.value?.querySelector(
      `.${ns.is("selected", true).slice(1)}`
    );
    if (selectedEl && optionsRef.value) {
      const containerHeight = optionsRef.value.clientHeight;
      const scrollTop = selectedEl.offsetTop - containerHeight / 2 + selectedEl.offsetHeight / 2;
      optionsRef.value.scrollTop = Math.max(0, scrollTop);
    }
  });
};
watch(visible, (val) => {
  if (val) {
    if (props.teleported) {
      nextTick(updateDropdownPosition);
    }
    scrollToSelected();
    query.value = "";
  }
  emit("visible-change", val);
});
onMounted(() => {
  if (props.teleported) {
    window.addEventListener("scroll", updateDropdownPosition, true);
    window.addEventListener("resize", updateDropdownPosition);
  }
});
onBeforeUnmount(() => {
  if (props.teleported) {
    window.removeEventListener("scroll", updateDropdownPosition, true);
    window.removeEventListener("resize", updateDropdownPosition);
  }
});
const handleOptionSelect = (option, event) => {
  if (option.disabled) return;
  if (event) {
    event.stopPropagation();
  }
  emit("update:modelValue", option.value);
  emit("change", option.value);
  visible.value = false;
  query.value = "";
  if (props.validateEvent) {
    triggerValidate("change");
  }
};
const handleClear = (event) => {
  event.stopPropagation();
  const value = props.valueOnClear ?? void 0;
  emit("update:modelValue", value);
  emit("change", value);
  emit("clear");
  query.value = "";
  if (props.validateEvent) {
    triggerValidate("change");
  }
};
const toggleDropdown = () => {
  if (props.disabled) return;
  visible.value = !visible.value;
  if (visible.value) {
    nextTick(() => {
      inputRef.value?.focus();
    });
  }
};
const handleInput = (event) => {
  if (!props.editable) return;
  const target = event.target;
  query.value = target.value;
};
const handleKeydown = (event) => {
  switch (event.key) {
    case "ArrowDown":
      event.preventDefault();
      if (!visible.value) {
        visible.value = true;
      } else {
        let nextIndex = hoveredIndex.value + 1;
        while (nextIndex < filteredOptions.value.length && filteredOptions.value[nextIndex]?.disabled) {
          nextIndex++;
        }
        if (nextIndex < filteredOptions.value.length) {
          hoveredIndex.value = nextIndex;
        }
      }
      break;
    case "ArrowUp":
      event.preventDefault();
      let prevIndex = hoveredIndex.value - 1;
      while (prevIndex >= 0 && filteredOptions.value[prevIndex]?.disabled) {
        prevIndex--;
      }
      if (prevIndex >= 0) {
        hoveredIndex.value = prevIndex;
      }
      break;
    case "Enter":
      event.preventDefault();
      if (visible.value && hoveredIndex.value >= 0 && filteredOptions.value[hoveredIndex.value]) {
        handleOptionSelect(filteredOptions.value[hoveredIndex.value]);
      } else if (!visible.value) {
        visible.value = true;
      }
      break;
    case "Escape":
      visible.value = false;
      break;
    case "Tab":
      visible.value = false;
      break;
  }
};
const handleFocus = (event) => {
  focused.value = true;
  emit("focus", event);
};
const handleBlur = (event) => {
  if (isClickingDropdown.value) {
    return;
  }
  focused.value = false;
  visible.value = false;
  emit("blur", event);
  if (props.validateEvent) {
    triggerValidate("blur");
  }
};
const handleDropdownMousedown = (event) => {
  event.preventDefault();
  isClickingDropdown.value = true;
};
const handleDropdownMouseup = () => {
  setTimeout(() => {
    isClickingDropdown.value = false;
  }, 0);
};
const handleOptionClick = (option, event) => {
  event.stopPropagation();
  handleOptionSelect(option, event);
  nextTick(() => {
    inputRef.value?.focus();
  });
};
const handleMouseEnter = () => {
  hovering.value = true;
};
const handleMouseLeave = () => {
  hovering.value = false;
};
const focus = () => {
  inputRef.value?.focus();
};
const blur = () => {
  inputRef.value?.blur();
};
defineExpose({
  focus,
  blur,
  inputRef
});
</script>

<template>
  <div
    ref="wrapperRef"
    :class="wrapperClasses"
    :style="themeStyle"
    @mouseenter="handleMouseEnter"
    @mouseleave="handleMouseLeave"
    @click="toggleDropdown"
  >
    <!-- 输入区域 -->
    <div :class="ns.e('wrapper')">
      <!-- 前缀图标 -->
      <span :class="ns.e('prefix')">
        <slot name="prefix">
          <component :is="prefixIcon" v-if="prefixIcon" :class="ns.e('icon')" />
          <svg v-else viewBox="0 0 1024 1024" width="1em" height="1em" :class="ns.e('icon')">
            <path
              fill="currentColor"
              d="M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896zm0 64a384 384 0 1 0 0 768 384 384 0 0 0 0-768zm0 128a32 32 0 0 1 32 32v192l128 64a32 32 0 0 1-28.864 57.088l-144-72A32 32 0 0 1 480 512V288a32 32 0 0 1 32-32z"
            />
          </svg>
        </slot>
      </span>

      <!-- 输入框 -->
      <input
        ref="inputRef"
        :id="inputId"
        :class="ns.e('inner')"
        :value="editable && visible ? query : ''"
        :placeholder="hasValue ? '' : placeholder ?? t('timeselect.placeholder')"
        :disabled="disabled"
        :readonly="!editable"
        :name="name"
        autocomplete="off"
        role="combobox"
        :aria-expanded="visible"
        :aria-controls="`${inputId}-listbox`"
        @input="handleInput"
        @focus="handleFocus"
        @blur="handleBlur"
        @keydown="handleKeydown"
      />

      <!-- 显示值 -->
      <span v-if="hasValue && !(editable && visible && query)" :class="ns.e('display-value')">
        {{ displayLabel }}
      </span>

      <!-- 后缀图标 -->
      <span :class="ns.e('suffix')">
        <!-- 清空按钮 -->
        <span v-if="showClear" :class="[ns.e('icon'), ns.e('clear')]" @click.stop="handleClear">
          <component :is="clearIcon" v-if="clearIcon" />
          <svg v-else viewBox="0 0 1024 1024" width="1em" height="1em">
            <path
              fill="currentColor"
              d="M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896zm0 393.664L407.936 353.6a38.4 38.4 0 1 0-54.336 54.336L457.664 512 353.6 616.064a38.4 38.4 0 1 0 54.336 54.336L512 566.336 616.064 670.4a38.4 38.4 0 1 0 54.336-54.336L566.336 512 670.4 407.936a38.4 38.4 0 1 0-54.336-54.336L512 457.664z"
            />
          </svg>
        </span>

        <!-- 箭头图标 -->
        <span :class="[ns.e('icon'), ns.e('arrow'), {
  'is-reverse': visible
}]">
          <svg viewBox="0 0 1024 1024" width="1em" height="1em">
            <path
              fill="currentColor"
              d="M831.872 340.864L512 652.672 192.128 340.864a30.592 30.592 0 0 0-42.752 0 29.12 29.12 0 0 0 0 41.6L489.664 714.24a32 32 0 0 0 44.672 0l340.288-331.712a29.12 29.12 0 0 0 0-41.728 30.592 30.592 0 0 0-42.752 0z"
            />
          </svg>
        </span>
      </span>
    </div>

    <!-- 下拉框 -->
    <Teleport to="body" :disabled="!teleported">
      <Transition :name="ns.b('dropdown')">
        <div
          v-show="visible"
          :class="[ns.e('dropdown'), popperClass, `is-${effect}`]"
          :style="teleported ? dropdownStyle : {}"
          @mousedown="handleDropdownMousedown"
          @mouseup="handleDropdownMouseup"
        >
          <!-- 无数据 -->
          <div v-if="filteredOptions.length === 0" :class="ns.e('empty')">
            <slot name="empty">
              {{ t("select.noData") }}
            </slot>
          </div>

          <!-- 选项列表 -->
          <div
            v-else
            ref="optionsRef"
            :id="`${inputId}-listbox`"
            :class="ns.e('options')"
            role="listbox"
          >
            <div
              v-for="(option, index) in filteredOptions"
              :key="option.value"
              :class="[ns.e('option'), ns.is('selected', modelValue === option.value), ns.is('disabled', option.disabled), ns.is('hovering', hoveredIndex === index)]"
              role="option"
              :aria-selected="modelValue === option.value"
              @mousedown.prevent
              @click="handleOptionClick(option, $event)"
              @mouseenter="hoveredIndex = index"
            >
              <slot name="option" :option="option">
                {{ option.label }}
              </slot>
              <span v-if="modelValue === option.value" :class="ns.e('option-check')">
                <svg viewBox="0 0 1024 1024" width="1em" height="1em">
                  <path
                    fill="currentColor"
                    d="M406.656 706.944L195.84 496.256a32 32 0 1 0-45.248 45.248l256 256 512-512a32 32 0 0 0-45.248-45.248L406.592 706.944z"
                  />
                </svg>
              </span>
            </div>
          </div>
        </div>
      </Transition>
    </Teleport>
  </div>
</template>

<style>
@charset "UTF-8";
/**
 * YH-UI Sass Variables
 * 原具体的 CSS 变量输出规则 (:root, html.dark 等) 已移至 root.scss，以防止各组件样式编译时产生重复的全局变量 CSS 冗余。
 */
.yh-time-select {
  --yh-time-select-height: var(--yh-component-size-default, 32px);
  --yh-time-select-font-size: var(--yh-font-size-base, 14px);
  --yh-time-select-icon-size: 14px;
  --yh-time-select-prefix-margin: 12px;
  --yh-time-select-display-padding: 38px;
  position: relative;
  display: inline-flex;
  width: 100%;
  cursor: pointer;
}
.yh-time-select__wrapper {
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
  height: var(--yh-time-select-height);
  font-size: var(--yh-time-select-font-size);
  background-color: var(--yh-fill-color-blank, #fff);
  border: 1px solid var(--yh-border-color, #dcdfe6);
  border-radius: var(--yh-radius-base, 4px);
  transition: var(--yh-transition-base, all 0.2s ease);
  box-sizing: border-box;
}
.yh-time-select__wrapper:hover {
  border-color: var(--yh-border-color-hover, #c0c4cc);
}

.yh-time-select__prefix {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--yh-text-color-placeholder, #a8abb2);
  margin-right: 8px;
  margin-left: var(--yh-time-select-prefix-margin);
  font-size: var(--yh-time-select-icon-size);
}

.yh-time-select__inner {
  flex: 1;
  width: 0;
  border: none;
  outline: none;
  background: transparent;
  color: var(--yh-text-color-primary, #303133);
  font-size: inherit;
  padding: 0;
  cursor: inherit;
}
.yh-time-select__inner::placeholder {
  color: var(--yh-text-color-placeholder, #a8abb2);
}

.yh-time-select__display-value {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  padding-left: var(--yh-time-select-display-padding);
  color: var(--yh-text-color-primary, #303133);
  font-size: inherit;
  pointer-events: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.yh-time-select__suffix {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  margin-right: 8px;
  gap: 4px;
}

.yh-time-select__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--yh-text-color-placeholder, #a8abb2);
  transition: var(--yh-transition-fast, all 0.15s ease);
  font-size: var(--yh-time-select-icon-size);
}
.yh-time-select__icon svg {
  width: 1em;
  height: 1em;
}

.yh-time-select__clear {
  cursor: pointer;
}
.yh-time-select__clear:hover {
  color: var(--yh-text-color-secondary, #909399);
}

.yh-time-select__arrow {
  transition: transform var(--yh-duration-base, 0.2s) ease;
}
.yh-time-select__arrow.is-reverse {
  transform: rotate(180deg);
}

.yh-time-select__dropdown {
  position: absolute;
  z-index: var(--yh-z-index-popper, 2000);
  background-color: var(--yh-bg-color-overlay, #fff);
  border: 1px solid var(--yh-border-color-light, #e4e7ed);
  border-radius: var(--yh-radius-base, 4px);
  box-shadow: var(--yh-shadow-lg);
  overflow: hidden;
}

.yh-time-select__options {
  max-height: 274px;
  overflow-y: auto;
  padding: 4px 0;
}

.yh-time-select__option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 12px;
  height: 34px;
  line-height: 34px;
  font-size: 14px;
  color: var(--yh-text-color-regular, #606266);
  cursor: pointer;
  transition: background-color var(--yh-duration-fast) ease;
}
.yh-time-select__option:hover, .yh-time-select__option.is-hovering {
  background-color: var(--yh-fill-color-light, #f5f7fa);
}
.yh-time-select__option.is-selected {
  color: var(--yh-color-white, #ffffff);
  background-color: var(--yh-color-primary, #409eff);
  font-weight: 700;
}

.yh-time-select__option.is-disabled {
  color: var(--yh-text-color-disabled, #c0c4cc);
  cursor: not-allowed;
}
.yh-time-select__option.is-disabled:hover {
  background-color: transparent;
}

.yh-time-select__option-check {
  display: inline-flex;
  align-items: center;
  color: inherit;
  margin-left: 8px;
}
.yh-time-select__option-check svg {
  width: 14px;
  height: 14px;
}

.yh-time-select__empty {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100px;
  font-size: 14px;
  color: var(--yh-text-color-placeholder, #a8abb2);
}

.yh-time-select {
  /* 尺寸变体 - 通过变量继承解决 BEM 嵌套导致的 at-root 丢失上下文问题 */
}
.yh-time-select--large {
  --yh-time-select-height: var(--yh-component-size-large, 40px);
  --yh-time-select-font-size: var(--yh-font-size-base, 14px);
  --yh-time-select-icon-size: 16px;
  --yh-time-select-display-padding: 42px;
}

.yh-time-select--small {
  --yh-time-select-height: var(--yh-component-size-small, 24px);
  --yh-time-select-font-size: var(--yh-font-size-xs, 12px);
  --yh-time-select-icon-size: 12px;
  --yh-time-select-prefix-margin: 8px;
  --yh-time-select-display-padding: 30px;
}

.yh-time-select.is-focused .yh-time-select__wrapper {
  border-color: var(--yh-color-primary);
  box-shadow: 0 0 0 2px var(--yh-color-primary-light-8);
}

.yh-time-select.is-disabled {
  cursor: not-allowed;
}
.yh-time-select.is-disabled .yh-time-select__wrapper {
  background-color: var(--yh-fill-color-light);
  border-color: var(--yh-border-color-light);
  color: var(--yh-text-color-disabled);
  cursor: not-allowed;
}
.yh-time-select.is-disabled .yh-time-select__wrapper:hover {
  border-color: var(--yh-border-color-light);
}
.yh-time-select.is-disabled .yh-time-select__inner {
  color: var(--yh-text-color-disabled);
  cursor: not-allowed;
}
.yh-time-select.is-disabled .yh-time-select__display-value {
  color: var(--yh-text-color-disabled);
}
</style>
