<script setup lang="ts">
/**
 * 通用 PC 悬浮子窗口：可拖拽、四角缩放、可最小化，始终保持在可视区域内
 */
import { computed, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue';

import { usePreferences } from '@vben/preferences';

import { CloseOutlined, MinusOutlined } from '@ant-design/icons-vue';

import { SUB_WINDOW_POPUP_Z_INDEX_KEY } from './constants';

const props = withDefaults(
  defineProps<{
    /** 是否显示窗口 */
    open: boolean;
    /** 标题 */
    title?: string;
    /** localStorage 缓存 key */
    storageKey?: string;
    /** 初始宽度 */
    width?: number;
    /** 初始高度 */
    height?: number;
    /** 最小宽度 */
    minWidth?: number;
    /** 最小高度 */
    minHeight?: number;
    /** 初始 X，-1 表示靠右 */
    defaultX?: number;
    /** 初始 Y */
    defaultY?: number;
    /** 层级 */
    zIndex?: number;
  }>(),
  {
    title: '窗口',
    storageKey: 'pc_sub_window_default',
    width: 420,
    height: 520,
    minWidth: 320,
    minHeight: 240,
    defaultX: -1,
    defaultY: 80,
    zIndex: 10000,
  },
);

const emit = defineEmits<{
  'update:open': [boolean];
  close: [];
}>();

const { isDark } = usePreferences();

const posX = ref(100);
const posY = ref(80);
const winWidth = ref(props.width);
const winHeight = ref(props.height);
const minimized = ref(false);

type DragMode =
  | ''
  | 'move'
  | 'resize-se'
  | 'resize-sw'
  | 'resize-ne'
  | 'resize-nw';
const dragMode = ref<DragMode>('');
const dragStartX = ref(0);
const dragStartY = ref(0);
const originX = ref(0);
const originY = ref(0);
const originW = ref(0);
const originH = ref(0);

/** 弹出层 z-index，需高于子窗口本体 */
const popupZIndex = computed(() => props.zIndex + 100);
provide(SUB_WINDOW_POPUP_Z_INDEX_KEY, popupZIndex);

const windowStyle = computed(() => ({
  left: `${posX.value}px`,
  top: `${posY.value}px`,
  width: `${winWidth.value}px`,
  height: minimized.value ? 'auto' : `${winHeight.value}px`,
  zIndex: props.zIndex,
}));

/** 从缓存或默认值初始化窗口位置与尺寸 */
function initLayout() {
  const cached = localStorage.getItem(props.storageKey);
  if (cached) {
    try {
      const data = JSON.parse(cached);
      posX.value = data.posX ?? posX.value;
      posY.value = data.posY ?? posY.value;
      winWidth.value = data.width ?? winWidth.value;
      winHeight.value = data.height ?? winHeight.value;
      minimized.value = !!data.minimized;
    } catch {
      /* ignore */
    }
  } else {
    winWidth.value = props.width;
    winHeight.value = props.height;
    posX.value =
      props.defaultX >= 0
        ? props.defaultX
        : Math.max(16, window.innerWidth - winWidth.value - 24);
    posY.value = props.defaultY;
  }
  clampWindow();
}

/** 约束窗口在可视区域内 */
function clampWindow() {
  const h = minimized.value ? 44 : winHeight.value;
  winWidth.value = Math.max(
    props.minWidth,
    Math.min(winWidth.value, window.innerWidth - 16),
  );
  winHeight.value = Math.max(
    props.minHeight,
    Math.min(winHeight.value, window.innerHeight - 16),
  );
  posX.value = Math.max(0, Math.min(posX.value, window.innerWidth - winWidth.value));
  posY.value = Math.max(0, Math.min(posY.value, window.innerHeight - h));
}

function saveLayout() {
  localStorage.setItem(
    props.storageKey,
    JSON.stringify({
      posX: posX.value,
      posY: posY.value,
      width: winWidth.value,
      height: winHeight.value,
      minimized: minimized.value,
    }),
  );
}

function bindDragListeners() {
  document.addEventListener('mousemove', onDocumentMouseMove);
  document.addEventListener('mouseup', onDocumentMouseUp);
}

function onTitleMouseDown(e: MouseEvent) {
  if (e.button !== 0) return;
  dragMode.value = 'move';
  dragStartX.value = e.clientX;
  dragStartY.value = e.clientY;
  originX.value = posX.value;
  originY.value = posY.value;
  bindDragListeners();
}

/** 四角缩放开始：记录初始位置与尺寸 */
function onResizeMouseDown(e: MouseEvent, mode: DragMode) {
  if (e.button !== 0) return;
  e.preventDefault();
  e.stopPropagation();
  dragMode.value = mode;
  dragStartX.value = e.clientX;
  dragStartY.value = e.clientY;
  originX.value = posX.value;
  originY.value = posY.value;
  originW.value = winWidth.value;
  originH.value = winHeight.value;
  bindDragListeners();
}

/** 左/上方向缩放时，宽高触达最小值需同步修正锚点位置 */
function applyHorizontalResizeFromLeft(dx: number) {
  let newW = originW.value - dx;
  let newX = originX.value + dx;
  if (newW < props.minWidth) {
    newX = originX.value + originW.value - props.minWidth;
    newW = props.minWidth;
  }
  winWidth.value = newW;
  posX.value = newX;
}

function applyVerticalResizeFromTop(dy: number) {
  let newH = originH.value - dy;
  let newY = originY.value + dy;
  if (newH < props.minHeight) {
    newY = originY.value + originH.value - props.minHeight;
    newH = props.minHeight;
  }
  winHeight.value = newH;
  posY.value = newY;
}

function onDocumentMouseMove(e: MouseEvent) {
  const dx = e.clientX - dragStartX.value;
  const dy = e.clientY - dragStartY.value;

  if (dragMode.value === 'move') {
    posX.value = originX.value + dx;
    posY.value = originY.value + dy;
    clampWindow();
    return;
  }

  if (dragMode.value === 'resize-se') {
    winWidth.value = originW.value + dx;
    winHeight.value = originH.value + dy;
  } else if (dragMode.value === 'resize-sw') {
    applyHorizontalResizeFromLeft(dx);
    winHeight.value = originH.value + dy;
  } else if (dragMode.value === 'resize-ne') {
    winWidth.value = originW.value + dx;
    applyVerticalResizeFromTop(dy);
  } else if (dragMode.value === 'resize-nw') {
    applyHorizontalResizeFromLeft(dx);
    applyVerticalResizeFromTop(dy);
  }
  clampWindow();
}

function onDocumentMouseUp() {
  dragMode.value = '';
  document.removeEventListener('mousemove', onDocumentMouseMove);
  document.removeEventListener('mouseup', onDocumentMouseUp);
  saveLayout();
}

function toggleMinimize() {
  minimized.value = !minimized.value;
  clampWindow();
  saveLayout();
}

function handleClose() {
  emit('update:open', false);
  emit('close');
}

/** 阻止滚轮穿透到主页面：内部可滚动区域正常滚，其余区域拦截 */
function onWindowWheel(e: WheelEvent) {
  e.stopPropagation();
  let node = e.target as HTMLElement | null;
  const root = e.currentTarget as HTMLElement;
  while (node && node !== root) {
    const style = getComputedStyle(node);
    const canScrollY =
      node.scrollHeight > node.clientHeight &&
      (style.overflowY === 'auto' || style.overflowY === 'scroll');
    if (canScrollY) {
      const goingUp = e.deltaY < 0;
      const goingDown = e.deltaY > 0;
      const atTop = node.scrollTop <= 0;
      const atBottom =
        node.scrollTop + node.clientHeight >= node.scrollHeight - 1;
      if ((goingUp && !atTop) || (goingDown && !atBottom)) return;
    }
    node = node.parentElement;
  }
  e.preventDefault();
}

watch(
  () => props.open,
  (v) => {
    if (v) initLayout();
  },
);

onMounted(() => {
  if (props.open) initLayout();
});

onBeforeUnmount(() => {
  document.removeEventListener('mousemove', onDocumentMouseMove);
  document.removeEventListener('mouseup', onDocumentMouseUp);
});
</script>

<template>
  <Teleport to="body">
    <div v-if="open" class="sub-window-root">
      <div
        class="sub-window"
        :class="{ minimized, 'is-dark': isDark }"
        :style="windowStyle"
        @wheel="onWindowWheel"
      >
        <div class="sub-window-header" @mousedown="onTitleMouseDown">
          <span class="sub-window-title">{{ title }}</span>
          <div class="sub-window-actions" @mousedown.stop>
            <button type="button" class="action-btn" @click="toggleMinimize">
              <MinusOutlined />
            </button>
            <button type="button" class="action-btn" @click="handleClose">
              <CloseOutlined />
            </button>
          </div>
        </div>
        <div v-show="!minimized" class="sub-window-body">
          <slot />
        </div>
        <template v-if="!minimized">
          <div
            class="resize-handle resize-handle-nw"
            @mousedown="onResizeMouseDown($event, 'resize-nw')"
          />
          <div
            class="resize-handle resize-handle-ne"
            @mousedown="onResizeMouseDown($event, 'resize-ne')"
          />
          <div
            class="resize-handle resize-handle-sw"
            @mousedown="onResizeMouseDown($event, 'resize-sw')"
          />
          <div
            class="resize-handle resize-handle-se"
            @mousedown="onResizeMouseDown($event, 'resize-se')"
          />
        </template>
      </div>
    </div>
  </Teleport>
</template>

<style scoped>
.sub-window-root {
  pointer-events: none;
}
.sub-window {
  position: fixed;
  pointer-events: auto;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid #e5e7eb;
}
.sub-window.is-dark {
  background: #1f1f1f;
  border-color: #424242;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45);
}
.sub-window-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 12px;
  background: linear-gradient(135deg, #f0fdf4, #ecfdf5);
  border-bottom: 1px solid #e5e7eb;
  cursor: move;
  user-select: none;
  flex-shrink: 0;
}
.sub-window.is-dark .sub-window-header {
  background: linear-gradient(135deg, #1a2e22, #1f1f1f);
  border-bottom-color: #424242;
}
.sub-window-title {
  font-size: 14px;
  font-weight: 600;
  color: #1d2129;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sub-window.is-dark .sub-window-title {
  color: rgba(255, 255, 255, 0.85);
}
.sub-window-actions {
  display: flex;
  gap: 4px;
}
.action-btn {
  width: 28px;
  height: 28px;
  border: none;
  background: transparent;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 4px;
  color: #666;
}
.action-btn:hover {
  background: rgba(0, 0, 0, 0.06);
}
.sub-window.is-dark .action-btn {
  color: rgba(255, 255, 255, 0.65);
}
.sub-window.is-dark .action-btn:hover {
  background: rgba(255, 255, 255, 0.08);
}
.sub-window-body {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  overscroll-behavior: contain;
}
/* 四角透明缩放热区，无视觉提示 */
.resize-handle {
  position: absolute;
  width: 16px;
  height: 16px;
  z-index: 20;
  pointer-events: auto;
  background: transparent;
}
.resize-handle-nw {
  left: 0;
  top: 0;
  cursor: nwse-resize;
}
.resize-handle-ne {
  right: 0;
  top: 0;
  cursor: nesw-resize;
}
.resize-handle-sw {
  left: 0;
  bottom: 0;
  cursor: nesw-resize;
}
.resize-handle-se {
  right: 0;
  bottom: 0;
  cursor: nwse-resize;
}
</style>
