<template>
  <Teleport to="body">
    <div v-if="enabled" class="liquid-glass-stage" ref="stageRef">
      <div class="liquid-glass-panel" ref="panelRef" :style="panelStyle" @pointerdown="handlePointerDown">
        <svg
          class="liquid-glass-svg"
          :width="panelSize.width"
          :height="panelSize.height"
          color-interpolation-filters="sRGB"
          xmlns="http://www.w3.org/2000/svg"
          xmlns:xlink="http://www.w3.org/1999/xlink"
        >
          <defs>
            <filter
              :id="svgFilterId"
              x="0"
              y="0"
              :width="panelSize.width"
              :height="panelSize.height"
              filterUnits="userSpaceOnUse"
              color-interpolation-filters="sRGB"
            >
              <feImage
                :id="displacementMapId"
                x="0"
                y="0"
                :width="panelSize.width"
                :height="panelSize.height"
                preserveAspectRatio="none"
                :href="displacementMapUrl || undefined"
                :result="displacementResultId"
              />
              <feDisplacementMap
                in="SourceGraphic"
                :in2="displacementResultId"
                xChannelSelector="R"
                yChannelSelector="G"
                :scale="displacementScale"
              />
            </filter>
          </defs>
          <template v-if="snapshot">
            <image
              :href="snapshot.url"
              :width="snapshot.width"
              :height="snapshot.height"
              preserveAspectRatio="none"
              :x="imageOffset.x"
              :y="imageOffset.y"
              :filter="`url(#${svgFilterId})`"
            />
          </template>
          <template v-else>
            <rect :width="panelSize.width" :height="panelSize.height" :fill="`url(#${fallbackGradientId})`" />
            <defs>
              <linearGradient :id="fallbackGradientId" x1="0" y1="0" x2="1" y2="1">
                <stop offset="0%" stop-color="rgba(180, 200, 255, 0.35)" />
                <stop offset="100%" stop-color="rgba(50, 70, 120, 0.35)" />
              </linearGradient>
            </defs>
          </template>
        </svg>
        <div class="liquid-glass-specular" aria-hidden="true"></div>
        <div class="liquid-glass-empty" v-if="!snapshot">
          <p>{{ capturing ? '捕获中...' : '点击下方刷新背景' }}</p>
        </div>
      </div>
      <div class="liquid-glass-toolbar">
        <button type="button" class="liquid-glass-btn" :disabled="capturing" @click="captureSnapshot">
          {{ capturing ? '捕获中...' : '刷新背景' }}
        </button>
        <button type="button" class="liquid-glass-btn ghost" @click="personalization.toggleLiquidGlassExperiment()">
          关闭实验
        </button>
      </div>
    </div>
  </Teleport>
</template>

<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import html2canvas from 'html2canvas';
import { usePersonalizationStore } from '@/stores/personalization';

defineOptions({ name: 'LiquidGlassWidget' });

const PANEL_WIDTH = 300;
const PANEL_HEIGHT = 200;
const EDGE_GAP = 10;
const CANVAS_DPI = 1;
const SNAPSHOT_SCALE = typeof window !== 'undefined' ? Math.min(window.devicePixelRatio || 1, 2) : 1;
const DISPLACEMENT_INTENSITY = 0.45;

interface PanelPosition {
  left: number;
  top: number;
}

interface SnapshotPayload {
  url: string;
  width: number; // CSS 像素
  height: number;
  scale: number;
  scrollX: number;
  scrollY: number;
}

const personalization = usePersonalizationStore();
const enabled = computed(() => personalization.experiments.liquidGlassEnabled);
const stageRef = ref<HTMLDivElement | null>(null);
const panelRef = ref<HTMLDivElement | null>(null);
const panelPosition = ref<PanelPosition>({ left: EDGE_GAP, top: EDGE_GAP });
const dragging = ref(false);
const pointerId = ref<number | null>(null);
const dragOffset = ref({ x: 0, y: 0 });
const snapshot = ref<SnapshotPayload | null>(null);
const capturing = ref(false);
const displacementMapUrl = ref('');
const displacementScale = ref(0);

const filterSeed = Math.random().toString(36).slice(2, 11);
const svgFilterId = `liquid-glass-${filterSeed}_filter`;
const displacementMapId = `liquid-glass-${filterSeed}_map`;
const displacementResultId = `liquid-glass-${filterSeed}_map_result`;
const fallbackGradientId = `liquid-glass-${filterSeed}-fallback`;
const panelSize = { width: PANEL_WIDTH, height: PANEL_HEIGHT };

