<template>
  <!--
    AI 加载动画：处方=药丸；病历=病历本
    type + text 由调用方传入；嵌入抽屉时默认不显示主题切换
  -->
  <div
    ref="containerRef"
    class="loading-wrapper"
    :class="{ 'loading-wrapper--dark': isDark }"
    :style="{ width: '100%', height: '100%' }"
  >
    <canvas ref="canvasRef" class="loading-canvas"></canvas>
    <button
      v-if="showToggle"
      class="theme-toggle-btn"
      :title="isDark ? '切换到亮色模式' : '切换到暗色模式'"
      @click="toggleTheme"
    >
      <span class="theme-icon">
        <svg
          v-if="!isDark"
          class="sun"
          width="20"
          height="20"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
        >
          <circle cx="12" cy="12" r="5" />
          <line x1="12" y1="1" x2="12" y2="3" />
          <line x1="12" y1="21" x2="12" y2="23" />
          <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
          <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
          <line x1="1" y1="12" x2="3" y2="12" />
          <line x1="21" y1="12" x2="23" y2="12" />
          <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
        </svg>
        <svg
          v-else
          class="moon"
          width="20"
          height="20"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
        >
          <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
        </svg>
      </span>
<!--      <span class="theme-label">{{ isDark ? '亮色' : '暗色' }}</span>-->
    </button>
  </div>
</template>

<script setup lang="ts">
/**
 * AI 生成加载动画
 * - type=prescription：中心画胶囊药丸
 * - type=medical_record：中心画病历本
 * - text：底部文案（可动态传）
 */
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';

export type AiLoadingType = 'prescription' | 'medical_record';

const props = withDefaults(
  defineProps<{
    /** 动画类型：处方 / 病历 */
    type?: AiLoadingType;
    /** 底部文案 */
    text?: string;
    /** 是否显示主题切换（嵌入业务抽屉建议关闭） */
    showToggle?: boolean;
  }>(),
  {
    type: 'prescription',
    text: '',
    showToggle: false,
  },
);

/** 未传文案时按 type 给默认值 */
const displayText = computed(() => {
  const t = String(props.text || '').trim();
  if (t) return t;
  return props.type === 'medical_record'
    ? 'AI正在生成病历'
    : 'AI正在生成处方';
});

const containerRef = ref<HTMLDivElement | null>(null);
const canvasRef = ref<HTMLCanvasElement | null>(null);
const isDark = ref(false);
/** 画布循环里读取最新文案/类型，避免闭包过期 */
const liveText = ref(displayText.value);
const liveType = ref<AiLoadingType>(props.type);

watch(
  displayText,
  (v) => {
    liveText.value = v;
  },
  { immediate: true },
);
watch(
  () => props.type,
  (v) => {
    liveType.value = v || 'prescription';
  },
  { immediate: true },
);

function detectAppDark(): boolean {
  return (
    document.documentElement.classList.contains('dark') ||
    document.documentElement.getAttribute('data-theme') === 'dark' ||
    document.documentElement.getAttribute('class')?.includes('dark') === true
  );
}

function syncThemeFromApp() {
  if (localStorage.getItem('prescription-loading-theme')) {
    isDark.value = localStorage.getItem('prescription-loading-theme') === 'dark';
    return;
  }
  isDark.value = detectAppDark();
}

function toggleTheme() {
  isDark.value = !isDark.value;
  localStorage.setItem(
    'prescription-loading-theme',
    isDark.value ? 'dark' : 'light',
  );
  resetParticles?.();
}

interface Particle {
  x: number;
  y: number;
  vx: number;
  vy: number;
  life: number;
  maxLife: number;
  size: number;
  color: { r: number; g: number; b: number };
  type: 'circle' | 'cross';
}

interface ThemeColors {
  textColor: string;
  ringColor: string;
  glowInner: string;
  glowMiddle: string;
  glowOuter: string;
  particleBright: number;
  iconShadow: string;
}

let animationId = 0;
let resetParticles: (() => void) | null = null;
let themeObserver: MutationObserver | null = null;
let resizeObserver: ResizeObserver | null = null;
let removeWindowResize: (() => void) | null = null;

