<script setup>
import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from "vue";
import { useNamespace, useFormItem, useLocale } from "@yh-ui/hooks";
import { useComponentTheme } from "@yh-ui/theme";
import { useConfig } from "@yh-ui/hooks";
import DateTable from "./date-table.vue";
import MonthTable from "./month-table.vue";
import YearTable from "./year-table.vue";
import QuarterTable from "./quarter-table.vue";
import {
  datePickerProps
} from "./date-picker";
import { DEFAULT_FORMATS, formatDate } from "./panel-utils";
import dayjs from "../../dayjs";
defineOptions({
  name: "YhDatePicker"
});
const props = defineProps(datePickerProps);
const emit = defineEmits(["update:modelValue", "change", "focus", "blur", "clear", "confirm", "panel-change", "visible-change"]);
const ns = useNamespace("date-picker");
const { t, locale } = useLocale();
const { form, formItem, validate: triggerValidate } = useFormItem();
const { globalSize } = useConfig();
const { themeStyle } = useComponentTheme(
  "date-picker",
  computed(() => props.themeOverrides)
);
const visible = ref(props.panelOnly);
const hovering = ref(false);
const originalValue = ref(null);
const confirmed = ref(false);
const handlePresetClick = (p) => {
  const val = typeof p.value === "function" ? p.value() : p.value;
  handleSelect(val);
  confirmed.value = true;
  if (!props.panelOnly) {
    visible.value = false;
  }
};
const getInitialView = (type) => {
  if (type.includes("year")) return "year";
  if (type.includes("month")) return "month";
  if (type.includes("quarter")) return "quarter";
  if (type === "week") return "date";
  return "date";
};
const currentView = ref(getInitialView(props.type));
const innerDate = ref(/* @__PURE__ */ new Date());
const rangeHoverDate = ref(null);
const timeState = ref({ hour: 0, minute: 0, second: 0 });
const startTimeState = ref({ hour: 0, minute: 0, second: 0 });
const endTimeState = ref({ hour: 0, minute: 0, second: 0 });
const syncTimeStates = () => {
  const val = props.modelValue;
  if (props.type === "datetime") {
    if (val && !Array.isArray(val) && dayjs(val).isValid()) {
      const d = dayjs(val);
      timeState.value = { hour: d.hour(), minute: d.minute(), second: d.second() };
    } else {
      const def = props.defaultTime;
      if (def && !Array.isArray(def) && dayjs(def).isValid()) {
        const d = dayjs(def);
        timeState.value = { hour: d.hour(), minute: d.minute(), second: d.second() };
      } else {
        timeState.value = { hour: 0, minute: 0, second: 0 };
      }
    }
  } else if (props.type === "datetimerange") {
    const valArr = Array.isArray(val) ? val : [null, null];
    if (valArr[0] && dayjs(valArr[0]).isValid()) {
      const d = dayjs(valArr[0]);
      startTimeState.value = { hour: d.hour(), minute: d.minute(), second: d.second() };
    } else {
      const def = Array.isArray(props.defaultTime) ? props.defaultTime[0] : props.defaultTime;
      if (def && dayjs(def).isValid()) {
        const d = dayjs(def);
        startTimeState.value = { hour: d.hour(), minute: d.minute(), second: d.second() };
      } else {
        startTimeState.value = { hour: 0, minute: 0, second: 0 };
      }
    }
    if (valArr[1] && dayjs(valArr[1]).isValid()) {
      const d = dayjs(valArr[1]);
      endTimeState.value = { hour: d.hour(), minute: d.minute(), second: d.second() };
    } else {
      const def = Array.isArray(props.defaultTime) ? props.defaultTime[1] : props.defaultTime;
      if (def && dayjs(def).isValid()) {
        const d = dayjs(def);
        endTimeState.value = { hour: d.hour(), minute: d.minute(), second: d.second() };
      } else {
        endTimeState.value = { hour: 0, minute: 0, second: 0 };
      }
    }
  }
};
const updateSingleTime = () => {
  const base = props.modelValue ? dayjs(props.modelValue) : dayjs(innerDate.value);
  const newDate = base.hour(timeState.value.hour).minute(timeState.value.minute).second(timeState.value.second).toDate();
  emitChange(newDate);
};
const updateRangeStartTime = () => {
  const current = props.modelValue || [null, null];
  const startBase = current[0] ? dayjs(current[0]) : dayjs(innerDate.value);
  const mergedStart = startBase.hour(startTimeState.value.hour).minute(startTimeState.value.minute).second(startTimeState.value.second).toDate();
  const mergedEnd = current[1] ? dayjs(current[1]).toDate() : null;
  emitChange([mergedStart, mergedEnd]);
};
const updateRangeEndTime = () => {
  const current = props.modelValue || [null, null];
  const endBase = current[1] ? dayjs(current[1]) : dayjs(innerDate.value);
  const mergedEnd = endBase.hour(endTimeState.value.hour).minute(endTimeState.value.minute).second(endTimeState.value.second).toDate();
  const mergedStart = current[0] ? dayjs(current[0]).toDate() : null;
  emitChange([mergedStart, mergedEnd]);
};
const wrapperRef = ref();
const panelRef = ref();
const isRange = computed(() => props.type.includes("range"));
const selectSize = computed(
  () => props.size || formItem?.size || form?.size || globalSize.value || "default"
);
const getFormat = () => {
  if (props.format) return props.format;
  if (props.dateFormat) {
    if (props.type.includes("datetime")) {
      return `${props.dateFormat} ${props.timeFormat || "HH:mm:ss"}`;
    }
    return props.dateFormat;
  }
  return DEFAULT_FORMATS[props.type] || "YYYY-MM-DD";
};
const displayValue = computed(() => {
  if (isRange.value) return "";
  if (!props.modelValue || props.modelValue === "") return "";
  const dateVal = Array.isArray(props.modelValue) ? props.modelValue[0] : props.modelValue;
  if (!dateVal) return "";
  const result = formatDate(dateVal, getFormat());
  return result === "Invalid Date" ? "" : result;
});
const rangeDisplayValue = computed(() => {
  if (!isRange.value || !Array.isArray(props.modelValue)) return ["", ""];
  const fmt = getFormat();
  const [start, end] = props.modelValue;
  return [start ? formatDate(start, fmt) : "", end ? formatDate(end, fmt) : ""];
});
const parsedSelectedDate = computed(() => {
  const val = props.modelValue;
  if (val === "" || val === null || val === void 0) return null;
  if (Array.isArray(val)) {
    return val.map((v) => v && v !== "" ? dayjs(v).toDate() : null).filter((v) => v !== null);
  }
  const d = dayjs(val);
  return d.isValid() ? d.toDate() : null;
});
const parsedRangeState = computed(() => {
  if (!isRange.value) return void 0;
  const val = props.modelValue;
  const from = Array.isArray(val) && val[0] ? dayjs(val[0]).toDate() : null;
  const to = Array.isArray(val) && val[1] ? dayjs(val[1]).toDate() : null;
  return {
    from,
    to,
    hovering: rangeHoverDate.value
  };
});
const wrapperClasses = computed(() => [
  ns.b(),
  ns.m(selectSize.value),
  ns.is("disabled", props.disabled),
  ns.is("focused", visible.value),
  ns.is("range", isRange.value),
  ns.is("panel-only", props.panelOnly),
  ns.m(props.status)
]);
const monthKeys = [
  "jan",
  "feb",
  "mar",
  "apr",
  "may",
  "jun",
  "jul",
  "aug",
  "sep",
  "oct",
  "nov",
  "dec"
];
const headerLabel = computed(() => {
  const d = dayjs(innerDate.value);
  const dateLocale = locale.value.yh.datepicker;
  if (currentView.value === "year") {
    const start = Math.floor(d.year() / 10) * 10;
    return `${start} - ${start + 9}`;
  }
  if (currentView.value === "month" || currentView.value === "quarter") {
    return dateLocale.monthBeforeYear ? d.format("YYYY") : `${d.year()}${dateLocale.year || ""}`;
  }
  const monthName = dateLocale.months[monthKeys[d.month()]];
  if (!dateLocale.monthBeforeYear) {
    return `${d.year()}${dateLocale.year || ""}${monthName}`;
  }
  return `${monthName} ${d.year()}`;
});
const moveMonth = (offset) => {
  innerDate.value = dayjs(innerDate.value).add(offset, "month").toDate();
};
const moveYear = (offset) => {
  const step = currentView.value === "year" ? 10 : 1;
  innerDate.value = dayjs(innerDate.value).add(offset * step, "year").toDate();
};
const handleHeaderClick = () => {
  if (currentView.value === "date") currentView.value = "month";
  else if (currentView.value === "month") currentView.value = "year";
};
const emitChange = (val) => {
  const fmt = props.valueFormat || "";
  let result = val;
  if (fmt && val) {
    if (Array.isArray(val)) {
      result = [val[0] ? formatDate(val[0], fmt) : null, val[1] ? formatDate(val[1], fmt) : null];
    } else {
      result = formatDate(val, fmt);
    }
  }
  emit("update:modelValue", result);
  emit("change", result);
  if (props.validateEvent) triggerValidate("change");
};
const handleSelect = (val) => {
  if (Array.isArray(val)) {
    if (isRange.value) emitChange([val[0], val[1]]);
    else emitChange(val[0]);
    if (!props.panelOnly) visible.value = false;
    return;
  }
  let targetDate;
  if (typeof val === "number") {
    if (currentView.value === "year") {
      targetDate = dayjs(innerDate.value).year(val).toDate();
      if (props.type.includes("year")) {
        performFinalSelect(targetDate);
      } else {
        innerDate.value = targetDate;
        currentView.value = "month";
      }
    } else if (currentView.value === "month") {
      targetDate = dayjs(innerDate.value).month(val).toDate();
      if (props.type.includes("month")) {
        performFinalSelect(targetDate);
      } else {
        innerDate.value = targetDate;
        currentView.value = "date";
      }
    } else if (currentView.value === "quarter") {
      targetDate = dayjs(innerDate.value).month((val - 1) * 3).toDate();
      performFinalSelect(targetDate);
    }
  } else {
    performFinalSelect(val);
  }
};
const performFinalSelect = (date) => {
  if (isRange.value) {
    const current = props.modelValue || [null, null];
    if (!current[0] || current[0] && current[1]) {
      const mergedStart = props.type.includes("datetime") ? dayjs(date).hour(startTimeState.value.hour).minute(startTimeState.value.minute).second(startTimeState.value.second).toDate() : date;
      emitChange([mergedStart, null]);
    } else {
      let start = dayjs(current[0]).toDate();
      let end = date;
      if (dayjs(end).isBefore(dayjs(start))) {
        if (props.orderOnConfirm) [start, end] = [end, start];
        else {
          start = date;
          end = null;
        }
      }
      const mergedStart = props.type.includes("datetime") ? dayjs(start).hour(startTimeState.value.hour).minute(startTimeState.value.minute).second(startTimeState.value.second).toDate() : start;
      const mergedEnd = end ? props.type.includes("datetime") ? dayjs(end).hour(endTimeState.value.hour).minute(endTimeState.value.minute).second(endTimeState.value.second).toDate() : end : null;
      emitChange([mergedStart, mergedEnd]);
      if (end && !props.panelOnly && !props.type.includes("datetime")) {
        confirmed.value = true;
        visible.value = false;
      }
    }
  } else {
    const mergedDate = props.type.includes("datetime") ? dayjs(date).hour(timeState.value.hour).minute(timeState.value.minute).second(timeState.value.second).toDate() : date;
    emitChange(mergedDate);
    if (!props.panelOnly && !props.type.includes("datetime")) {
      confirmed.value = true;
      visible.value = false;
    }
  }
};
const DROPDOWN_GAP = 8;
const PANEL_OFFSET = 4;
const DEFAULT_PANEL_WIDTH = 380;
const dropdownStyle = ref({});
const updatePosition = async () => {
  if (typeof window === "undefined" || !wrapperRef.value || !props.teleported || props.panelOnly)
    return;
  await nextTick();
  const rect = wrapperRef.value.getBoundingClientRect();
  const viewportWidth = window.innerWidth;
  const viewportHeight = window.innerHeight;
  const styles = window.getComputedStyle(wrapperRef.value);
  const primary = styles.getPropertyValue("--yh-color-primary").trim();
  const primaryRgb = styles.getPropertyValue("--yh-color-primary-rgb").trim();
  const primaryLight9 = styles.getPropertyValue("--yh-color-primary-light-9").trim();
  const panelWidthVar = Number.parseFloat(styles.getPropertyValue("--yh-date-picker-panel-width"));
  const preferredPanelWidth = Number.isFinite(panelWidthVar) ? panelWidthVar : DEFAULT_PANEL_WIDTH;
  const panelWidth = Math.min(
    panelRef.value?.offsetWidth || preferredPanelWidth,
    Math.max(viewportWidth - DROPDOWN_GAP * 2, 240)
  );
  const panelHeight = panelRef.value?.offsetHeight || 0;
  const maxLeft = Math.max(DROPDOWN_GAP, viewportWidth - panelWidth - DROPDOWN_GAP);
  const left = Math.min(Math.max(rect.left, DROPDOWN_GAP), maxLeft);
  const preferredTop = rect.bottom + PANEL_OFFSET;
  const canPlaceAbove = rect.top - panelHeight - PANEL_OFFSET >= DROPDOWN_GAP;
  const shouldPlaceAbove = panelHeight > 0 && preferredTop + panelHeight + DROPDOWN_GAP > viewportHeight && canPlaceAbove;
  const top = shouldPlaceAbove ? Math.max(DROPDOWN_GAP, rect.top - panelHeight - PANEL_OFFSET) : Math.max(
    DROPDOWN_GAP,
    Math.min(
      preferredTop,
      viewportHeight - Math.min(panelHeight, viewportHeight - DROPDOWN_GAP * 2) - DROPDOWN_GAP
    )
  );
  dropdownStyle.value = {
    ...themeStyle.value,
    position: "fixed",
    top: `${top}px`,
    left: `${left}px`,
    zIndex: "2000",
    "--yh-date-picker-panel-width": `${panelWidth}px`,
    "--yh-color-primary": primary,
    "--yh-color-primary-rgb": primaryRgb,
    "--yh-color-primary-light-9": primaryLight9
  };
};
const syncInnerDate = () => {
  const modelVal = Array.isArray(props.modelValue) ? props.modelValue[0] : props.modelValue;
  const defaultVal = Array.isArray(props.defaultValue) ? props.defaultValue[0] : props.defaultValue;
  if (modelVal && dayjs(modelVal).isValid()) {
    innerDate.value = dayjs(modelVal).toDate();
  } else if (defaultVal && dayjs(defaultVal).isValid()) {
    innerDate.value = dayjs(defaultVal).toDate();
  } else {
    innerDate.value = /* @__PURE__ */ new Date();
  }
  syncTimeStates();
};
watch(visible, (val) => {
  if (props.panelOnly && !val) {
    visible.value = true;
    return;
  }
  emit("visible-change", val);
  if (val) {
    confirmed.value = false;
    originalValue.value = Array.isArray(props.modelValue) ? [...props.modelValue] : props.modelValue;
    currentView.value = getInitialView(props.type);
    void updatePosition();
    syncInnerDate();
    if (!props.panelOnly && typeof window !== "undefined") {
      window.addEventListener("click", handleOutsideClick, true);
      if (props.teleported) {
        window.addEventListener("scroll", updatePosition, true);
        window.addEventListener("resize", updatePosition);
      }
    }
  } else {
    if (!confirmed.value) {
      emitChange(originalValue.value);
    }
    if (!props.panelOnly && typeof window !== "undefined") {
      window.removeEventListener("click", handleOutsideClick, true);
      window.removeEventListener("scroll", updatePosition, true);
      window.removeEventListener("resize", updatePosition);
    }
  }
});
watch(
  () => props.type,
  (val) => {
    currentView.value = getInitialView(val);
    syncInnerDate();
    if (props.panelOnly) {
      visible.value = true;
    }
  }
);
watch(
  () => props.modelValue,
  () => {
    syncInnerDate();
  }
);
watch([currentView, innerDate], ([view, date]) => {
  emit("panel-change", date, view);
});
const handleClear = (e) => {
  e.stopPropagation();
  confirmed.value = true;
  emitChange(null);
  emit("clear");
};
const togglePanel = (e) => {
  if (props.disabled || props.readonly || props.panelOnly) return;
  if (e) e.stopPropagation();
  visible.value = !visible.value;
};
const handleOutsideClick = (e) => {
  const target = e.target;
  if (!visible.value || props.panelOnly) return;
  if (wrapperRef.value?.contains(target)) return;
  const poppers = document.querySelectorAll(`.${ns.e("panel")}`);
  for (const p of poppers) {
    if (p.contains(target)) return;
  }
  visible.value = false;
};
const shouldShowFooter = computed(() => {
  if (props.showFooter === false) return false;
  return props.type.includes("datetime") || isRange.value || props.presets.length > 0;
});
const handleConfirmClick = () => {
  confirmed.value = true;
  emit("confirm", props.modelValue);
  if (!props.panelOnly) {
    visible.value = false;
  }
};
onMounted(() => {
  syncInnerDate();
  if (visible.value && !props.panelOnly && typeof window !== "undefined") {
    void updatePosition();
    window.addEventListener("click", handleOutsideClick, true);
    if (props.teleported) {
      window.addEventListener("scroll", updatePosition, true);
      window.addEventListener("resize", updatePosition);
    }
  }
});
onBeforeUnmount(() => {
  if (!props.panelOnly && typeof window !== "undefined") {
    window.removeEventListener("click", handleOutsideClick, true);
    window.removeEventListener("scroll", updatePosition, true);
    window.removeEventListener("resize", updatePosition);
  }
});
</script>