const clampChannel = (value: number) => Math.max(0, Math.min(255, value));
const mix = (a: number, b: number, t: number) => a * (1 - t) + b * t;

const smoothStep = (a: number, b: number, t: number) => {
  const x = Math.max(0, Math.min(1, (t - a) / (b - a)));
  return x * x * (3 - 2 * x);
};

const length2d = (x: number, y: number) => Math.sqrt(x * x + y * y);

const roundedRectSDF = (x: number, y: number, width: number, height: number, radius: number) => {
  const qx = Math.abs(x) - width + radius;
  const qy = Math.abs(y) - height + radius;
  return Math.min(Math.max(qx, qy), 0) + length2d(Math.max(qx, 0), Math.max(qy, 0)) - radius;
};

const ensureDisplacementReady = () => {
  if (displacementMapUrl.value) {
    return;
  }
  const canvas = document.createElement('canvas');
  canvas.width = PANEL_WIDTH * CANVAS_DPI;
  canvas.height = PANEL_HEIGHT * CANVAS_DPI;
  const context = canvas.getContext('2d');
  if (!context) {
    return;
  }

  const w = canvas.width;
  const h = canvas.height;
  const data = new Uint8ClampedArray(w * h * 4);
  const rawValues = new Float32Array(w * h * 2);
  let maxScale = 0;
  let rawIndex = 0;

  const fragment = (uv: { x: number; y: number }) => {
    const ix = uv.x - 0.5;
    const iy = uv.y - 0.5;
    const distanceToEdge = roundedRectSDF(ix, iy, 0.3, 0.2, 0.6);
    const displacement = smoothStep(0.8, 0, distanceToEdge - 0.15);
    const scaled = smoothStep(0, 1, displacement);
    const displacedX = ix * scaled + 0.5;
    const displacedY = iy * scaled + 0.5;
    return {
      x: mix(uv.x, displacedX, DISPLACEMENT_INTENSITY),
      y: mix(uv.y, displacedY, DISPLACEMENT_INTENSITY)
    };
  };

  for (let y = 0; y < h; y += 1) {
    for (let x = 0; x < w; x += 1) {
      const pos = fragment({ x: x / w, y: y / h });
      const dx = pos.x * w - x;
      const dy = pos.y * h - y;
      maxScale = Math.max(maxScale, Math.abs(dx), Math.abs(dy));
      rawValues[rawIndex++] = dx;
      rawValues[rawIndex++] = dy;
    }
  }

  maxScale *= 0.5;
  const safeScale = maxScale === 0 ? 1 : maxScale;
  rawIndex = 0;

  for (let i = 0; i < data.length; i += 4) {
    const r = rawValues[rawIndex++] / safeScale + 0.5;
    const g = rawValues[rawIndex++] / safeScale + 0.5;
    data[i] = clampChannel(r * 255);
    data[i + 1] = clampChannel(g * 255);
    data[i + 2] = 0;
    data[i + 3] = 255;
  }

  context.putImageData(new ImageData(data, w, h), 0, 0);
  displacementMapUrl.value = canvas.toDataURL('image/png');
  displacementScale.value = safeScale / CANVAS_DPI;
};

const panelStyle = computed(() => ({
  width: `${PANEL_WIDTH}px`,
  height: `${PANEL_HEIGHT}px`,
  left: `${panelPosition.value.left}px`,
  top: `${panelPosition.value.top}px`
}));

const imageOffset = computed(() => {
  if (!snapshot.value) {
    return { x: 0, y: 0 };
  }
  const scrollX = snapshot.value.scrollX || 0;
  const scrollY = snapshot.value.scrollY || 0;
  return {
    x: -(panelPosition.value.left + scrollX),
    y: -(panelPosition.value.top + scrollY)
  };
});

const clampPosition = (left: number, top: number) => {
  if (typeof window === 'undefined') {
    return { left, top };
  }
  const maxLeft = Math.max(EDGE_GAP, window.innerWidth - PANEL_WIDTH - EDGE_GAP);
  const maxTop = Math.max(EDGE_GAP, window.innerHeight - PANEL_HEIGHT - EDGE_GAP);
  return {
    left: Math.min(Math.max(EDGE_GAP, left), maxLeft),
    top: Math.min(Math.max(EDGE_GAP, top), maxTop)
  };
};

