<!-- SPEC: PJ2026-010405 云端控制台. -->
<!-- Implementation reference: draft-2026-07-13-p0-cloud-console. -->

<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, ref, toRef, watch } from "vue";
import { nextOverlayId, useOverlaySurface } from "@/composables/useOverlaySurface";

const props = withDefaults(defineProps<{
  open: boolean;
  title: string;
  placement?: "start" | "end";
  offset?: number;
}>(), {
  placement: "start",
  offset: 8
});

const emit = defineEmits<{ close: []; toggle: [] }>();
const anchor = ref<HTMLElement | null>(null);
const surface = ref<HTMLElement | null>(null);
const titleId = nextOverlayId("console-popover-title");
const position = ref({ top: 0, left: 0 });
const surfaceStyle = computed(() => ({ top: `${position.value.top}px`, left: `${position.value.left}px` }));

useOverlaySurface(toRef(props, "open"), surface, () => emit("close"), { modal: false, trapFocus: false });

watch(() => props.open, async (open) => {
  if (!open) return;
  await nextTick();
  updatePosition();
});

onMounted(() => {
  document.addEventListener("pointerdown", onOutsidePointer);
  window.addEventListener("resize", updatePosition);
  window.addEventListener("scroll", updatePosition, true);
});

onBeforeUnmount(() => {
  document.removeEventListener("pointerdown", onOutsidePointer);
  window.removeEventListener("resize", updatePosition);
  window.removeEventListener("scroll", updatePosition, true);
});

function updatePosition(): void {
  const anchorRect = anchor.value?.getBoundingClientRect();
  const surfaceRect = surface.value?.getBoundingClientRect();
  if (!anchorRect || !surfaceRect) return;
  const desiredLeft = props.placement === "end" ? anchorRect.right - surfaceRect.width : anchorRect.left;
  position.value = {
    top: Math.max(12, Math.min(window.innerHeight - surfaceRect.height - 12, anchorRect.bottom + props.offset)),
    left: Math.max(12, Math.min(window.innerWidth - surfaceRect.width - 12, desiredLeft))
  };
}

function onOutsidePointer(event: PointerEvent): void {
  const target = event.target;
  if (!props.open || !(target instanceof Node)) return;
  if (anchor.value?.contains(target) || surface.value?.contains(target)) return;
  emit("close");
}
</script>

<template>
  <span ref="anchor" class="console-popover-anchor">
    <slot name="trigger" :open="open" :toggle="() => emit('toggle')">
      <button type="button" class="btn btn-secondary btn-sm" :aria-expanded="open" aria-haspopup="dialog" @click="emit('toggle')">{{ title }}</button>
    </slot>
  </span>
  <Teleport to="body">
    <section v-if="open" ref="surface" class="console-popover" :style="surfaceStyle" role="dialog" :aria-labelledby="titleId" tabindex="-1">
      <strong :id="titleId" class="sr-only">{{ title }}</strong>
      <slot />
    </section>
  </Teleport>
</template>
