<script setup lang="ts">
/**
 * 浮动面板展示层：组合 Vant Overlay + FloatingPanel，关闭动画与 CSS 变量。
 * 锚点合并 / autoHeight 逻辑见 `floatPanelAnchors.ts`、`useFloatPanelAnchorsAutoHeight.ts`。
 */
import { computed, nextTick, onUpdated, ref, watch, watchPostEffect } from 'vue';
import { useElementSize } from '@vueuse/core';
import { computeAnchorsAndSnapHeight } from './floatPanelAnchors';
import type { FloatPanelInstance, FloatPanelProps } from './types';
import { useFloatPanelAnchorsAutoHeight } from './useFloatPanelAnchorsAutoHeight';

// --- props（v-model：panel-height / panel-anchor）---
const {
  closeOnClickOverlay,
  overlay = true,
  overlayClass,
  overlayStyle,

  containerClass,
  containerStyle,

  title,
  maxContainerHeight = innerHeight,
  full = false,
  closable = false,

  contentDraggable = true,

  autoHeight = false,

  duration = 0.3,
} = defineProps<FloatPanelProps>();

// --- 动画：秒 -> 数值；与 Vant `duration`、关闭收尾定时器共用 ---
const durationSec = computed(() => {
  const n = Number(duration);
  return Number.isFinite(n) && n >= 0 ? n : 0.3;
});

/** 关闭动画期间保持遮罩，避免 height 已为 0 时立刻隐藏打断过渡 */
const isClosing = ref(false);
const closeTimer = ref<ReturnType<typeof setTimeout> | null>(null);

function clearCloseTimer() {
  if (closeTimer.value != null) {
    clearTimeout(closeTimer.value);
    closeTimer.value = null;
  }
}

onBeforeUnmount(() => {
  clearCloseTimer();
  isClosing.value = false;
});

// --- 样式 class ---
const overlayClassList = computed(() => {
  const value = Array.isArray(overlay) ? overlayClass : overlayClass ? [overlayClass] : [];
  return ['float-panel-overlay', ...value, { hide: !overlay }];
});
const containerClassList = computed(() => {
  const value = Array.isArray(containerClass) ? containerClass : containerClass ? [containerClass] : [];
  return ['float-panel-container', ...value, { 'float-panel--auto-height': autoHeight }];
});

const height = defineModel<number>('panel-height', { default: 20 });
const anchors = defineModel<number[]>('panel-anchor', { default: [] });

const emit = defineEmits<{
  /** 关闭动画与遮罩收尾结束（`duration` 为 0 时为同步收尾后） */
  closed: [];
}>();

/** 立刻把 v-model height 置 0（Vant 做过渡）；遮罩用 isClosing 多留一程；结束后 `emit('closed')` */
function applyCloseAnimation() {
  clearCloseTimer();
  isClosing.value = true;
  height.value = 0;
  const ms = durationSec.value * 1000;
  if (ms <= 0) {
    isClosing.value = false;
    emit('closed');
    return;
  }
  closeTimer.value = setTimeout(() => {
    closeTimer.value = null;
    isClosing.value = false;
    emit('closed');
  }, ms);
}

// --- 布局测量：头高参与 setHeight；内容外包层高度参与 autoHeight ---
const panelHeaderRef = useTemplateRef('panel-header');
const { height: panelHeaderHeight } = useElementSize(panelHeaderRef, { height: 30 }, { box: 'border-box' });

const panelContentRootRef = useTemplateRef('panel-content-root');
const { height: measuredContentHeight } = useElementSize(panelContentRootRef, { height: 0 }, { box: 'border-box' });
const panelContentHeight = ref(0);

/** autoHeight 下内容区被限制高度后，useElementSize 只有可视高；锚点需按 scrollHeight 取真实内容高 */
const intrinsicAutoContentHeight = ref(0);
function syncIntrinsicAutoContentHeight() {
  if (!autoHeight) return;
  nextTick(() => {
    const el = panelContentRootRef.value;
    const sh = el?.scrollHeight ?? 0;
    if (intrinsicAutoContentHeight.value !== sh) intrinsicAutoContentHeight.value = sh;
  });
}
watchPostEffect(() => {
  if (!autoHeight) return;
  height.value;
  panelContentHeight.value;
  measuredContentHeight.value;
  syncIntrinsicAutoContentHeight();
});
onUpdated(() => {
  if (autoHeight) syncIntrinsicAutoContentHeight();
});

const measuredContentHeightForAnchors = computed(() =>
  autoHeight ? intrinsicAutoContentHeight.value : measuredContentHeight.value,
);

const style = computed(() => {
  return {
    '--van-floating-panel-header-height': `${panelHeaderHeight.value}px`,
    '--van-floating-panel-content-height': `${panelContentHeight.value}px`,
  };
});