onMounted(async () => {
  await nextTick();
  syncThemeFromApp();
  if (!containerRef.value || !canvasRef.value) return;

  const canvas = canvasRef.value;
  const ctx = canvas.getContext('2d');
  if (!ctx) return;
  const container = containerRef.value;

  const dpr = Math.min(window.devicePixelRatio || 1, 2);
  let cssWidth = 0;
  let cssHeight = 0;
  let prevW = 0;
  let prevH = 0;
  let startTime: number | null = null;
  let lastTime: number | null = null;
  let particles: Particle[] = [];
  const MAX_PARTICLES = 28;

  resetParticles = () => {
    particles = [];
  };

  function updateSize() {
    const rect = container.getBoundingClientRect();
    cssWidth = rect.width || 300;
    cssHeight = rect.height || 280;
    const pw = Math.round(cssWidth * dpr);
    const ph = Math.round(cssHeight * dpr);
    if (canvas.width !== pw || canvas.height !== ph) {
      canvas.width = pw;
      canvas.height = ph;
      if (
        prevW &&
        (Math.abs(cssWidth - prevW) > 2 || Math.abs(cssHeight - prevH) > 2)
      ) {
        particles = [];
      }
      prevW = cssWidth;
      prevH = cssHeight;
    }
  }

  function createParticle(cx: number, cy: number, s: number): Particle {
    const angle = Math.random() * Math.PI * 2;
    const distance = s * (0.15 + Math.random() * 0.45);
    const speed = s * (0.03 + Math.random() * 0.08);
    const colors = [
      { r: 61, g: 214, b: 208 },
      { r: 74, g: 195, b: 190 },
      { r: 93, g: 173, b: 226 },
      { r: 46, g: 180, b: 170 },
      { r: 100, g: 200, b: 215 },
    ];
    const color = colors[Math.floor(Math.random() * colors.length)]!;
    return {
      x: cx + Math.cos(angle) * distance,
      y: cy + Math.sin(angle) * distance,
      vx: Math.cos(angle) * speed,
      vy: Math.sin(angle) * speed,
      life: 1,
      maxLife: 1.8 + Math.random() * 2.5,
      size: 1.2 + Math.random() * 2.8,
      color,
      type: Math.random() < 0.25 ? 'cross' : 'circle',
    };
  }

  function updateParticles(dt: number, cx: number, cy: number, s: number) {
    for (let i = particles.length - 1; i >= 0; i--) {
      const p = particles[i]!;
      p.life -= dt / p.maxLife;
      p.x += p.vx * dt;
      p.y += p.vy * dt;
      p.vx *= 0.997;
      p.vy *= 0.997;
      if (p.life <= 0) particles.splice(i, 1);
    }
    while (particles.length < MAX_PARTICLES) {
      const p = createParticle(cx, cy, s);
      const ang = Math.random() * Math.PI * 2;
      p.x = cx + Math.cos(ang) * s * 0.12 * Math.random();
      p.y = cy + Math.sin(ang) * s * 0.12 * Math.random();
      particles.push(p);
    }
  }

  function drawParticle(
    c: CanvasRenderingContext2D,
    p: Particle,
    bright: number,
  ) {
    const alpha = p.life;
    const { r, g, b } = p.color;
    const size = p.size * (0.7 + 0.3 * p.life);
    const br = Math.min(255, Math.round(r * bright));
    const bg = Math.min(255, Math.round(g * bright));
    const bb = Math.min(255, Math.round(b * bright));
    c.save();
    c.globalAlpha = alpha;
    if (p.type === 'cross') {
      const len = size * 1.8;
      const w = size * 0.55;
      c.strokeStyle = `rgba(${br},${bg},${bb},${alpha})`;
      c.lineWidth = w;
      c.lineCap = 'round';
      c.beginPath();
      c.moveTo(p.x - len, p.y);
      c.lineTo(p.x + len, p.y);
      c.stroke();
      c.beginPath();
      c.moveTo(p.x, p.y - len);
      c.lineTo(p.x, p.y + len);
      c.stroke();
      c.fillStyle = `rgba(255,255,255,${alpha * 0.9})`;
      c.beginPath();
      c.arc(p.x, p.y, size * 0.35, 0, Math.PI * 2);
      c.fill();
    } else {
      c.shadowColor = `rgba(${br},${bg},${bb},${alpha * 0.7})`;
      c.shadowBlur = size * 2.5;
      c.fillStyle = `rgba(${br},${bg},${bb},${alpha})`;
      c.beginPath();
      c.arc(p.x, p.y, size, 0, Math.PI * 2);
      c.fill();
      c.shadowBlur = 0;
      c.fillStyle = `rgba(255,255,255,${alpha * 0.6})`;
      c.beginPath();
      c.arc(p.x, p.y, size * 0.4, 0, Math.PI * 2);
      c.fill();
    }
    c.restore();
  }

  /** 处方药丸（处方） */
  function drawPill(
    c: CanvasRenderingContext2D,
    px: number,
    py: number,
    pw: number,
    ph: number,
    floatOff: number,
    pulse: number,
    shadowCol: string,
  ) {
    c.save();
    const cx = px + pw / 2;
    const cy = py + ph / 2;
    const hw = (pw / 2) * pulse;
    const hh = (ph / 2) * pulse;
    const adjY = cy + floatOff;
    const dx = cx - hw;
    const dy = adjY - hh;
    const dw = hw * 2;
    const dh = hh * 2;
    const rad = hh;
    const path = new Path2D();
    path.roundRect(dx, dy, dw, dh, rad);

    c.shadowColor = shadowCol;
    c.shadowBlur = hh * 0.7;
    c.shadowOffsetY = hh * 0.25;
    c.fillStyle = '#ffffff';
    c.fill(path);
    c.shadowColor = 'transparent';
    c.shadowBlur = 0;
    c.shadowOffsetY = 0;

    c.save();
    c.clip(path);
    const leftGrad = c.createLinearGradient(dx, 0, dx + dw * 0.5, 0);
    leftGrad.addColorStop(0, '#FDFBF7');
    leftGrad.addColorStop(0.7, '#F5F1EA');
    leftGrad.addColorStop(1, '#EDE8DF');
    c.fillStyle = leftGrad;
    c.fillRect(dx, dy, dw * 0.5, dh);

    const rightGrad = c.createLinearGradient(dx + dw * 0.5, 0, dx + dw, 0);
    rightGrad.addColorStop(0, '#5DD9D1');
    rightGrad.addColorStop(0.5, '#3DD6D0');
    rightGrad.addColorStop(1, '#2CB8B0');
    c.fillStyle = rightGrad;
    c.fillRect(dx + dw * 0.5, dy, dw * 0.5, dh);

    c.strokeStyle = 'rgba(180,180,180,0.5)';
    c.lineWidth = Math.max(0.6, hh * 0.06);
    c.beginPath();
    c.moveTo(dx + dw * 0.5, dy + 1);
    c.lineTo(dx + dw * 0.5, dy + dh - 1);
    c.stroke();

    const hy = dy + dh * 0.15;
    const hh2 = dh * 0.38;
    const hp = new Path2D();
    hp.roundRect(dx + dw * 0.06, hy, dw * 0.88, hh2, hh2 / 2);
    c.fillStyle = 'rgba(255,255,255,0.45)';
    c.fill(hp);
    c.restore();

    c.strokeStyle = 'rgba(200,200,200,0.5)';
    c.lineWidth = Math.max(0.8, hh * 0.08);
    c.stroke(path);
    c.restore();
  }

  /** 病历本（病历） */
  function drawMedicalRecord(
    c: CanvasRenderingContext2D,
    cx: number,
    cy: number,
    scale: number,
    floatOff: number,
    pulse: number,
    shadowCol: string,
  ) {
    c.save();
    const w = scale * 0.28 * pulse;
    const h = scale * 0.34 * pulse;
    const x = cx - w / 2;
    const y = cy - h / 2 + floatOff;
    const r = Math.max(4, scale * 0.018);
    const path = new Path2D();
    path.roundRect(x, y, w, h, r);

    c.shadowColor = shadowCol;
    c.shadowBlur = scale * 0.035;
    c.shadowOffsetY = scale * 0.012;
    c.fillStyle = '#ffffff';
    c.fill(path);
    c.shadowColor = 'transparent';
    c.shadowBlur = 0;
    c.shadowOffsetY = 0;

    // 左侧装订条
    c.fillStyle = '#3DD6D0';
    c.fillRect(x, y + r * 0.2, w * 0.12, h - r * 0.4);

    // 横线（像病历行）
    c.strokeStyle = 'rgba(61,214,208,0.35)';
    c.lineWidth = Math.max(1, scale * 0.004);
    for (let i = 0; i < 4; i++) {
      const ly = y + h * (0.32 + i * 0.14);
      c.beginPath();
      c.moveTo(x + w * 0.22, ly);
      c.lineTo(x + w * 0.88, ly);
      c.stroke();
    }
    // 顶部标题条
    c.fillStyle = 'rgba(61,214,208,0.2)';
    c.beginPath();
    c.roundRect(x + w * 0.22, y + h * 0.12, w * 0.5, h * 0.08, 2);
    c.fill();

    c.strokeStyle = 'rgba(180,200,200,0.55)';
    c.lineWidth = Math.max(0.8, scale * 0.005);
    c.stroke(path);
    c.restore();
  }

  function drawCross(
    c: CanvasRenderingContext2D,
    x: number,
    y: number,
    size: number,
    alpha: number,
    rot: number,
  ) {
    c.save();
    c.translate(x, y);
    c.rotate(rot);
    c.globalAlpha = alpha;
    const len = size;
    const w = size * 0.3;
    c.strokeStyle = '#3DD6D0';
    c.lineWidth = w;
    c.lineCap = 'round';
    c.beginPath();
    c.moveTo(-len, 0);
    c.lineTo(len, 0);
    c.stroke();
    c.beginPath();
    c.moveTo(0, -len);
    c.lineTo(0, len);
    c.stroke();
    c.fillStyle = '#ffffff';
    c.beginPath();
    c.arc(0, 0, size * 0.25, 0, Math.PI * 2);
    c.fill();
    c.restore();
  }

  function getTheme(): ThemeColors {
    const dark = isDark.value;
    return {
      textColor: dark ? '#d1d5db' : '#4a5568',
      ringColor: dark ? 'rgba(255,255,255,0.25)' : 'rgba(180,195,200,0.45)',
      glowInner: dark ? 'rgba(61,214,208,0.15)' : 'rgba(61,214,208,0.13)',
      glowMiddle: dark ? 'rgba(61,214,208,0.05)' : 'rgba(61,214,208,0.04)',
      glowOuter: 'rgba(61,214,208,0)',
      particleBright: dark ? 1.4 : 1.0,
      iconShadow: dark ? 'rgba(0,0,0,0.4)' : 'rgba(0,0,0,0.12)',
    };
  }

  function draw(timestamp: number) {
    updateSize();
    if (cssWidth <= 0 || cssHeight <= 0) {
      animationId = requestAnimationFrame(draw);
      return;
    }
    if (startTime === null) {
      startTime = timestamp;
      lastTime = timestamp;
    }
    const elapsed = (timestamp - startTime) / 1000;
    let dt = (timestamp - (lastTime ?? timestamp)) / 1000;
    if (dt <= 0) dt = 0.016;
    if (dt > 0.2) dt = 0.2;
    lastTime = timestamp;

    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.save();
    ctx.scale(dpr, dpr);

    const cx = cssWidth / 2;
    const cy = cssHeight / 2 - cssHeight * 0.04;
    const scale = Math.min(cssWidth, cssHeight);
    const orbitR = scale * 0.26;
    const fontSize = Math.max(12, scale * 0.055);
    const textY = cy + orbitR + fontSize * 2.4;
    const text = liveText.value;
    const animType = liveType.value;

    const dashOff = elapsed * scale * 0.025;
    const arcRot = elapsed * 3.5;
    const arcLen = (100 * Math.PI) / 180;
    const floatOff = Math.sin(elapsed * 2.5) * scale * 0.025;
    const pulseS = 1 + Math.sin(elapsed * 3.7) * 0.04;
    const textAlpha = 0.55 + 0.45 * Math.sin(elapsed * 1.6);
    const theme = getTheme();

    const grad = ctx.createRadialGradient(
      cx,
      cy,
      orbitR * 0.15,
      cx,
      cy,
      orbitR * 1.6,
    );
    grad.addColorStop(0, theme.glowInner);
    grad.addColorStop(0.5, theme.glowMiddle);
    grad.addColorStop(1, theme.glowOuter);
    ctx.fillStyle = grad;
    ctx.fillRect(0, 0, cssWidth, cssHeight);

    ctx.save();
    ctx.setLineDash([scale * 0.018, scale * 0.04]);
    ctx.lineDashOffset = -dashOff;
    ctx.strokeStyle = theme.ringColor;
    ctx.lineWidth = Math.max(1, scale * 0.006);
    ctx.beginPath();
    ctx.arc(cx, cy, orbitR, 0, Math.PI * 2);
    ctx.stroke();
    ctx.setLineDash([]);
    ctx.restore();

    const arcS = arcRot;
    const arcE = arcRot + arcLen;
    const fx = cx + orbitR * Math.cos(arcE);
    const fy = cy + orbitR * Math.sin(arcE);
    ctx.save();
    ctx.shadowColor = 'rgba(61,214,208,0.8)';
    ctx.shadowBlur = scale * 0.04;
    ctx.strokeStyle = '#3DD6D0';
    ctx.lineWidth = Math.max(2, scale * 0.014);
    ctx.lineCap = 'round';
    ctx.beginPath();
    ctx.arc(cx, cy, orbitR, arcS, arcE);
    ctx.stroke();
    ctx.restore();

    ctx.save();
    ctx.shadowColor = 'rgba(255,255,255,0.9)';
    ctx.shadowBlur = scale * 0.03;
    ctx.fillStyle = '#ffffff';
    ctx.beginPath();
    ctx.arc(fx, fy, Math.max(2.5, scale * 0.014), 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();

    // 中心图标：按 type 切换
    if (animType === 'medical_record') {
      drawMedicalRecord(ctx, cx, cy, scale, floatOff, pulseS, theme.iconShadow);
    } else {
      const pillW = scale * 0.34;
      const pillH = scale * 0.1;
      drawPill(
        ctx,
        cx - pillW / 2,
        cy - pillH / 2,
        pillW,
        pillH,
        floatOff,
        pulseS,
        theme.iconShadow,
      );
    }

    for (let i = 0; i < 3; i++) {
      const ang = elapsed * 1.8 + (i * Math.PI * 2) / 3;
      const crossOrbitR = orbitR * 1.02;
      const rx = cx + crossOrbitR * Math.cos(ang);
      const ry = cy + crossOrbitR * Math.sin(ang);
      const rs = Math.max(4, scale * 0.022);
      const ra = 0.4 + 0.35 * Math.sin(elapsed * 3.2 + i * 1.7);
      drawCross(ctx, rx, ry, rs, ra, elapsed * 0.9 + i);
    }

    updateParticles(dt, cx, cy, scale);
    for (const p of particles) {
      drawParticle(ctx, p, theme.particleBright);
    }

    ctx.save();
    ctx.globalAlpha = textAlpha;
    ctx.fillStyle = theme.textColor;
    ctx.font = `${fontSize}px "PingFang SC","Microsoft YaHei","Noto Sans SC","Helvetica Neue",sans-serif`;
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.shadowColor = 'rgba(61,214,208,0.25)';
    ctx.shadowBlur = fontSize * 0.6;
    ctx.fillText(text, cx, textY);
    ctx.restore();

    const charW = fontSize * 0.55;
    const dotsStartX = cx + (text.length / 2) * charW + fontSize * 0.5;
    const dotR = Math.max(2, fontSize * 0.16);
    const dotSpacing = fontSize * 0.7;
    for (let i = 0; i < 3; i++) {
      const phase = (elapsed * 2.8 + i * 1.0) % 3;
      let brightness = phase < 1 ? phase : phase < 2 ? 2 - phase : 0;
      brightness = brightness * brightness * (3 - 2 * brightness);
      const alpha = 0.2 + brightness * 0.75;
      const dx = dotsStartX + i * dotSpacing * 2.2;
      ctx.save();
      ctx.shadowColor = `rgba(61,214,208,${alpha * 0.8})`;
      ctx.shadowBlur = dotR * 3;
      ctx.fillStyle = `rgba(61,214,208,${alpha})`;
      ctx.beginPath();
      ctx.arc(dx, textY, dotR, 0, Math.PI * 2);
      ctx.fill();
      ctx.restore();
    }

    ctx.restore();
    animationId = requestAnimationFrame(draw);
  }

  updateSize();
  animationId = requestAnimationFrame(draw);

  if (window.ResizeObserver) {
    resizeObserver = new ResizeObserver(() => updateSize());
    resizeObserver.observe(container);
  } else {
    const onResize = () => updateSize();
    window.addEventListener('resize', onResize);
    removeWindowResize = () => window.removeEventListener('resize', onResize);
  }

  // 跟随应用 dark class（未手动锁定主题时）
  themeObserver = new MutationObserver(() => {
    if (!localStorage.getItem('prescription-loading-theme')) {
      syncThemeFromApp();
    }
  });
  themeObserver.observe(document.documentElement, {
    attributes: true,
    attributeFilter: ['class', 'data-theme'],
  });
});

onUnmounted(() => {
  cancelAnimationFrame(animationId);
  resizeObserver?.disconnect();
  resizeObserver = null;
  removeWindowResize?.();
  removeWindowResize = null;
  themeObserver?.disconnect();
  themeObserver = null;
  resetParticles = null;
});
</script>

<style scoped>
.loading-wrapper {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 220px;
  /* background: #ffffff; */
  border-radius: 12px;
  overflow: hidden;
}
.loading-wrapper--dark {
  /* background: #1e2227; */
}
.loading-canvas {
  display: block;
  width: 100%;
  height: 100%;
}
.theme-toggle-btn {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 14px;
  background: rgba(255, 255, 255, 0.85);
  border: 1px solid rgba(128, 128, 128, 0.3);
  border-radius: 20px;
  cursor: pointer;
  font-size: 14px;
  color: #4a5568;
  backdrop-filter: blur(8px);
  transition: all 0.25s;
}
.loading-wrapper--dark .theme-toggle-btn {
  background: rgba(30, 34, 39, 0.9);
  color: #d1d5db;
}
.theme-toggle-btn:hover {
  border-color: #3dd6d0;
  transform: scale(1.03);
}
.theme-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
}
</style>