<template>
  <div
    ref="wrapperRef"
    :class="wrapperClasses"
    :style="[themeStyle, $attrs.style]"
    @click="togglePanel"
    @mouseenter="hovering = true"
    @mouseleave="hovering = false"
  >
    <!-- 输入框部分 -->
    <div v-if="!panelOnly" :class="ns.e('wrapper')">
      <span :class="ns.e('icon')">
        <slot name="prefix-icon">
          <component :is="prefixIcon" v-if="prefixIcon" />
          <svg v-else viewBox="0 0 24 24" width="1em" height="1em">
            <path
              fill="currentColor"
              d="M19 4h-1V2h-2v2H8V2H6v2H5c-1.11 0-1.99.9-1.99 2L3 20a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2m0 16H5V10h14zM9 14H7v-2h2zm4 0h-2v-2h2zm4 0h-2v-2h2zm-8 4H7v-2h2zm4 4H11V16h2z"
            />
          </svg>
        </slot>
      </span>

      <template v-if="!isRange">
        <input
          :class="ns.e('inner')"
          :placeholder="placeholder ?? t('datepicker.selectDate')"
          :value="displayValue"
          readonly
          @focus="e => emit('focus', e)"
          @blur="e => emit('blur', e)"
        />
      </template>
      <template v-else>
        <div :class="ns.e('range-input-wrapper')">
          <input
            :class="ns.e('range-input')"
            :placeholder="startPlaceholder ?? t('datepicker.startDate')"
            :value="rangeDisplayValue[0]"
            readonly
            @focus="e => emit('focus', e)"
            @blur="e => emit('blur', e)"
          />
          <span :class="ns.e('range-separator')">{{ rangeSeparator }}</span>
          <input
            :class="ns.e('range-input')"
            :placeholder="endPlaceholder ?? t('datepicker.endDate')"
            :value="rangeDisplayValue[1]"
            readonly
            @focus="e => emit('focus', e)"
            @blur="e => emit('blur', e)"
          />
        </div>
      </template>

      <span :class="ns.e('suffix')">
        <span
          v-if="clearable && modelValue && hovering && !disabled"
          :class="ns.e('clear')"
          @click.stop="handleClear"
        >
          <slot name="clear-icon">
            <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>
          </slot>
        </span>
      </span>
    </div>

    <!-- 弹窗面板 -->
    <Teleport to="body" :disabled="!props.teleported || panelOnly">
      <Transition :name="panelOnly ? '' : ns.b('panel')">
        <div
          ref="panelRef"
          v-if="visible"
          :class="[ns.e('panel'), popperClass, ns.is('plain', panelOnly), presets.length > 0 ? ns.em('panel', 'has-presets-' + presetPosition) : '']"
          :style="!panelOnly && props.teleported ? dropdownStyle : {}"
          @click.stop
        >
          <div
            v-if="presets.length > 0 && (presetPosition === 'left' || presetPosition === 'right')"
            :class="[ns.e('presets'), ns.is(presetPosition)]"
          >
            <button
              v-for="p in presets"
              :key="p.label"
              :class="ns.e('preset-item')"
              @click="handlePresetClick(p)"
            >
              {{ p.label }}
            </button>
          </div>

          <div :class="ns.e('main')">
            <div
              v-if="presets.length > 0 && presetPosition === 'top'"
              :class="[ns.e('presets'), ns.is('top')]"
            >
              <button
                v-for="p in presets"
                :key="p.label"
                :class="ns.e('preset-item')"
                @click="handlePresetClick(p)"
              >
                {{ p.label }}
              </button>
            </div>
            <div :class="ns.e('header')">
              <div :class="ns.e('header-group')">
                <button
                  :class="[ns.e('header-btns'), ns.em('header-btns', 'double-left')]"
                  @click="moveYear(-1)"
                >
                  «
                </button>
                <button
                  v-if="currentView === 'date'"
                  :class="[ns.e('header-btns'), ns.em('header-btns', 'left')]"
                  @click="moveMonth(-1)"
                >
                  ‹
                </button>
              </div>
              <span :class="ns.e('header-label')" @click="handleHeaderClick">{{
                headerLabel
              }}</span>
              <div :class="ns.e('header-group')">
                <button
                  v-if="currentView === 'date'"
                  :class="[ns.e('header-btns'), ns.em('header-btns', 'right')]"
                  @click="moveMonth(1)"
                >
                  ›
                </button>
                <button
                  :class="[ns.e('header-btns'), ns.em('header-btns', 'double-right')]"
                  @click="moveYear(1)"
                >
                  »
                </button>
              </div>
            </div>

            <div :class="ns.e('content')">
              <DateTable
                v-if="currentView === 'date'"
                :date="innerDate"
                :selected-date="parsedSelectedDate"
                :selection-mode="type === 'week' ? 'week' : 'date'"
                :range-state="parsedRangeState"
                :disabled-date="disabledDate"
                :first-day-of-week="firstDayOfWeek"
                :cell-shape="cellShape"
                :cell-render="cellRender"
                @select="handleSelect"
                @hover="val => rangeHoverDate = val"
              >
                <template #date-cell="slotProps">
                  <slot name="date-cell" v-bind="slotProps" />
                </template>
              </DateTable>
              <MonthTable
                v-else-if="currentView === 'month'"
                :date="innerDate"
                :selected-date="parsedSelectedDate"
                :range-state="parsedRangeState"
                :disabled-date="disabledDate"
                :cell-shape="cellShape"
                @select="handleSelect"
                @hover="val => rangeHoverDate = val"
              />
              <YearTable
                v-else-if="currentView === 'year'"
                :date="innerDate"
                :selected-date="parsedSelectedDate"
                :range-state="parsedRangeState"
                :disabled-date="disabledDate"
                :cell-shape="cellShape"
                @select="handleSelect"
                @hover="val => rangeHoverDate = val"
              />
              <QuarterTable
                v-else-if="currentView === 'quarter'"
                :date="innerDate"
                :selected-date="parsedSelectedDate"
                :range-state="parsedRangeState"
                :disabled-date="disabledDate"
                :cell-shape="cellShape"
                @select="handleSelect"
                @hover="val => rangeHoverDate = val"
              />
            </div>

            <div v-if="$slots.extra" :class="ns.e('extra')">
              <slot name="extra"></slot>
            </div>

            <div v-if="shouldShowFooter" :class="ns.e('footer')">
              <slot name="footer">
                <div v-if="type === 'datetime'" :class="ns.e('footer-time')">
                  <select v-model="timeState.hour" @change="updateSingleTime">
                    <option v-for="h in 24" :key="h - 1" :value="h - 1">
                      {{ String(h - 1).padStart(2, "0") }}
                    </option>
                  </select>
                  <span :class="ns.e('time-separator')">:</span>
                  <select v-model="timeState.minute" @change="updateSingleTime">
                    <option v-for="m in 60" :key="m - 1" :value="m - 1">
                      {{ String(m - 1).padStart(2, "0") }}
                    </option>
                  </select>
                  <span
                    v-if="(props.timeFormat || 'HH:mm:ss').toLowerCase().includes('s')"
                    :class="ns.e('time-separator')"
                    >:</span
                  >
                  <select
                    v-if="(props.timeFormat || 'HH:mm:ss').toLowerCase().includes('s')"
                    v-model="timeState.second"
                    @change="updateSingleTime"
                  >
                    <option v-for="s in 60" :key="s - 1" :value="s - 1">
                      {{ String(s - 1).padStart(2, "0") }}
                    </option>
                  </select>
                </div>
                <div v-else-if="type === 'datetimerange'" :class="ns.e('footer-time-range')">
                  <div :class="ns.e('footer-time')">
                    <span :class="ns.e('time-label')"
                      >{{ t("datepicker.startTime") || "Start" }}:</span
                    >
                    <select v-model="startTimeState.hour" @change="updateRangeStartTime">
                      <option v-for="h in 24" :key="h - 1" :value="h - 1">
                        {{ String(h - 1).padStart(2, "0") }}
                      </option>
                    </select>
                    <span :class="ns.e('time-separator')">:</span>
                    <select v-model="startTimeState.minute" @change="updateRangeStartTime">
                      <option v-for="m in 60" :key="m - 1" :value="m - 1">
                        {{ String(m - 1).padStart(2, "0") }}
                      </option>
                    </select>
                    <span
                      v-if="(props.timeFormat || 'HH:mm:ss').toLowerCase().includes('s')"
                      :class="ns.e('time-separator')"
                      >:</span
                    >
                    <select
                      v-if="(props.timeFormat || 'HH:mm:ss').toLowerCase().includes('s')"
                      v-model="startTimeState.second"
                      @change="updateRangeStartTime"
                    >
                      <option v-for="s in 60" :key="s - 1" :value="s - 1">
                        {{ String(s - 1).padStart(2, "0") }}
                      </option>
                    </select>
                  </div>
                  <div :class="ns.e('footer-time')">
                    <span :class="ns.e('time-label')">{{ t("datepicker.endTime") || "End" }}:</span>
                    <select v-model="endTimeState.hour" @change="updateRangeEndTime">
                      <option v-for="h in 24" :key="h - 1" :value="h - 1">
                        {{ String(h - 1).padStart(2, "0") }}
                      </option>
                    </select>
                    <span :class="ns.e('time-separator')">:</span>
                    <select v-model="endTimeState.minute" @change="updateRangeEndTime">
                      <option v-for="m in 60" :key="m - 1" :value="m - 1">
                        {{ String(m - 1).padStart(2, "0") }}
                      </option>
                    </select>
                    <span
                      v-if="(props.timeFormat || 'HH:mm:ss').toLowerCase().includes('s')"
                      :class="ns.e('time-separator')"
                      >:</span
                    >
                    <select
                      v-if="(props.timeFormat || 'HH:mm:ss').toLowerCase().includes('s')"
                      v-model="endTimeState.second"
                      @change="updateRangeEndTime"
                    >
                      <option v-for="s in 60" :key="s - 1" :value="s - 1">
                        {{ String(s - 1).padStart(2, "0") }}
                      </option>
                    </select>
                  </div>
                </div>
                <div :class="ns.e('footer-btns')">
                  <button
                    v-if="isRange || type.includes('datetime')"
                    :class="ns.e('footer-btn')"
                    @click="!panelOnly && (visible = false)"
                  >
                    {{ t("datepicker.cancel") }}
                  </button>
                  <button
                    :class="[ns.e('footer-btn'), ns.e('footer-btn--confirm')]"
                    @click="handleConfirmClick"
                  >
                    {{ t("datepicker.confirm") }}
                  </button>
                </div>
              </slot>
            </div>
            <div
              v-if="presets.length > 0 && presetPosition === 'bottom'"
              :class="[ns.e('presets'), ns.is('bottom')]"
            >
              <button
                v-for="p in presets"
                :key="p.label"
                :class="ns.e('preset-item')"
                @click="handlePresetClick(p)"
              >
                {{ p.label }}
              </button>
            </div>
          </div>
        </div>
      </Transition>
    </Teleport>
  </div>