const applyPanelPosition = (left: number, top: number, persist = false) => {
  const next = clampPosition(left, top);
  panelPosition.value = next;
  if (persist) {
    personalization.updateLiquidGlassPosition(next);
  }
};

const initPosition = () => {
  if (typeof window === 'undefined') {
    return;
  }
  const saved = personalization.experiments.liquidGlassPosition;
  if (saved) {
    applyPanelPosition(saved.left, saved.top, false);
    return;
  }
  const centeredLeft = Math.round(window.innerWidth / 2 - PANEL_WIDTH / 2);
  const centeredTop = Math.round(window.innerHeight / 2 - PANEL_HEIGHT / 2);
  applyPanelPosition(centeredLeft, centeredTop, true);
};

const captureSnapshot = async () => {
  if (capturing.value || typeof window === 'undefined') {
    return;
  }
  capturing.value = true;
  const stage = stageRef.value;
  const originalVisibility = stage?.style.visibility;
  if (stage) {
    stage.style.visibility = 'hidden';
  }
  try {
    const canvas = await html2canvas(document.body, {
      logging: false,
      backgroundColor: null,
      useCORS: true,
      scale: SNAPSHOT_SCALE
    });
    const logicalWidth = canvas.width / SNAPSHOT_SCALE;
    const logicalHeight = canvas.height / SNAPSHOT_SCALE;
    snapshot.value = {
      url: canvas.toDataURL('image/png'),
      width: logicalWidth,
      height: logicalHeight,
      scale: SNAPSHOT_SCALE,
      scrollX: window.scrollX,
      scrollY: window.scrollY
    };
  } catch (error) {
    console.error('捕获背景失败：', error);
  } finally {
    if (stage) {
      stage.style.visibility = originalVisibility || '';
    }
    capturing.value = false;
  }
};

const handlePointerDown = (event: PointerEvent) => {
  if (event.pointerType === 'mouse' && event.button !== 0) {
    return;
  }
  if (!panelRef.value) {
    return;
  }
  dragging.value = true;
  pointerId.value = event.pointerId;
  panelRef.value.setPointerCapture?.(event.pointerId);
  const rect = panelRef.value.getBoundingClientRect();
  dragOffset.value = { x: event.clientX - rect.left, y: event.clientY - rect.top };
  panelRef.value.style.cursor = 'grabbing';
};

const handlePointerMove = (event: PointerEvent) => {
  if (!dragging.value || pointerId.value !== event.pointerId) {
    return;
  }
  const left = event.clientX - dragOffset.value.x;
  const top = event.clientY - dragOffset.value.y;
  applyPanelPosition(left, top, false);
};

const handlePointerUpGlobal = (event: PointerEvent) => {
  if (!dragging.value || pointerId.value !== event.pointerId) {
    return;
  }
  dragging.value = false;
  pointerId.value = null;
  panelRef.value?.releasePointerCapture?.(event.pointerId);
  panelRef.value?.style.setProperty('cursor', 'grab');
  applyPanelPosition(panelPosition.value.left, panelPosition.value.top, true);
};

const handleResize = () => {
  applyPanelPosition(panelPosition.value.left, panelPosition.value.top, true);
};

const attachWindowListeners = () => {
  window.addEventListener('pointermove', handlePointerMove);
  window.addEventListener('pointerup', handlePointerUpGlobal);
  window.addEventListener('pointercancel', handlePointerUpGlobal);
  window.addEventListener('resize', handleResize);
};

const detachWindowListeners = () => {
  window.removeEventListener('pointermove', handlePointerMove);
  window.removeEventListener('pointerup', handlePointerUpGlobal);
  window.removeEventListener('pointercancel', handlePointerUpGlobal);
  window.removeEventListener('resize', handleResize);
};

watch(
  enabled,
  async (active) => {
    if (!active) {
      dragging.value = false;
      snapshot.value = snapshot.value; // keep last snapshot
      if (pointerId.value !== null) {
        panelRef.value?.releasePointerCapture?.(pointerId.value);
        panelRef.value?.style.setProperty('cursor', 'grab');
        pointerId.value = null;
      }
      return;
    }
    await nextTick();
    ensureDisplacementReady();
    initPosition();
    captureSnapshot();
  },
  { immediate: false }
);

onMounted(() => {
  if (enabled.value) {
    ensureDisplacementReady();
    initPosition();
    captureSnapshot();
  }
  attachWindowListeners();
});

onBeforeUnmount(() => {
  detachWindowListeners();
});
</script>
