'use strict';

var React3 = require('react');
var THREE2 = require('three');

function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }

function _interopNamespace(e) {
  if (e && e.__esModule) return e;
  var n = Object.create(null);
  if (e) {
    Object.keys(e).forEach(function (k) {
      if (k !== 'default') {
        var d = Object.getOwnPropertyDescriptor(e, k);
        Object.defineProperty(n, k, d.get ? d : {
          enumerable: true,
          get: function () { return e[k]; }
        });
      }
    });
  }
  n.default = e;
  return Object.freeze(n);
}

var React3__default = /*#__PURE__*/_interopDefault(React3);
var THREE2__namespace = /*#__PURE__*/_interopNamespace(THREE2);

// src/mikuFireworks3D/components/MikuFireworks3D.tsx
function DanmakuPanel({ onSend }) {
  const [text, setText] = React3.useState("");
  const emit = () => {
    const value = text.trim();
    if (!value) {
      return;
    }
    onSend(value);
    setText("");
  };
  return /* @__PURE__ */ React3__default.default.createElement("div", { className: "rounded-xl border border-slate-600/40 bg-slate-900/70 p-3 text-slate-100 backdrop-blur-sm" }, /* @__PURE__ */ React3__default.default.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React3__default.default.createElement(
    "input",
    {
      type: "text",
      value: text,
      onChange: (event) => setText(event.target.value),
      onKeyDown: (event) => {
        if (event.key === "Enter") {
          emit();
        }
      },
      placeholder: "\u53D1\u9001\u5F39\u5E55\uFF08\u652F\u6301 /miku /avatar /normal\uFF09",
      className: "flex-1 rounded-md border border-slate-600 bg-slate-950 px-2.5 py-2 text-sm text-slate-100 outline-none focus:border-cyan-400"
    }
  ), /* @__PURE__ */ React3__default.default.createElement(
    "button",
    {
      type: "button",
      onClick: emit,
      className: "rounded-md bg-cyan-400 px-3 py-2 text-sm font-medium text-slate-950 hover:bg-cyan-300"
    },
    "\u53D1\u9001"
  )));
}
function FireworksCanvas({ canvasRef }) {
  return /* @__PURE__ */ React3__default.default.createElement(
    "canvas",
    {
      ref: canvasRef,
      className: "absolute inset-0 h-full w-full",
      style: {
        background: "radial-gradient(circle at 20% 20%, rgba(57,197,187,0.15) 0%, rgba(6,8,22,1) 45%, rgba(4,6,15,1) 100%)"
      }
    }
  );
}

// src/mikuFireworks3D/constants.ts
var DEFAULT_MAX_PARTICLES = 5e3;
var DEFAULT_MAX_ACTIVE_FIREWORKS = 12;
var FIREWORK_KIND_LABELS = {
  normal: "\u666E\u901A\u70DF\u82B1",
  miku: "MIKU \u4E3B\u9898",
  avatar: "\u5934\u50CF\u70DF\u82B1"
};
var MIKU_PALETTE = ["#39c5bb", "#66e3db", "#7ad8ff", "#b0fff8", "#8cf7e0"];
var NORMAL_PALETTE = ["#ffe066", "#ff6b6b", "#4dabf7", "#c77dff", "#69db7c"];
var DANMAKU_MAX_LENGTH = 32;
var DANMAKU_TRACK_COUNT = 8;