// --- 对外 API（expose / useFloatPanel ref）---
const instance: FloatPanelInstance = {
  getAnchors() {
    return toValue(anchors);
  },
  setAnchors(values: number | number[], reset = true) {
    const result = computeAnchorsAndSnapHeight(
      values,
      reset,
      height.value,
      maxContainerHeight,
      anchors.value,
    );
    if (!result) return;
    anchors.value = result.anchors;
    height.value = result.height;
  },
  setHeight(value: number, updateAnchor = true) {
    const total = value + panelHeaderHeight.value;
    if (updateAnchor) {
      instance.setAnchors(total, false);
      height.value = total;
      return;
    }
    height.value = total;
  },
  closeAnimated: applyCloseAnimation,

  snapMin() {
    const target = toValue(anchors).find((a) => a > 0);
    if (target === undefined) return false;
    if (height.value === target) return false;
    instance.setAnchors(target, false);
    height.value = target;
    return true;
  },
  snapMax() {
    const list = toValue(anchors);
    const target = list[list.length - 1];
    if (target === undefined) return false;
    if (height.value === target) return false;
    instance.setAnchors(target, false);
    height.value = target;
    return true;
  },
  snapFull() {
    const target = maxContainerHeight;
    if (height.value === target) return false;
    instance.setAnchors(target, false);
    height.value = target;
    return true;
  },
  snapToFirstAnchor() {
    return instance.snapMin();
  },
  snapToLastAnchor() {
    return instance.snapMax();
  },
  snapToMaxContainerHeight() {
    return instance.snapFull();
  },
};

const { resetAutoHeightSyncOnReopen } = useFloatPanelAnchorsAutoHeight({
  height,
  measuredContentHeight: measuredContentHeightForAnchors,
  panelHeaderHeight,
  closable: () => closable,
  full: () => full,
  maxContainerHeight: () => maxContainerHeight,
  autoHeight: () => autoHeight,
  setAnchors: (v, r) => instance.setAnchors(v, r),
  setHeight: (v, u) => instance.setHeight(v, u),
});

const onClose = () => applyCloseAnimation();
const onClickOverlay = () => {
  if (closeOnClickOverlay) onClose();
};

/** 与 Vant `height-change` 一致：内容区高度 = 面板总高 − 头高。逻辑层改 `v-model:height` 时未必触发该事件，需自行同步 CSS 变量 */
function syncPanelContentHeightFromTotal(total: number) {
  panelContentHeight.value =
    total <= 0 ? 0 : Math.max(0, total - panelHeaderHeight.value);
}

const onUpdateHeight = ({ height: h }: { height: number }) => {
  syncPanelContentHeightFromTotal(h);
};

watch(
  [height, panelHeaderHeight],
  () => syncPanelContentHeightFromTotal(height.value),
  { flush: 'post', immediate: true },
);

watch(height, (h, prev) => {
  if (h !== 0) {
    clearCloseTimer();
    isClosing.value = false;
  } else if (prev !== undefined && prev !== 0 && !isClosing.value) {
    // 非 closeAnimated：等 v-model 同步到 Wrapper 后再通知（便于 `onUpdate:panelHeight` 先置 pending）
    nextTick(() => emit('closed'));
  }
  resetAutoHeightSyncOnReopen(prev, h);
});

defineExpose(instance);
</script>

<template>
  <!-- isClosing：height 已为 0 时仍短暂展示遮罩，与 Vant 收起动画同长 -->
  <van-overlay
    :class="overlayClassList"
    :style="overlayStyle"
    :show="height !== 0 || isClosing"
    :duration="durationSec"
    @click="onClickOverlay"
  >
    <van-floating-panel
      :class="containerClassList"
      :style="[containerStyle, style]"
      v-model:height="height"
      :duration="durationSec"
      :anchors="anchors"
      :content-draggable="contentDraggable"
      @click.stop
      @height-change="onUpdateHeight"
    >
      <template #header>
        <div class="van-floating-panel__header">
          <div ref="panel-header">
            <van-icon v-if="closable" class="van-floating-panel__header_icon" name="cross" size="18" @click.stop="onClose()" />
            <div class="van-floating-panel__header-bar"></div>
            <slot name="header" :title="title">
              <div>{{ title }}</div>
            </slot>
          </div>
        </div>
      </template>
      <template #default>
        <div ref="panel-content-root" class="float-panel-content-root">
          <slot name="content"></slot>
        </div>
      </template>
    </van-floating-panel>
  </van-overlay>
</template>

<style scoped lang="scss">
.float-panel-overlay {
  --van-overlay-z-index: var(--van-floating-panel-z-index);
  &.hide {
    --van-overlay-background: transparent;
  }
}
.float-panel-container {
  $gap: calc((30px - var(--van-floating-panel-bar-height)) / 2);

  :deep(.van-floating-panel__header) {
    padding: 0;
    position: relative;
    > div {
      padding-top: $gap;
      display: flex;
      flex-direction: column;
      align-items: center;
      width: 100%;
      min-height: 30px;
    }
    .van-floating-panel__header-bar {
      & + * {
        padding: calc($gap / 2) 0;
      }
    }
    .van-floating-panel__header_icon {
      position: absolute;
      top: calc($gap / 2);
      right:calc($gap / 2);
      color: var(--van-floating-panel-bar-color);
    }
  }
  :deep(.van-floating-panel__content) {
    height: var(--van-floating-panel-content-height);
    overflow: hidden !important;
    flex: none;
    min-height: 0;
    > * {
      height: 100%;
      min-height: 0;
      overflow-y: auto;
      box-sizing: border-box;
    }
  }
}
</style>