</template>

<style>
@charset "UTF-8";
/**
 * YH-UI Sass Variables
 * 原具体的 CSS 变量输出规则 (:root, html.dark 等) 已移至 root.scss，以防止各组件样式编译时产生重复的全局变量 CSS 冗余。
 */
/* 尺寸变量映射 - 统一对接 YH-UI 全局主题变量 */
.yh-date-picker {
  /* 局部变量定义 - 允许通过作用域或内联样式覆盖 */
  --yh-date-picker-primary: var(--yh-color-primary);
  --yh-date-picker-primary-rgb: var(--yh-color-primary-rgb);
  --yh-date-picker-text-main: var(--yh-text-color-primary);
  --yh-date-picker-text-secondary: var(--yh-text-color-secondary);
  --yh-date-picker-border: var(--yh-border-color);
  --yh-date-picker-panel-shadow: var(--yh-shadow-lg);
  --yh-date-picker-item-hover: var(--yh-fill-color-light);
  --yh-date-picker-range-bg: var(--yh-color-primary-light-9);
  --yh-date-picker-panel-width: 380px;
  --yh-date-picker-panel-bg: var(--yh-bg-color-overlay);
  --yh-date-picker-hover-bg: var(--yh-date-picker-item-hover);
  --yh-date-picker-active-bg: var(--yh-date-picker-primary);
  --yh-date-picker-active-color: var(--yh-color-white);
  position: relative;
  display: inline-flex;
  width: var(--yh-date-picker-width, 220px);
  font-family: var(--yh-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
  box-sizing: border-box;
  max-width: 100%;
}
.yh-date-picker.is-range {
  width: var(--yh-date-picker-range-width, 100%);
  max-width: 100%;
}
.yh-date-picker.is-disabled {
  opacity: var(--yh-disabled-opacity, 0.5);
  cursor: not-allowed;
}
.yh-date-picker.is-panel-only {
  width: auto;
  display: block;
  max-width: 100%;
  box-sizing: border-box;
}
.yh-date-picker__wrapper {
  position: relative;
  display: inline-flex;
  align-items: center;
  width: 100%;
  max-width: 100%;
  padding: 0 12px;
  background: var(--yh-bg-color);
  border: 1px solid var(--yh-date-picker-border);
  border-radius: var(--yh-radius-md);
  transition: border-color 0.25s, box-shadow 0.25s;
  cursor: pointer;
  box-sizing: border-box;
}
.yh-date-picker__wrapper:hover:not(.is-disabled) {
  border-color: var(--yh-date-picker-primary);
}

.yh-date-picker {
  /* 应用统一 Size */
}
.yh-date-picker--large .yh-date-picker__wrapper {
  height: var(--yh-component-size-large);
}

.yh-date-picker--large {
  font-size: var(--yh-font-size-lg);
}

.yh-date-picker--default .yh-date-picker__wrapper {
  height: var(--yh-component-size-default);
}

.yh-date-picker--default {
  font-size: var(--yh-font-size-base);
}

.yh-date-picker--small .yh-date-picker__wrapper {
  height: var(--yh-component-size-small);
}

.yh-date-picker--small {
  font-size: var(--yh-font-size-sm);
}

.yh-date-picker__icon {
  display: flex;
  align-items: center;
  color: var(--yh-date-picker-text-secondary);
  margin-right: 8px;
  font-size: 16px;
  flex-shrink: 0;
}

.yh-date-picker__inner {
  flex: 1;
  border: none !important;
  outline: none !important;
  background: transparent !important;
  color: var(--yh-date-picker-text-main);
  font-size: inherit;
  cursor: pointer;
  padding: 0 30px 0 0 !important; /* 为右侧清空按钮留出固定空间，防止文字重叠和布局抖动 */
  box-shadow: none !important;
  height: 100%;
}

.yh-date-picker__range-input-wrapper {
  display: flex;
  align-items: center;
  flex: 1;
  height: 100%;
  padding-right: 24px; /* 范围模式同样留出后缀空间，精简以减少偏心感 */
  min-width: 0;
}

.yh-date-picker__range-input {
  flex: 1;
  border: none !important;
  outline: none !important;
  background: transparent !important;
  text-align: center;
  color: var(--yh-date-picker-text-main);
  font-weight: 500;
  padding: 0 !important;
  box-shadow: none !important;
  height: 100%;
  min-width: 0;
}

.yh-date-picker__range-separator {
  padding: 0 8px;
  font-size: 13px;
  color: var(--yh-date-picker-text-secondary);
  flex-shrink: 0;
}

.yh-date-picker__suffix {
  position: absolute; /* 核心修复：绝对定位确保不会飘出去或影响文字对齐 */
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  pointer-events: none; /* 穿透，除非内部按钮可见 */
}
.yh-date-picker__clear {
  font-size: 16px;
  color: var(--yh-date-picker-text-secondary);
  cursor: pointer;
  display: flex;
  align-items: center;
  transition: color 0.2s;
  pointer-events: auto; /* 允许点击清空 */
}
.yh-date-picker__clear:hover {
  color: var(--yh-color-danger);
}

.yh-date-picker {
  /* 面板 - 彻底隔离文档样式干扰 */
}
.yh-date-picker__panel {
  /* 核心修复：确保面板在 Teleport 后也能解析局部变量 */
  --yh-date-picker-primary: var(--yh-color-primary);
  --yh-date-picker-primary-rgb: var(--yh-color-primary-rgb);
  --yh-date-picker-text-main: var(--yh-text-color-primary);
  --yh-date-picker-text-secondary: var(--yh-text-color-secondary);
  --yh-date-picker-border: var(--yh-border-color);
  --yh-date-picker-panel-shadow: var(--yh-shadow-lg);
  --yh-date-picker-item-hover: var(--yh-fill-color-light);
  --yh-date-picker-range-bg: var(--yh-color-primary-light-9);
  --yh-date-picker-panel-bg: var(--yh-bg-color-overlay);
  --yh-date-picker-hover-bg: var(--yh-date-picker-item-hover);
  --yh-date-picker-active-bg: var(--yh-date-picker-primary);
  --yh-date-picker-active-color: var(--yh-color-white);
  position: fixed;
  background: var(--yh-date-picker-panel-bg);
  border-radius: var(--yh-radius-xl);
  box-shadow: var(--yh-date-picker-panel-shadow);
  border: 1px solid var(--yh-border-color-extra-light);
  margin-top: 12px;
  z-index: var(--yh-z-index-popper);
  width: min(var(--yh-date-picker-panel-width), 100vw - 16px) !important;
  box-sizing: border-box !important;
  display: inline-block;
  overflow: hidden;
  animation: yh-picker-pop 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.yh-date-picker__panel.is-plain {
  position: static;
  box-shadow: none;
  border: none;
}
.yh-date-picker__panel--has-presets-left {
  display: flex !important;
  flex-direction: row;
  --yh-date-picker-panel-width: 500px !important;
}

.yh-date-picker__panel--has-presets-right {
  display: flex !important;
  flex-direction: row-reverse;
  --yh-date-picker-panel-width: 500px !important;
}

.yh-date-picker__panel--has-presets-top {
  display: flex !important;
  flex-direction: column;
}

.yh-date-picker__panel--has-presets-bottom {
  display: flex !important;
  flex-direction: column-reverse;
}

.yh-date-picker__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 20px 24px 8px;
  box-sizing: border-box;
}
.yh-date-picker__header-label {
  font-size: 17px;
  font-weight: 700;
  color: var(--yh-date-picker-text-main);
  cursor: pointer;
  padding: 4px 12px;
  border-radius: 8px;
  transition: all 0.2s;
  white-space: nowrap;
  flex-shrink: 0;
}
.yh-date-picker__header-label:hover {
  background: var(--yh-date-picker-item-hover);
  color: var(--yh-date-picker-primary);
}

.yh-date-picker__header-group {
  display: flex;
  gap: 4px;
  background: var(--yh-date-picker-item-hover);
  padding: 4px;
  border-radius: 50px;
}

.yh-date-picker__header-btns {
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: none;
  background: transparent;
  cursor: pointer;
  font-size: 14px;
  color: var(--yh-date-picker-text-main);
}
.yh-date-picker__header-btns:hover {
  background: var(--yh-bg-color);
  color: var(--yh-date-picker-primary);
  box-shadow: var(--yh-shadow-sm);
}

.yh-date-picker__content {
  padding: 0 20px 24px; /* 极致平衡补白 */
  width: 100% !important;
  box-sizing: border-box !important;
  display: block !important; /* 避免 flex 导致 table 宽度计算失效 */
}

.yh-date-picker {
  /* 表格系统 - 强制重置，防御文档样式 */
}
.yh-date-picker table.yh-date-picker__table {
  display: table !important;
  width: 100% !important;
  max-width: 100% !important;
  table-layout: fixed !important;
  border-collapse: collapse !important;
  border: none !important;
  margin: 0 !important;
  padding: 0 !important;
  background: transparent !important;
  /* 暴力重置文档中 table th td 可能存在的干扰 */
}
.yh-date-picker table.yh-date-picker__table thead,
.yh-date-picker table.yh-date-picker__table tbody,
.yh-date-picker table.yh-date-picker__table tr {
  border: none !important;
  background: transparent !important;
}
.yh-date-picker table.yh-date-picker__table th {
  height: 44px !important;
  border: none !important;
  padding: 0 !important;
  font-size: 12px;
  color: var(--yh-date-picker-text-secondary);
  font-weight: 700;
  text-align: center;
  vertical-align: middle;
  opacity: 0.5;
  background: transparent !important;
}
.yh-date-picker table.yh-date-picker__table td {
  border: none !important;
  padding: 0 !important;
  margin: 0 !important;
  background: transparent !important;
  text-align: center;
  vertical-align: middle;
}
.yh-date-picker__cell {
  height: 48px;
  position: relative;
  cursor: pointer;
}
.yh-date-picker__cell-content {
  width: 42px;
  height: 44px; /* 提升高度，给间距留出空间 */
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin: 0 auto;
  border-radius: 12px;
  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
  position: relative;
  z-index: 2;
}
.yh-date-picker__cell-date {
  font-size: 14px;
  font-weight: 700;
  line-height: 1;
}

.yh-date-picker__cell-extra {
  font-size: 10px;
  font-weight: 400;
  line-height: 1.2;
  margin-top: 4px; /* 增加文案间距 */
  opacity: 0.8;
}

.yh-date-picker__cell {
  /* 范围背景条 - 统一背景逻辑 */
}
.yh-date-picker__cell.is-in-range::before, .yh-date-picker__cell.is-range-start::before, .yh-date-picker__cell.is-range-end::before {
  content: "";
  position: absolute;
  top: 5px;
  bottom: 5px;
  left: 0;
  right: 0;
  background: var(--yh-date-picker-range-bg);
  z-index: 1;
}
.yh-date-picker__cell.is-in-range .yh-date-picker__cell-content {
  color: var(--yh-date-picker-primary);
}

.yh-date-picker__cell.is-range-start::before {
  border-radius: 10px 0 0 10px;
}
.yh-date-picker__cell.is-range-end::before {
  border-radius: 0 10px 10px 0;
}
.yh-date-picker__cell {
  /* 选中效果 */
}
.yh-date-picker__cell.is-selected .yh-date-picker__cell-content {
  background: var(--yh-date-picker-primary);
  color: var(--yh-color-white) !important;
  box-shadow: var(--yh-shadow-md);
}

.yh-date-picker__cell {
  /* 形状配置化逻辑 */
}
.yh-date-picker__cell.is-round .yh-date-picker__cell-content {
  border-radius: 50%;
}

.yh-date-picker__cell.is-round.is-range-start::before {
  border-radius: 19px 0 0 19px;
}
.yh-date-picker__cell.is-round.is-range-end::before {
  border-radius: 0 19px 19px 0;
}
.yh-date-picker__cell:hover:not(.is-disabled):not(.is-selected) .yh-date-picker__cell-content {
  background: var(--yh-date-picker-item-hover);
  color: var(--yh-date-picker-primary);
  transform: translateY(-1px);
}

.yh-date-picker__cell {
  /* 今日点缀 */
}
.yh-date-picker__cell.is-today:not(.is-selected) .yh-date-picker__cell-content {
  color: var(--yh-date-picker-primary);
}
.yh-date-picker__cell.is-today:not(.is-selected) .yh-date-picker__cell-content::after {
  content: "";
  position: absolute;
  bottom: 0px; /* 向下微调，增加点与文字的距离 */
  left: 50%;
  transform: translateX(-50%);
  width: 4px;
  height: 4px;
  background: var(--yh-date-picker-primary);
  border-radius: 50%;
}

.yh-date-picker__cell.is-prev-month, .yh-date-picker__cell.is-next-month {
  color: var(--yh-date-picker-text-secondary);
  opacity: 0.35;
}
.yh-date-picker__cell.is-disabled {
  opacity: 0.2;
  cursor: not-allowed;
}

.yh-date-picker {
  /* 非日期视图网格 - 彻底解决编译合并报错 */
}
.yh-date-picker__table--month {
  display: grid !important;
  grid-template-columns: repeat(4, 1fr) !important;
  gap: 0; /* 移除 gap 保证范围背景连贯 */
  padding: 12px 0;
}
.yh-date-picker__table--month .yh-date-picker__cell {
  height: 74px;
}
.yh-date-picker__table--month .yh-date-picker__cell .yh-date-picker__cell-content {
  width: 100%;
  max-width: 68px; /* 约束宽度避免过扁 */
  height: 48px;
  border-radius: 14px;
}

.yh-date-picker__table--year {
  display: grid !important;
  grid-template-columns: repeat(4, 1fr) !important;
  gap: 0;
  padding: 12px 0;
}
.yh-date-picker__table--year .yh-date-picker__cell {
  height: 74px;
}
.yh-date-picker__table--year .yh-date-picker__cell .yh-date-picker__cell-content {
  width: 100%;
  max-width: 68px;
  height: 48px;
  border-radius: 14px;
}

.yh-date-picker__table--quarter {
  display: grid !important;
  grid-template-columns: repeat(4, 1fr) !important;
  gap: 0;
  padding: 12px 0;
}
.yh-date-picker__table--quarter .yh-date-picker__cell {
  height: 90px;
}
.yh-date-picker__table--quarter .yh-date-picker__cell .yh-date-picker__cell-content {
  width: 100%;
  max-width: 68px;
  height: 64px;
  border-radius: 18px;
}

.yh-date-picker__presets {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 12px 24px;
  background: var(--yh-date-picker-item-hover);
  border-top: 1px solid rgba(0, 0, 0, 0.04);
}
.yh-date-picker__presets.is-left {
  flex-direction: column;
  border-top: none;
  border-right: 1px solid rgba(0, 0, 0, 0.04);
  padding: 24px 12px;
  width: 120px;
  flex-shrink: 0;
}
.yh-date-picker__presets.is-right {
  flex-direction: column;
  border-top: none;
  border-left: 1px solid rgba(0, 0, 0, 0.04);
  padding: 24px 12px;
  width: 120px;
  flex-shrink: 0;
}
.yh-date-picker__presets.is-top {
  border-top: none;
  border-bottom: 1px solid rgba(0, 0, 0, 0.04);
}
.yh-date-picker__presets.is-bottom {
  border-top: 1px solid rgba(0, 0, 0, 0.04);
}

.yh-date-picker__preset-item {
  padding: 5px 12px;
  font-size: 13px;
  font-weight: 600;
  color: var(--yh-date-picker-text-secondary);
  background: var(--yh-bg-color);
  border: 1px solid var(--yh-date-picker-border);
  border-radius: var(--yh-radius-md);
  cursor: pointer;
  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.yh-date-picker__preset-item:hover {
  color: var(--yh-date-picker-primary);
  border-color: var(--yh-date-picker-primary);
  box-shadow: var(--yh-shadow-sm);
  transform: translateY(-1px);
}

.yh-date-picker {
  /* 底部操作 */
}
.yh-date-picker__footer {
  padding: 16px 24px 24px;
  border-top: 1px solid rgba(0, 0, 0, 0.05);
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  box-sizing: border-box;
}
.yh-date-picker__footer-time-range {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.yh-date-picker__footer-time {
  font-weight: 700;
  color: var(--yh-date-picker-text-main);
  font-size: 13px;
  padding: 6px 12px;
  background: var(--yh-date-picker-item-hover);
  border-radius: 10px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.yh-date-picker__footer-time select {
  border: 1px solid var(--yh-date-picker-border);
  background: var(--yh-bg-color);
  color: var(--yh-date-picker-text-main);
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  padding: 2px 6px;
  border-radius: 6px;
  outline: none;
  cursor: pointer;
  transition: border-color 0.2s, box-shadow 0.2s;
}
.yh-date-picker__footer-time select:focus {
  border-color: var(--yh-date-picker-primary);
  box-shadow: 0 0 0 2px var(--yh-date-picker-range-bg);
}

.yh-date-picker__time-separator {
  color: var(--yh-date-picker-text-secondary);
  font-weight: bold;
}

.yh-date-picker__time-label {
  margin-right: 4px;
  font-size: 12px;
  color: var(--yh-date-picker-text-secondary);
}

.yh-date-picker__footer-btns {
  display: flex;
  gap: 12px;
  margin-left: auto;
}

.yh-date-picker__footer-btn {
  padding: 10px 22px;
  border-radius: var(--yh-radius-lg);
  font-weight: 600;
  font-size: var(--yh-font-size-base);
  cursor: pointer;
  transition: all 0.2s;
  border: 1px solid var(--yh-date-picker-border);
  background: var(--yh-bg-color);
  color: var(--yh-date-picker-text-main);
}
.yh-date-picker__footer-btn:hover {
  border-color: var(--yh-date-picker-primary);
  color: var(--yh-date-picker-primary);
  transform: translateY(-1px);
}
.yh-date-picker__footer-btn--confirm {
  background: var(--yh-date-picker-primary);
  color: var(--yh-color-white) !important;
  border: none;
  box-shadow: var(--yh-shadow-md);
}

@keyframes yh-picker-pop {
  from {
    opacity: 0;
    transform: translateY(8px) scale(0.97);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}
</style>