// src/mikuFireworks3D/components/FireworksControlPanel.tsx
function FireworksControlPanel({
  selectedKind,
  onKindChange,
  autoLaunchOnDanmaku,
  onAutoLaunchChange,
  avatarUrl,
  onAvatarUrlChange,
  onLaunch,
  fps,
  realtimeConnected,
  onlineCount
}) {
  return /* @__PURE__ */ React3__default.default.createElement("div", { className: "rounded-xl border border-slate-600/40 bg-slate-900/70 p-3 text-slate-100 backdrop-blur-sm" }, /* @__PURE__ */ React3__default.default.createElement("div", { className: "mb-3 flex flex-wrap items-center gap-2" }, Object.keys(FIREWORK_KIND_LABELS).map((kind) => {
    const active = kind === selectedKind;
    return /* @__PURE__ */ React3__default.default.createElement(
      "button",
      {
        key: kind,
        type: "button",
        onClick: () => onKindChange(kind),
        className: `rounded-md px-3 py-1.5 text-sm transition ${active ? "bg-cyan-500 text-slate-950" : "bg-slate-700/70 hover:bg-slate-600/80"}`
      },
      FIREWORK_KIND_LABELS[kind]
    );
  }), /* @__PURE__ */ React3__default.default.createElement(
    "button",
    {
      type: "button",
      onClick: onLaunch,
      className: "ml-auto rounded-md bg-emerald-400 px-3 py-1.5 text-sm font-medium text-slate-900 hover:bg-emerald-300"
    },
    "\u53D1\u5C04\u70DF\u82B1"
  )), /* @__PURE__ */ React3__default.default.createElement("div", { className: "grid gap-2 md:grid-cols-2" }, /* @__PURE__ */ React3__default.default.createElement("label", { className: "flex items-center gap-2 text-sm" }, /* @__PURE__ */ React3__default.default.createElement(
    "input",
    {
      type: "checkbox",
      checked: autoLaunchOnDanmaku,
      onChange: (event) => onAutoLaunchChange(event.target.checked)
    }
  ), "\u53D1\u9001\u5F39\u5E55\u540E\u81EA\u52A8\u653E\u70DF\u82B1"), /* @__PURE__ */ React3__default.default.createElement("div", { className: "text-sm text-slate-300" }, "FPS: ", fps), typeof realtimeConnected === "boolean" ? /* @__PURE__ */ React3__default.default.createElement("div", { className: "text-sm text-slate-300" }, "\u5B9E\u65F6\u72B6\u6001: ", realtimeConnected ? "\u5DF2\u8FDE\u63A5" : "\u672A\u8FDE\u63A5", typeof onlineCount === "number" ? ` \xB7 \u5728\u7EBF ${onlineCount}` : "") : null), selectedKind === "avatar" ? /* @__PURE__ */ React3__default.default.createElement("div", { className: "mt-2" }, /* @__PURE__ */ React3__default.default.createElement(
    "input",
    {
      type: "url",
      value: avatarUrl,
      onChange: (event) => onAvatarUrlChange(event.target.value),
      placeholder: "\u5934\u50CF\u56FE\u7247 URL\uFF08\u7528\u4E8E\u5934\u50CF\u70DF\u82B1\uFF09",
      className: "w-full rounded-md border border-slate-600 bg-slate-950 px-2.5 py-2 text-sm text-slate-100 outline-none focus:border-cyan-400"
    }
  )) : null);
}
function useDanmakuController(options) {
  const [items, setItems] = React3.useState([]);
  const cursorRef = React3.useRef(0);
  const removeItem = React3.useCallback((id) => {
    setItems((prev) => prev.filter((item) => item.id !== id));
  }, []);
  const addIncoming = React3.useCallback((message) => {
    setItems((prev) => {
      const track = cursorRef.current % DANMAKU_TRACK_COUNT;
      const item = {
        ...message,
        track,
        durationMs: 8e3 + Math.floor(Math.random() * 2800)
      };
      return [...prev.slice(-40), item];
    });
    cursorRef.current += 1;
  }, []);
  const send = React3.useCallback(
    (text, color, sendOptions) => {
      const trimmed = text.trim();
      if (!trimmed) {
        return null;
      }
      const { content, launchKind } = parseCommand(trimmed);
      const safeText = content.slice(0, DANMAKU_MAX_LENGTH);
      if (!safeText) {
        return null;
      }
      const message = {
        id: `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
        text: safeText,
        color,
        timestamp: Date.now()
      };
      const optimistic = sendOptions?.optimistic ?? true;
      if (optimistic) {
        addIncoming(message);
      }
      options?.onSend?.(message);
      return {
        message,
        launchKind
      };
    },
    [addIncoming, options]
  );
  return React3.useMemo(
    () => ({
      items,
      send,
      addIncoming,
      removeItem
    }),
    [addIncoming, items, removeItem, send]
  );
}
function parseCommand(text) {
  if (text.startsWith("/miku ")) {
    return { launchKind: "miku", content: text.replace("/miku ", "").trim() };
  }
  if (text === "/miku") {
    return { launchKind: "miku", content: "MIKU!" };
  }
  if (text.startsWith("/avatar ")) {
    return { launchKind: "avatar", content: text.replace("/avatar ", "").trim() };
  }
  if (text === "/avatar") {
    return { launchKind: "avatar", content: "Avatar Firework!" };
  }
  if (text.startsWith("/normal ")) {
    return { launchKind: "normal", content: text.replace("/normal ", "").trim() };
  }
  if (text === "/normal") {
    return { launchKind: "normal", content: "Fireworks!" };
  }
  return { content: text };
}
function createCircularSpriteTexture() {
  const size = 64;
  const canvas = document.createElement("canvas");
  canvas.width = size;
  canvas.height = size;
  const ctx = canvas.getContext("2d");
  if (!ctx) {
    return new THREE2__namespace.CanvasTexture(canvas);
  }
  const center = size / 2;
  const gradient = ctx.createRadialGradient(center, center, 2, center, center, center);
  gradient.addColorStop(0, "rgba(255,255,255,1)");
  gradient.addColorStop(0.4, "rgba(255,255,255,0.9)");
  gradient.addColorStop(1, "rgba(255,255,255,0)");
  ctx.fillStyle = gradient;
  ctx.fillRect(0, 0, size, size);
  const texture = new THREE2__namespace.CanvasTexture(canvas);
  texture.needsUpdate = true;
  return texture;
}

// src/mikuFireworks3D/utils/colorPalettes.ts
function pickPalette(kind) {
  if (kind === "miku") {
    return MIKU_PALETTE;
  }
  return NORMAL_PALETTE;
}
function pickRandomColor(colors) {
  if (colors.length === 0) {
    return "#ffffff";
  }
  const index = Math.floor(Math.random() * colors.length);
  return colors[index] || "#ffffff";
}

// src/mikuFireworks3D/utils/avatarSprite.ts
async function sampleAvatarPoints(avatarUrl, sampleStep = 4, maxPoints = 500) {
  const image = await loadImage(avatarUrl);
  const size = 64;
  const canvas = document.createElement("canvas");
  canvas.width = size;
  canvas.height = size;
  const ctx = canvas.getContext("2d");
  if (!ctx) {
    return [];
  }
  ctx.clearRect(0, 0, size, size);
  ctx.drawImage(image, 0, 0, size, size);
  const { data } = ctx.getImageData(0, 0, size, size);
  const points = [];
  for (let y = 0; y < size; y += sampleStep) {
    for (let x = 0; x < size; x += sampleStep) {
      const index = (y * size + x) * 4;
      const alpha = data[index + 3] ?? 0;
      if (alpha < 24) {
        continue;
      }
      const r = data[index] ?? 0;
      const g = data[index + 1] ?? 0;
      const b = data[index + 2] ?? 0;
      const brightness = (r + g + b) / (3 * 255);
      points.push({ x: x - size / 2, y: size / 2 - y, brightness });
      if (points.length >= maxPoints) {
        return points;
      }
    }
  }
  return points;
}
function loadImage(url) {
  return new Promise((resolve, reject) => {
    const image = new window.Image();
    image.crossOrigin = "anonymous";
    image.onload = () => resolve(image);
    image.onerror = () => reject(new Error("Failed to load avatar image."));
    image.src = url;
  });
}

// src/mikuFireworks3D/engine/patterns/avatar.ts
async function createAvatarSeeds(avatarUrl, fallbackColor) {
  const points = await sampleAvatarPoints(avatarUrl);
  if (points.length === 0) {
    return [];
  }
  return points.map((point) => {
    const spread = 0.22;
    return {
      x: 0,
      y: 0,
      z: 0,
      vx: point.x * spread + (Math.random() - 0.5) * 2.4,
      vy: point.y * spread + Math.random() * 2.2,
      vz: (Math.random() - 0.5) * 3.5,
      life: 1.1 + point.brightness * 1.6,
      color: fallbackColor
    };
  });
}

// src/mikuFireworks3D/engine/patterns/miku.ts
function createMikuSeeds(count) {
  const seeds = [];
  for (let i = 0; i < count; i += 1) {
    const ratio = i / Math.max(count - 1, 1);
    const angle = ratio * Math.PI * 2 * 2;
    const radial = 7 + Math.random() * 9;
    const spiralBoost = 3 + Math.random() * 4;
    seeds.push({
      x: 0,
      y: 0,
      z: 0,
      vx: Math.cos(angle) * radial,
      vy: Math.sin(angle * 0.5) * spiralBoost + 7,
      vz: Math.sin(angle) * radial,
      life: 1.2 + Math.random() * 1.5,
      color: pickRandomColor(MIKU_PALETTE)
    });
  }
  return seeds;
}

// src/mikuFireworks3D/engine/patterns/normal.ts
function createNormalSeeds(count, color) {
  const seeds = [];
  for (let i = 0; i < count; i += 1) {
    const theta = Math.random() * Math.PI * 2;
    const phi = Math.acos(2 * Math.random() - 1);
    const speed = 8 + Math.random() * 12;
    seeds.push({
      x: 0,
      y: 0,
      z: 0,
      vx: speed * Math.sin(phi) * Math.cos(theta),
      vy: speed * Math.cos(phi),
      vz: speed * Math.sin(phi) * Math.sin(theta),
      life: 1 + Math.random() * 1.6,
      color
    });
  }
  return seeds;
}

// src/mikuFireworks3D/engine/emitters.ts
async function createSeedParticles(options) {
  const palette = pickPalette(options.kind);
  const color = options.color ?? pickRandomColor(palette);
  if (options.kind === "miku") {
    return createMikuSeeds(options.count);
  }
  if (options.kind === "avatar" && options.avatarUrl) {
    const avatarSeeds = await createAvatarSeeds(options.avatarUrl, color);
    if (avatarSeeds.length > 0) {
      return avatarSeeds;
    }
  }
  return createNormalSeeds(options.count, color);
}

// src/mikuFireworks3D/engine/particlePool.ts
var ParticlePool = class {
  constructor() {
    this.pool = [];
  }
  acquire() {
    const reused = this.pool.pop();
    if (reused) {
      return reused;
    }
    return {
      x: 0,
      y: 0,
      z: 0,
      vx: 0,
      vy: 0,
      vz: 0,
      life: 0,
      maxLife: 1,
      r: 1,
      g: 1,
      b: 1
    };
  }
  release(particle) {
    this.pool.push(particle);
  }
};

// src/mikuFireworks3D/engine/postfx.ts
function evaluateDegradePolicy(fps) {
  if (fps < 24) {
    return { shouldDegrade: true, recommendedParticleScale: 0.65 };
  }
  if (fps < 34) {
    return { shouldDegrade: true, recommendedParticleScale: 0.82 };
  }
  return { shouldDegrade: false, recommendedParticleScale: 1 };
}

// src/mikuFireworks3D/engine/FireworksEngine.ts
var FireworksEngine = class {
  constructor(init) {
    this.pool = new ParticlePool();
    this.bursts = [];
    this.animationFrameId = null;
    this.lastTick = 0;
    this.fpsWindow = { frames: 0, elapsed: 0, fps: 60, particleScale: 1 };
    this.resizeObserver = null;
    this.loop = () => {
      const now = window.performance.now();
      const dt = Math.min((now - this.lastTick) / 1e3, 0.05);
      this.lastTick = now;
      this.update(dt);
      this.renderer.render(this.scene, this.camera);
      this.animationFrameId = window.requestAnimationFrame(this.loop);
    };
    this.canvas = init.canvas;
    this.container = init.container;
    this.maxParticles = init.options?.maxParticles ?? DEFAULT_MAX_PARTICLES;
    this.maxActiveFireworks = init.options?.maxActiveFireworks ?? DEFAULT_MAX_ACTIVE_FIREWORKS;
    this.onError = init.options?.onError;
    this.onFpsReport = init.options?.onFpsReport;
    this.scene = new THREE2__namespace.Scene();
    this.scene.background = new THREE2__namespace.Color("#060816");
    this.camera = new THREE2__namespace.PerspectiveCamera(50, 1, 0.1, 1e3);
    this.camera.position.set(0, 0, 45);
    this.renderer = new THREE2__namespace.WebGLRenderer({
      canvas: this.canvas,
      alpha: true,
      antialias: true,
      powerPreference: "high-performance"
    });
    this.renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
    this.spriteTexture = createCircularSpriteTexture();
    this.attachResizeObserver();
    this.resize();
  }
  start() {
    if (this.animationFrameId != null) {
      return;
    }
    this.lastTick = window.performance.now();
    this.loop();
  }
  stop() {
    if (this.animationFrameId != null) {
      window.cancelAnimationFrame(this.animationFrameId);
      this.animationFrameId = null;
    }
  }
  async launch(payload) {
    try {
      this.enforceBurstCap();
      const particleBudget = Math.max(80, Math.floor(280 * this.fpsWindow.particleScale));
      const seeds = await createSeedParticles({
        kind: payload.kind,
        count: particleBudget,
        color: payload.color,
        avatarUrl: payload.avatarUrl
      });
      if (seeds.length === 0) {
        return;
      }
      const launchPosition = payload.position ?? {
        x: (Math.random() - 0.5) * 18,
        y: -4 + Math.random() * 12,
        z: (Math.random() - 0.5) * 4
      };
      const particles = [];
      const positions = new Float32Array(seeds.length * 3);
      const colors = new Float32Array(seeds.length * 3);
      const colorHelper = new THREE2__namespace.Color();
      for (let i = 0; i < seeds.length; i += 1) {
        const seed = seeds[i];
        if (!seed) {
          continue;
        }
        const particle = this.pool.acquire();
        particle.x = launchPosition.x + seed.x;
        particle.y = launchPosition.y + seed.y;
        particle.z = launchPosition.z + seed.z;
        particle.vx = seed.vx;
        particle.vy = seed.vy;
        particle.vz = seed.vz;
        particle.life = seed.life;
        particle.maxLife = seed.life;
        colorHelper.set(seed.color);
        particle.r = colorHelper.r;
        particle.g = colorHelper.g;
        particle.b = colorHelper.b;
        particles.push(particle);
        const offset = i * 3;
        positions[offset] = particle.x;
        positions[offset + 1] = particle.y;
        positions[offset + 2] = particle.z;
        colors[offset] = particle.r;
        colors[offset + 1] = particle.g;
        colors[offset + 2] = particle.b;
      }
      if (this.totalParticleCount() + particles.length > this.maxParticles) {
        this.releaseParticles(particles);
        return;
      }
      const geometry = new THREE2__namespace.BufferGeometry();
      geometry.setAttribute("position", new THREE2__namespace.BufferAttribute(positions, 3));
      geometry.setAttribute("color", new THREE2__namespace.BufferAttribute(colors, 3));
      const material = new THREE2__namespace.PointsMaterial({
        size: payload.kind === "miku" ? 0.42 : 0.36,
        vertexColors: true,
        map: this.spriteTexture,
        transparent: true,
        opacity: 1,
        depthWrite: false,
        blending: THREE2__namespace.AdditiveBlending
      });
      const points = new THREE2__namespace.Points(geometry, material);
      this.scene.add(points);
      const burst = {
        id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
        points,
        geometry,
        material,
        positions,
        colors,
        particles
      };
      this.bursts.push(burst);
    } catch (error) {
      this.onError?.(error instanceof Error ? error : new Error("Failed to launch firework."));
    }
  }
  dispose() {
    this.stop();
    if (this.resizeObserver) {
      this.resizeObserver.disconnect();
      this.resizeObserver = null;
    }
    for (const burst of this.bursts) {
      this.destroyBurst(burst);
    }
    this.bursts.length = 0;
    this.spriteTexture.dispose();
    this.renderer.dispose();
  }
  update(dt) {
    const gravity = -8.8;
    for (let b = this.bursts.length - 1; b >= 0; b -= 1) {
      const burst = this.bursts[b];
      if (!burst) {
        continue;
      }
      let alive = 0;
      for (let i = 0; i < burst.particles.length; i += 1) {
        const particle = burst.particles[i];
        if (!particle) {
          continue;
        }
        particle.life -= dt;
        if (particle.life <= 0) {
          continue;
        }
        particle.vx *= 0.992;
        particle.vy = particle.vy * 0.992 + gravity * dt;
        particle.vz *= 0.992;
        particle.x += particle.vx * dt;
        particle.y += particle.vy * dt;
        particle.z += particle.vz * dt;
        const idx = i * 3;
        burst.positions[idx] = particle.x;
        burst.positions[idx + 1] = particle.y;
        burst.positions[idx + 2] = particle.z;
        const alpha = Math.max(particle.life / particle.maxLife, 0);
        burst.colors[idx] = particle.r * alpha;
        burst.colors[idx + 1] = particle.g * alpha;
        burst.colors[idx + 2] = particle.b * alpha;
        alive += 1;
      }
      const positionAttr = burst.geometry.getAttribute("position");
      const colorAttr = burst.geometry.getAttribute("color");
      positionAttr.needsUpdate = true;
      colorAttr.needsUpdate = true;
      burst.material.opacity = Math.min(1, 0.22 + alive / Math.max(burst.particles.length, 1));
      if (alive === 0) {
        this.bursts.splice(b, 1);
        this.destroyBurst(burst);
      }
    }
    this.updateFpsStats(dt);
  }
  updateFpsStats(dt) {
    this.fpsWindow.frames += 1;
    this.fpsWindow.elapsed += dt;
    if (this.fpsWindow.elapsed < 0.6) {
      return;
    }
    const fps = this.fpsWindow.frames / this.fpsWindow.elapsed;
    this.fpsWindow.fps = fps;
    const policy = evaluateDegradePolicy(fps);
    this.fpsWindow.particleScale = policy.recommendedParticleScale;
    this.onFpsReport?.(Math.round(fps));
    this.fpsWindow.frames = 0;
    this.fpsWindow.elapsed = 0;
  }
  totalParticleCount() {
    return this.bursts.reduce((sum, burst) => sum + burst.particles.length, 0);
  }
  enforceBurstCap() {
    while (this.bursts.length >= this.maxActiveFireworks) {
      const burst = this.bursts.shift();
      if (!burst) {
        break;
      }
      this.destroyBurst(burst);
    }
  }
  destroyBurst(burst) {
    this.scene.remove(burst.points);
    burst.geometry.dispose();
    burst.material.dispose();
    this.releaseParticles(burst.particles);
  }
  releaseParticles(particles) {
    for (const particle of particles) {
      this.pool.release(particle);
    }
  }
  attachResizeObserver() {
    this.resizeObserver = new ResizeObserver(() => this.resize());
    this.resizeObserver.observe(this.container);
  }
  resize() {
    const width = Math.max(1, this.container.clientWidth);
    const height = Math.max(1, this.container.clientHeight);
    this.camera.aspect = width / height;
    this.camera.updateProjectionMatrix();
    this.renderer.setSize(width, height, false);
  }
};

// src/mikuFireworks3D/hooks/useFireworksEngine.ts
function useFireworksEngine(options) {
  const containerRef = React3.useRef(null);
  const canvasRef = React3.useRef(null);
  const engineRef = React3.useRef(null);
  const pendingLaunchesRef = React3.useRef([]);
  const [fps, setFps] = React3.useState(60);
  const { maxParticles, maxActiveFireworks, onError, onFpsReport, onLaunch } = options || {};
  React3.useEffect(() => {
    if (!containerRef.current || !canvasRef.current) {
      return;
    }
    const engine = new FireworksEngine({
      container: containerRef.current,
      canvas: canvasRef.current,
      options: {
        maxParticles,
        maxActiveFireworks,
        onError,
        onFpsReport: (nextFps) => {
          setFps(nextFps);
          onFpsReport?.(nextFps);
        }
      }
    });
    engine.start();
    engineRef.current = engine;
    if (pendingLaunchesRef.current.length > 0) {
      const pending = [...pendingLaunchesRef.current];
      pendingLaunchesRef.current = [];
      pending.forEach((payload) => {
        void engine.launch(payload);
      });
    }
    return () => {
      engineRef.current?.dispose();
      engineRef.current = null;
      pendingLaunchesRef.current = [];
    };
  }, [maxParticles, maxActiveFireworks, onError, onFpsReport]);
  const launch = React3.useCallback(
    (payload) => {
      if (!engineRef.current) {
        pendingLaunchesRef.current.push(payload);
        if (pendingLaunchesRef.current.length > 120) {
          pendingLaunchesRef.current.splice(0, pendingLaunchesRef.current.length - 120);
        }
      } else {
        void engineRef.current.launch(payload);
      }
      onLaunch?.(payload);
    },
    [onLaunch]
  );
  const api = React3.useMemo(
    () => ({
      containerRef,
      canvasRef,
      launch,
      fps
    }),
    [fps, launch]
  );
  return api;
}

// src/mikuFireworks3D/client/WebSocketTransport.ts
var WebSocketTransport = class {
  constructor(config, callbacks) {
    this.socket = null;
    this.reconnectTimer = null;
    this.isManualClose = false;
    this.pendingQueue = [];
    this.config = config;
    this.callbacks = callbacks || {};
    this.state = {
      connected: false,
      joined: false,
      onlineCount: 0,
      roomId: config.roomId
    };
  }
  connect() {
    if (this.socket && (this.socket.readyState === window.WebSocket.OPEN || this.socket.readyState === window.WebSocket.CONNECTING)) {
      return;
    }
    this.isManualClose = false;
    try {
      this.socket = this.config.protocols ? new window.WebSocket(this.config.serverUrl, this.config.protocols) : new window.WebSocket(this.config.serverUrl);
    } catch {
      this.callbacks.onError?.(new Error("Failed to create WebSocket connection."));
      return;
    }
    this.socket.onopen = () => {
      this.updateState({ connected: true, joined: false });
      this.send({
        type: "join",
        roomId: this.config.roomId,
        user: this.config.user
      });
    };
    this.socket.onmessage = (event) => {
      const parsed = parseServerMessage(event.data);
      if (!parsed) {
        return;
      }
      this.handleServerMessage(parsed);
    };
    this.socket.onerror = () => {
      this.callbacks.onError?.(new Error("WebSocket transport error."));
    };
    this.socket.onclose = () => {
      this.updateState({ connected: false, joined: false });
      this.scheduleReconnect();
    };
  }
  disconnect() {
    this.isManualClose = true;
    this.pendingQueue.length = 0;
    if (this.reconnectTimer != null) {
      window.clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
    if (!this.socket) {
      return;
    }
    this.send({ type: "leave" });
    this.socket.close();
    this.socket = null;
  }
  sendDanmaku(payload) {
    this.send({
      type: "danmaku.send",
      payload
    });
  }
  sendFirework(payload) {
    this.send({
      type: "firework.launch",
      payload
    });
  }
  getState() {
    return this.state;
  }
  send(message) {
    if (!this.socket || this.socket.readyState !== window.WebSocket.OPEN) {
      if (message.type === "danmaku.send" || message.type === "firework.launch") {
        this.pendingQueue.push(message);
      }
      return;
    }
    if ((message.type === "danmaku.send" || message.type === "firework.launch") && !this.state.joined) {
      this.pendingQueue.push(message);
      return;
    }
    this.socket.send(JSON.stringify(message));
  }
  updateState(partial) {
    this.state = {
      ...this.state,
      ...partial
    };
    this.callbacks.onStateChange?.(this.state);
  }
  handleServerMessage(message) {
    if (message.type === "joined") {
      this.updateState({ roomId: message.roomId, onlineCount: message.onlineCount, joined: true });
      this.flushPendingQueue();
      return;
    }
    if (message.type === "room.user_joined" || message.type === "room.user_left") {
      this.updateState({ onlineCount: message.onlineCount, roomId: message.roomId });
      return;
    }
    if (message.type === "room.snapshot") {
      this.updateState({ roomId: message.roomId, onlineCount: message.users.length });
      this.callbacks.onSnapshot?.(message);
      return;
    }
    if (message.type === "danmaku.broadcast") {
      this.callbacks.onDanmakuBroadcast?.(message.event);
      return;
    }
    if (message.type === "firework.broadcast") {
      this.callbacks.onFireworkBroadcast?.(message.event);
      return;
    }
    if (message.type === "error") {
      this.callbacks.onError?.(new Error(`${message.code}: ${message.message}`));
    }
  }
  scheduleReconnect() {
    const reconnect = this.config.reconnect ?? true;
    if (this.isManualClose || !reconnect) {
      return;
    }
    if (this.reconnectTimer != null) {
      return;
    }
    const delay = this.config.reconnectIntervalMs ?? 1500;
    this.reconnectTimer = window.setTimeout(() => {
      this.reconnectTimer = null;
      this.connect();
    }, delay);
  }
  flushPendingQueue() {
    if (!this.socket || this.socket.readyState !== window.WebSocket.OPEN || !this.state.joined) {
      return;
    }
    while (this.pendingQueue.length > 0) {
      const message = this.pendingQueue.shift();
      if (!message) {
        break;
      }
      this.socket.send(JSON.stringify(message));
    }
  }
};
function parseServerMessage(raw) {
  if (typeof raw !== "string") {
    return null;
  }
  try {
    return JSON.parse(raw);
  } catch {
    return null;
  }
}

// src/mikuFireworks3D/hooks/useFireworksRealtime.ts
function useFireworksRealtime(options) {
  const { config, enabled, onDanmakuBroadcast, onFireworkBroadcast, onSnapshot, onError, onStateChange } = options;
  const transportRef = React3.useRef(null);
  const callbackRef = React3.useRef({
    onDanmakuBroadcast,
    onFireworkBroadcast,
    onSnapshot,
    onError,
    onStateChange
  });
  const serverUrl = config?.serverUrl ?? "";
  const roomId = config?.roomId ?? "";
  const userId = config?.user.userId ?? "";
  const nickname = config?.user.nickname ?? "";
  const avatarUrl = config?.user.avatarUrl ?? "";
  const reconnect = config?.reconnect ?? true;
  const reconnectIntervalMs = config?.reconnectIntervalMs ?? 1500;
  const [state, setState] = React3.useState({
    connected: false,
    joined: false,
    onlineCount: 0,
    roomId
  });
  React3.useEffect(() => {
    callbackRef.current = {
      onDanmakuBroadcast,
      onFireworkBroadcast,
      onSnapshot,
      onError,
      onStateChange
    };
  }, [onDanmakuBroadcast, onError, onFireworkBroadcast, onSnapshot, onStateChange]);
  const normalizedConfig = React3.useMemo(() => {
    if (!serverUrl || !roomId || !userId) {
      return void 0;
    }
    const protocols = Array.isArray(config?.protocols) ? [...config.protocols] : config?.protocols;
    return {
      serverUrl,
      roomId,
      user: {
        userId,
        nickname: nickname || void 0,
        avatarUrl: avatarUrl || void 0
      },
      protocols,
      reconnect,
      reconnectIntervalMs
    };
  }, [avatarUrl, config?.protocols, nickname, reconnect, reconnectIntervalMs, roomId, serverUrl, userId]);
  React3.useEffect(() => {
    if (!enabled || !normalizedConfig) {
      transportRef.current?.disconnect();
      transportRef.current = null;
      setState({
        connected: false,
        joined: false,
        onlineCount: 0,
        roomId: normalizedConfig?.roomId
      });
      return;
    }
    const transport = new WebSocketTransport(normalizedConfig, {
      onStateChange: (nextState) => {
        setState(nextState);
        callbackRef.current.onStateChange?.(nextState);
      },
      onDanmakuBroadcast: (event) => {
        callbackRef.current.onDanmakuBroadcast?.({
          id: event.id,
          text: event.text,
          color: event.color,
          kind: event.kind,
          userId: event.user.userId,
          timestamp: event.timestamp
        });
      },
      onFireworkBroadcast: (event) => {
        callbackRef.current.onFireworkBroadcast?.({
          id: event.id,
          payload: event.payload,
          userId: event.user.userId,
          timestamp: event.timestamp
        });
      },
      onSnapshot: (snapshot) => {
        callbackRef.current.onSnapshot?.({
          roomId: snapshot.roomId,
          danmakuHistory: snapshot.danmakuHistory.map((item) => ({
            id: item.id,
            text: item.text,
            color: item.color,
            kind: item.kind,
            userId: item.user.userId,
            timestamp: item.timestamp
          })),
          fireworkHistory: snapshot.fireworkHistory.map((item) => ({
            id: item.id,
            payload: item.payload,
            userId: item.user.userId,
            timestamp: item.timestamp
          }))
        });
      },
      onError: (error) => {
        callbackRef.current.onError?.(error);
      }
    });
    transport.connect();
    transportRef.current = transport;
    return () => {
      transport.disconnect();
      if (transportRef.current === transport) {
        transportRef.current = null;
      }
    };
  }, [enabled, normalizedConfig]);
  return React3.useMemo(
    () => ({
      state,
      sendDanmaku: (payload) => {
        transportRef.current?.sendDanmaku(payload);
      },
      sendFirework: (payload) => {
        transportRef.current?.sendFirework(payload);
      }
    }),
    [state]
  );
}

// src/mikuFireworks3D/components/MikuFireworks3D.tsx
function MikuFireworks3D({
  width = "100%",
  height = 520,
  className,
  defaultKind = "normal",
  autoLaunchOnDanmaku = true,
  maxParticles,
  maxActiveFireworks,
  defaultAvatarUrl = "",
  onLaunch,
  onDanmakuSend,
  onError,
  onFpsReport,
  onRealtimeStateChange,
  realtime
}) {
  const [selectedKind, setSelectedKind] = React3.useState(defaultKind);
  const [avatarUrl, setAvatarUrl] = React3.useState(defaultAvatarUrl);
  const [autoLaunch, setAutoLaunch] = React3.useState(autoLaunchOnDanmaku);
  const seenDanmakuIdsRef = React3.useRef(/* @__PURE__ */ new Set());
  const seenFireworkIdsRef = React3.useRef(/* @__PURE__ */ new Set());
  const { containerRef, canvasRef, launch, fps } = useFireworksEngine({
    maxParticles,
    maxActiveFireworks,
    onLaunch,
    onError,
    onFpsReport
  });
  const { items, send, addIncoming, removeItem } = useDanmakuController({
    onSend: onDanmakuSend
  });
  const realtimeEnabled = Boolean(realtime && (realtime.enabled ?? true));
  const realtimeApi = useFireworksRealtime({
    enabled: realtimeEnabled,
    config: realtime,
    onStateChange: onRealtimeStateChange,
    onError,
    onDanmakuBroadcast: (event) => {
      if (seenDanmakuIdsRef.current.has(event.id)) {
        return;
      }
      seenDanmakuIdsRef.current.add(event.id);
      addIncoming({
        id: event.id,
        userId: event.userId,
        text: event.text,
        color: event.color,
        timestamp: event.timestamp
      });
    },
    onFireworkBroadcast: (event) => {
      if (seenFireworkIdsRef.current.has(event.id)) {
        return;
      }
      seenFireworkIdsRef.current.add(event.id);
      launch(event.payload);
    },
    onSnapshot: (snapshot) => {
      for (const danmaku of snapshot.danmakuHistory) {
        if (seenDanmakuIdsRef.current.has(danmaku.id)) {
          continue;
        }
        seenDanmakuIdsRef.current.add(danmaku.id);
        addIncoming({
          id: danmaku.id,
          userId: danmaku.userId,
          text: danmaku.text,
          color: danmaku.color,
          timestamp: danmaku.timestamp
        });
      }
      for (const firework of snapshot.fireworkHistory) {
        if (seenFireworkIdsRef.current.has(firework.id)) {
          continue;
        }
        seenFireworkIdsRef.current.add(firework.id);
        launch(firework.payload);
      }
    }
  });
  const handleLaunch = (kind) => {
    const payload = {
      kind,
      avatarUrl: kind === "avatar" ? avatarUrl || void 0 : void 0
    };
    if (realtimeEnabled && realtimeApi.state.connected && realtimeApi.state.joined) {
      realtimeApi.sendFirework(payload);
      return;
    }
    launch(payload);
  };
  const handleSendDanmaku = (text) => {
    const result = send(text, void 0, {
      optimistic: !(realtimeEnabled && realtimeApi.state.connected && realtimeApi.state.joined)
    });
    if (!result) {
      return;
    }
    const launchKind = result.launchKind ?? selectedKind;
    if (realtimeEnabled && realtimeApi.state.connected && realtimeApi.state.joined) {
      realtimeApi.sendDanmaku({
        text: result.message.text,
        color: result.message.color,
        kind: result.launchKind
      });
      if (autoLaunch) {
        realtimeApi.sendFirework({
          kind: launchKind,
          avatarUrl: launchKind === "avatar" ? avatarUrl || void 0 : void 0,
          message: result.message
        });
      }
      return;
    }
    if (autoLaunch || result.launchKind) {
      launch({
        kind: launchKind,
        avatarUrl: launchKind === "avatar" ? avatarUrl || void 0 : void 0,
        message: result.message
      });
    }
  };
  const containerStyle = React3.useMemo(
    () => ({
      width,
      height,
      minHeight: 360
    }),
    [height, width]
  );
  return /* @__PURE__ */ React3__default.default.createElement("div", { className: `mx-auto flex w-full max-w-5xl flex-col gap-3 ${className || ""}` }, /* @__PURE__ */ React3__default.default.createElement("div", { ref: containerRef, className: "relative overflow-hidden rounded-2xl border border-slate-700", style: containerStyle }, /* @__PURE__ */ React3__default.default.createElement(FireworksCanvas, { canvasRef }), /* @__PURE__ */ React3__default.default.createElement("div", { className: "pointer-events-none absolute inset-0 overflow-hidden" }, items.map((item) => /* @__PURE__ */ React3__default.default.createElement(
    "div",
    {
      key: item.id,
      onAnimationEnd: () => removeItem(item.id),
      className: "absolute right-[-30%] whitespace-nowrap text-sm font-semibold text-white drop-shadow",
      style: {
        top: `${8 + item.track * 11}%`,
        color: item.color || "#ffffff",
        animation: `sa2kit-danmaku-move ${item.durationMs}ms linear forwards`
      }
    },
    item.text
  )))), /* @__PURE__ */ React3__default.default.createElement(
    FireworksControlPanel,
    {
      selectedKind,
      onKindChange: setSelectedKind,
      autoLaunchOnDanmaku: autoLaunch,
      onAutoLaunchChange: setAutoLaunch,
      avatarUrl,
      onAvatarUrlChange: setAvatarUrl,
      onLaunch: () => handleLaunch(selectedKind),
      fps,
      realtimeConnected: realtimeEnabled ? realtimeApi.state.connected : void 0,
      onlineCount: realtimeEnabled ? realtimeApi.state.onlineCount : void 0
    }
  ), /* @__PURE__ */ React3__default.default.createElement(DanmakuPanel, { onSend: handleSendDanmaku }), /* @__PURE__ */ React3__default.default.createElement("style", null, `
        @keyframes sa2kit-danmaku-move {
          0% {
            transform: translateX(0);
            opacity: 1;
          }
          100% {
            transform: translateX(-160vw);
            opacity: 0.92;
          }
        }
      `));
}

exports.DANMAKU_MAX_LENGTH = DANMAKU_MAX_LENGTH;
exports.DANMAKU_TRACK_COUNT = DANMAKU_TRACK_COUNT;
exports.DEFAULT_MAX_ACTIVE_FIREWORKS = DEFAULT_MAX_ACTIVE_FIREWORKS;
exports.DEFAULT_MAX_PARTICLES = DEFAULT_MAX_PARTICLES;
exports.DanmakuPanel = DanmakuPanel;
exports.FIREWORK_KIND_LABELS = FIREWORK_KIND_LABELS;
exports.FireworksCanvas = FireworksCanvas;
exports.FireworksControlPanel = FireworksControlPanel;
exports.MIKU_PALETTE = MIKU_PALETTE;
exports.MikuFireworks3D = MikuFireworks3D;
exports.NORMAL_PALETTE = NORMAL_PALETTE;
exports.WebSocketTransport = WebSocketTransport;
exports.useDanmakuController = useDanmakuController;
exports.useFireworksEngine = useFireworksEngine;
exports.useFireworksRealtime = useFireworksRealtime;
//# sourceMappingURL=chunk-WZFJ3S3E.js.map
//# sourceMappingURL=chunk-WZFJ3S3E.js.map