/* ============================================================
   3D 生物建模編輯器 — Creature Editor
   基於 Three.js 的方塊式建模，包含：
   - 部件階層、Pivot、逐部件紋理編輯
   - 動畫時間軸、關鍵影格
   - AI 參考圖模式（前/側視圖、透明度、一鍵生成基礎模型）
   - 滑鼠 / 觸控 / 觸控筆（Pointer Events）
   ============================================================ */

function CreatureView({ project, touchMode, rightPanelCollapsed, onToggleRightPanel }) {
  const containerRef = React.useRef(null);
  const sceneRef = React.useRef(null);
  const cameraRef = React.useRef(null);
  const rendererRef = React.useRef(null);
  const rootGroupRef = React.useRef(null);
  const raycasterRef = React.useRef(null);
  const mouseRef = React.useRef(new THREE.Vector2());
  const refSpriteRef = React.useRef(null);

  const meshesRef = React.useRef({}); // partId -> { mesh, material, texture, group }

  const [parts, setParts] = React.useState([]);
  const [activePartId, setActivePartId] = React.useState(null);
  const [animations, setAnimations] = React.useState({});
  const [currentAnim, setCurrentAnim] = React.useState('Idle');
  const [currentFrame, setCurrentFrame] = React.useState(0);
  const [isPlaying, setIsPlaying] = React.useState(false);
  const [selectedKeyframe, setSelectedKeyframe] = React.useState(null);
  const [drawerMode, setDrawerMode] = React.useState('timeline');

  // 2D 材質編輯器狀態
  const [tool2d, setTool2d] = React.useState('pencil');
  const [color2d, setColor2d] = React.useState('#FFFFFFFF');
  const [zoom2d, setZoom2d] = React.useState(20);
  const [offset2d, setOffset2d] = React.useState({ x: 0, y: 0 });
  const [isDrawing2d, setIsDrawing2d] = React.useState(false);
  const [isPanning2d, setIsPanning2d] = React.useState(false);
  const [panStart2d, setPanStart2d] = React.useState(null);
  const [lastPos2d, setLastPos2d] = React.useState({ x: -1, y: -1 });
  const [shapeStart2d, setShapeStart2d] = React.useState(null);
  const [strokeStarted2d, setStrokeStarted2d] = React.useState(false);
  const [showGrid, setShowGrid] = React.useState(true);

  const editCanvasRef = React.useRef(null);
  const editOverlayRef = React.useRef(null);
  const drawingRef = React.useRef(false);
  const lastPosRef = React.useRef({ x: -1, y: -1 });

  // 3D 相機控制狀態
  const [, forceUpdate] = React.useReducer(x => x + 1, 0);
  const cameraStateRef = React.useRef({
    theta: Math.PI / 4, // 水平角度
    phi: Math.PI / 4,   // 垂直角度
    radius: 40,         // 距離
    target: new THREE.Vector3(0, 2, 0),
    isOrtho: false,
    isRotating: false,
    isPanning: false,
    lastX: 0,
    lastY: 0,
    rotateStartX: 0,
    rotateStartY: 0,
    panStartX: 0,
    panStartY: 0,
  });

  // Pointer 事件追蹤（雙指縮放/平移）
  const pointersRef = React.useRef(new Map());
  const pinchRef = React.useRef(null);
  const activePointerRef = React.useRef(null);
  const gestureModeRef = React.useRef(null); // 'rotate' | 'pan' | 'pinch' | null

  // AI 參考圖狀態
  const [referenceImage, setReferenceImage] = React.useState(null);
  const [showReference, setShowReference] = React.useState(false);
  const [referenceOpacity, setReferenceOpacity] = React.useState(60);
  const [referenceView, setReferenceView] = React.useState('front'); // 'front' | 'side'

  // 動畫播放 refs
  const animIdRef = React.useRef(null);
  const lastTimeRef = React.useRef(0);

  // ---- 初始化 Three.js 場景 ----
  React.useEffect(() => {
    const container = containerRef.current;
    if (!container) return;
    const w = container.clientWidth;
    const h = container.clientHeight;

    // 場景
    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x1e1e24);
    sceneRef.current = scene;

    // 相機
    const camera = new THREE.PerspectiveCamera(45, w / h, 0.1, 1000);
    cameraRef.current = camera;

    // 渲染器
    const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
    renderer.setSize(w, h);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.shadowMap.enabled = true;
    container.appendChild(renderer.domElement);
    rendererRef.current = renderer;

    // Raycaster 用於選取部件
    raycasterRef.current = new THREE.Raycaster();

    // 光照
    const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
    scene.add(ambientLight);
    const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
    dirLight.position.set(10, 20, 10);
    dirLight.castShadow = true;
    scene.add(dirLight);
    const fillLight = new THREE.DirectionalLight(0x88aaff, 0.3);
    fillLight.position.set(-10, 5, -10);
    scene.add(fillLight);

    // 地面格線
    const gridHelper = new THREE.GridHelper(32, 32, 0x444444, 0x2a2a2a);
    gridHelper.position.y = -0.01;
    scene.add(gridHelper);

    // 根群組
    const rootGroup = new THREE.Group();
    scene.add(rootGroup);
    rootGroupRef.current = rootGroup;

    // 參考圖精靈
    const refTexture = new THREE.TextureLoader().load('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxIiBoZWlnaHQ9IjEiLz4=');
    const refMaterial = new THREE.SpriteMaterial({
      map: refTexture,
      transparent: true,
      opacity: 0,
      depthTest: false,
      depthWrite: false,
    });
    const refSprite = new THREE.Sprite(refMaterial);
    refSprite.position.set(0, 8, -15);
    refSprite.scale.set(16, 16, 1);
    refSprite.visible = false;
    scene.add(refSprite);
    refSpriteRef.current = refSprite;

    let newParts;
    let newAnims;
    if (AppState.creatureParts && AppState.creatureParts.length) {
      newParts = AppState.creatureParts;
      newAnims = AppState.creatureAnimations || {};
    } else {
      const created = createHumanoidTemplate(project.width, 'human');
      newParts = created.parts;
      newAnims = created.animations;
    }
    newParts.forEach(part => {
      createPartMesh(part, rootGroup);
    });
    setParts(newParts);
    setAnimations(newAnims);
    setActivePartId(AppState.activePartId || newParts[0].id);

    // 更新相機
    updateCamera();

    // 渲染迴圈
    const animate = () => {
      requestAnimationFrame(animate);
      if (isPlaying && animations[currentAnim]) {
        tickAnimation();
      }
      renderer.render(scene, camera);
    };
    animate();

    const sizeWatch = observeRendererSize(container, camera, renderer);
    const handleResize = () => sizeWatch.apply();
    window.addEventListener('resize', handleResize);
    window.addEventListener('mcstudio:resize', handleResize);

    return () => {
      cancelAnimationFrame(animIdRef.current);
      sizeWatch.disconnect();
      window.removeEventListener('resize', handleResize);
      window.removeEventListener('mcstudio:resize', handleResize);
      renderer.dispose();
      if (renderer.domElement.parentNode) {
        renderer.domElement.parentNode.removeChild(renderer.domElement);
      }
      Object.values(meshesRef.current).forEach(ref => {
        ref.mesh.geometry.dispose();
        ref.material.dispose();
        ref.texture.dispose();
      });
    };
    // eslint-disable-next-line
  }, []);

  React.useEffect(() => {
    AppState.creatureParts = parts;
    AppState.creatureAnimations = animations;
    AppState.activePartId = activePartId;
  }, [parts, animations, activePartId]);

  // 更新參考圖精靈
  const updateReferenceSprite = (imgData, opacity, view) => {
    if (!refSpriteRef.current || !sceneRef.current) return;
    const loader = new THREE.TextureLoader();
    loader.load(imgData, (tex) => {
      tex.magFilter = THREE.LinearFilter;
      tex.minFilter = THREE.LinearFilter;
      const mat = refSpriteRef.current.material;
      mat.map = tex;
      mat.opacity = (opacity || 60) / 100;
      mat.needsUpdate = true;
      refSpriteRef.current.visible = showReference;
      // 計算適當的尺寸
      const aspect = tex.image.width / tex.image.height;
      const height = 16;
      refSpriteRef.current.scale.set(height * aspect, height, 1);
      if (view === 'side') {
        refSpriteRef.current.position.set(15, 8, 0);
      } else {
        refSpriteRef.current.position.set(0, 8, -15);
      }
    });
  };

  // 顯示/隱藏參考圖
  React.useEffect(() => {
    if (refSpriteRef.current) {
      refSpriteRef.current.visible = showReference && !!referenceImage;
      refSpriteRef.current.material.opacity = referenceOpacity / 100;
    }
  }, [showReference, referenceImage, referenceOpacity]);

  // 切換參考圖視圖
  React.useEffect(() => {
    if (!refSpriteRef.current || !referenceImage) return;
    if (referenceView === 'side') {
      refSpriteRef.current.position.set(15, 8, 0);
      refSpriteRef.current.rotation.y = Math.PI / 2;
    } else {
      refSpriteRef.current.position.set(0, 8, -15);
      refSpriteRef.current.rotation.y = 0;
    }
  }, [referenceView, referenceImage]);

  // 相機控制
  const updateCamera = () => {
    const cam = cameraRef.current;
    const cs = cameraStateRef.current;
    const x = cs.target.x + cs.radius * Math.sin(cs.phi) * Math.cos(cs.theta);
    const y = cs.target.y + cs.radius * Math.cos(cs.phi);
    const z = cs.target.z + cs.radius * Math.sin(cs.phi) * Math.sin(cs.theta);
    cam.position.set(x, y, z);
    cam.lookAt(cs.target);
  };

  const setCameraView = (view) => {
    const cs = cameraStateRef.current;
    const r = cs.radius;
    switch (view) {
      case 'front':
        cs.theta = Math.PI; cs.phi = Math.PI / 2;
        break;
      case 'back':
        cs.theta = 0; cs.phi = Math.PI / 2;
        break;
      case 'left':
        cs.theta = -Math.PI / 2; cs.phi = Math.PI / 2;
        break;
      case 'right':
        cs.theta = Math.PI / 2; cs.phi = Math.PI / 2;
        break;
      case 'top':
        cs.theta = 0; cs.phi = 0.01;
        break;
      case 'bottom':
        cs.theta = 0; cs.phi = Math.PI - 0.01;
        break;
    }
    updateCamera();
  };

  const resetCamera = () => {
    const cs = cameraStateRef.current;
    cs.theta = Math.PI / 4;
    cs.phi = Math.PI / 4;
    cs.radius = 40;
    cs.target.set(0, 2, 0);
    updateCamera();
  };

  // ---- Pointer 事件處理（3D 視圖）----
  const handlePointerDown = (e) => {
    e.preventDefault();
    const canvas = rendererRef.current.domElement;
    try { canvas.setPointerCapture(e.pointerId); } catch (err) {}

    pointersRef.current.set(e.pointerId, { id: e.pointerId, x: e.clientX, y: e.clientY });

    // 雙指：pinch 模式
    if (pointersRef.current.size === 2) {
      const pts = Array.from(pointersRef.current.values());
      const dx = pts[1].x - pts[0].x;
      const dy = pts[1].y - pts[0].y;
      const dist = Math.sqrt(dx * dx + dy * dy);
      const midX = (pts[0].x + pts[1].x) / 2;
      const midY = (pts[0].y + pts[1].y) / 2;
      pinchRef.current = {
        startDist: dist,
        startRadius: cameraStateRef.current.radius,
        startTarget: cameraStateRef.current.target.clone(),
        midX, midY,
      };
      gestureModeRef.current = 'pinch';
      return;
    }

    // 單指：檢查是否點擊到部件（僅在滑鼠左鍵且非 alt）
    if (e.button === 0 && !e.altKey && !e.shiftKey) {
      // 嘗試選取部件
      const hit = pickPart(e);
      if (hit) {
        setActivePartId(hit);
        activePointerRef.current = e.pointerId;
        gestureModeRef.current = null;
        return;
      }
      // 否則開始旋轉
      cameraStateRef.current.isRotating = true;
      cameraStateRef.current.rotateStartX = e.clientX;
      cameraStateRef.current.rotateStartY = e.clientY;
      cameraStateRef.current.startTheta = cameraStateRef.current.theta;
      cameraStateRef.current.startPhi = cameraStateRef.current.phi;
      gestureModeRef.current = 'rotate';
    } else if (e.button === 2 || e.shiftKey) {
      // 右鍵或 Shift：平移
      cameraStateRef.current.isPanning = true;
      cameraStateRef.current.panStartX = e.clientX;
      cameraStateRef.current.panStartY = e.clientY;
      cameraStateRef.current.panStartTarget = cameraStateRef.current.target.clone();
      gestureModeRef.current = 'pan';
    } else if (e.button === 1) {
      // 中鍵：平移
      cameraStateRef.current.isPanning = true;
      cameraStateRef.current.panStartX = e.clientX;
      cameraStateRef.current.panStartY = e.clientY;
      cameraStateRef.current.panStartTarget = cameraStateRef.current.target.clone();
      gestureModeRef.current = 'pan';
    }
    activePointerRef.current = e.pointerId;
  };

  const handlePointerMove = (e) => {
    e.preventDefault();

    if (pointersRef.current.has(e.pointerId)) {
      pointersRef.current.set(e.pointerId, { id: e.pointerId, x: e.clientX, y: e.clientY });
    }

    const cs = cameraStateRef.current;

    // 雙指縮放 + 平移
    if (gestureModeRef.current === 'pinch' && pinchRef.current && pointersRef.current.size >= 2) {
      const pts = Array.from(pointersRef.current.values());
      const dx = pts[1].x - pts[0].x;
      const dy = pts[1].y - pts[0].y;
      const dist = Math.sqrt(dx * dx + dy * dy);
      const ratio = pinchRef.current.startDist / dist;
      cs.radius = Math.max(5, Math.min(200, pinchRef.current.startRadius * ratio));

      // 雙指平移
      const midX = (pts[0].x + pts[1].x) / 2;
      const midY = (pts[0].y + pts[1].y) / 2;
      const dxMid = midX - pinchRef.current.midX;
      const dyMid = midY - pinchRef.current.midY;

      // 將畫面平移量轉換為場景平移
      const panScale = cs.radius * 0.01;
      cs.target.x = pinchRef.current.startTarget.x - dxMid * panScale;
      cs.target.y = pinchRef.current.startTarget.y + dyMid * panScale;

      updateCamera();
      return;
    }

    if (cs.isRotating && activePointerRef.current === e.pointerId) {
      const dx = e.clientX - cs.rotateStartX;
      const dy = e.clientY - cs.rotateStartY;
      cs.theta = cs.startTheta - dx * 0.01;
      cs.phi = Math.max(0.05, Math.min(Math.PI - 0.05, cs.startPhi - dy * 0.01));
      updateCamera();
      return;
    }

    if (cs.isPanning && activePointerRef.current === e.pointerId) {
      const dx = e.clientX - cs.panStartX;
      const dy = e.clientY - cs.panStartY;
      const panScale = cs.radius * 0.005;
      // 計算相機的 right / up 向量
      const cam = cameraRef.current;
      const right = new THREE.Vector3();
      const up = new THREE.Vector3(0, 1, 0);
      cam.getWorldDirection(right);
      right.cross(up).normalize();
      cs.target.x = cs.panStartTarget.x - right.x * dx * panScale;
      cs.target.z = cs.panStartTarget.z - right.z * dx * panScale;
      cs.target.y = cs.panStartTarget.y + dy * panScale;
      updateCamera();
      return;
    }
  };

  const handlePointerUp = (e) => {
    e.preventDefault();
    pointersRef.current.delete(e.pointerId);

    if (pointersRef.current.size < 2) {
      pinchRef.current = null;
      if (gestureModeRef.current === 'pinch') {
        gestureModeRef.current = null;
      }
    }

    const cs = cameraStateRef.current;
    if (activePointerRef.current === e.pointerId) {
      cs.isRotating = false;
      cs.isPanning = false;
      activePointerRef.current = null;
    }
  };

  const handlePointerCancel = (e) => {
    pointersRef.current.delete(e.pointerId);
    if (pointersRef.current.size < 2) {
      pinchRef.current = null;
    }
    const cs = cameraStateRef.current;
    if (activePointerRef.current === e.pointerId) {
      cs.isRotating = false;
      cs.isPanning = false;
      activePointerRef.current = null;
    }
  };

  const handleWheel = (e) => {
    e.preventDefault();
    const cs = cameraStateRef.current;
    const factor = e.deltaY > 0 ? 1.1 : 0.9;
    cs.radius = Math.max(5, Math.min(200, cs.radius * factor));
    updateCamera();
  };

  // Raycasting 選取部件
  const pickPart = (e) => {
    const container = containerRef.current;
    if (!container) return null;
    const rect = container.getBoundingClientRect();
    mouseRef.current.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
    mouseRef.current.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;

    const raycaster = raycasterRef.current;
    raycaster.setFromCamera(mouseRef.current, cameraRef.current);
    const meshes = Object.values(meshesRef.current).map(m => m.mesh);
    const intersects = raycaster.intersectObjects(meshes, false);

    if (intersects.length > 0) {
      const hit = intersects[0].object;
      // 找到對應的 partId
      for (const [partId, ref] of Object.entries(meshesRef.current)) {
        if (ref.mesh === hit) return partId;
      }
    }
    return null;
  };

  // ---- 建立部件網格 ----
  const createPartMesh = (part, parentGroup) => {
    const tex = new THREE.CanvasTexture(part.texture.canvas);
    tex.magFilter = THREE.NearestFilter;
    tex.minFilter = THREE.NearestFilter;
    tex.generateMipmaps = false;
    tex.needsUpdate = true;

    const geo = new THREE.BoxGeometry(part.size.w, part.size.h, part.size.d);
    const mat = new THREE.MeshStandardMaterial({
      map: tex,
      transparent: true,
      side: THREE.FrontSide,
    });
    const mesh = new THREE.Mesh(geo, mat);
    mesh.position.set(part.pos.x, part.pos.y, part.pos.z);
    mesh.rotation.set(
      part.rot.x * Math.PI / 180,
      part.rot.y * Math.PI / 180,
      part.rot.z * Math.PI / 180
    );
    mesh.scale.set(part.scale.x, part.scale.y, part.scale.z);
    mesh.userData.partId = part.id;

    // 用一個子群組來處理 pivot
    const group = new THREE.Group();
    group.position.set(part.pivot.x, part.pivot.y, part.pivot.z);
    mesh.position.set(
      part.pos.x - part.pivot.x,
      part.pos.y - part.pivot.y,
      part.pos.z - part.pivot.z
    );
    group.add(mesh);
    parentGroup.add(group);

    meshesRef.current[part.id] = { mesh, material: mat, texture: tex, group };
  };

  // 更新部件
  const updatePart = (partId, changes) => {
    const part = parts.find(p => p.id === partId);
    if (!part) return;
    const newParts = parts.map(p => p.id === partId ? { ...p, ...changes } : p);
    setParts(newParts);

    // 更新網格
    const ref = meshesRef.current[partId];
    if (ref) {
      if (changes.pos) {
        ref.mesh.position.x = (changes.pos.x ?? part.pos.x) - (changes.pivot?.x ?? part.pivot.x);
        ref.mesh.position.y = (changes.pos.y ?? part.pos.y) - (changes.pivot?.y ?? part.pivot.y);
        ref.mesh.position.z = (changes.pos.z ?? part.pos.z) - (changes.pivot?.z ?? part.pivot.z);
      }
      if (changes.rot) {
        ref.mesh.rotation.x = (changes.rot.x ?? part.rot.x) * Math.PI / 180;
        ref.mesh.rotation.y = (changes.rot.y ?? part.rot.y) * Math.PI / 180;
        ref.mesh.rotation.z = (changes.rot.z ?? part.rot.z) * Math.PI / 180;
      }
      if (changes.scale) {
        ref.mesh.scale.set(changes.scale.x ?? part.scale.x, changes.scale.y ?? part.scale.y, changes.scale.z ?? part.scale.z);
      }
      if (changes.pivot) {
        ref.group.position.set(changes.pivot.x ?? part.pivot.x, changes.pivot.y ?? part.pivot.y, changes.pivot.z ?? part.pivot.z);
        ref.mesh.position.x = (changes.pos?.x ?? part.pos.x) - (changes.pivot.x ?? part.pivot.x);
        ref.mesh.position.y = (changes.pos?.y ?? part.pos.y) - (changes.pivot.y ?? part.pivot.y);
        ref.mesh.position.z = (changes.pos?.z ?? part.pos.z) - (changes.pivot.z ?? part.pivot.z);
      }
      if (changes.size) {
        ref.mesh.geometry.dispose();
        ref.mesh.geometry = new THREE.BoxGeometry(
          changes.size.w ?? part.size.w,
          changes.size.h ?? part.size.h,
          changes.size.d ?? part.size.d
        );
      }
      if (changes.name) {
        ref.texture.name = changes.name;
      }
      if (changes.visible !== undefined) {
        ref.group.visible = changes.visible;
      }
    }
  };

  // 新增部件
  const addPart = (type) => {
    pushUndo();
    const id = uid();
    const nameMap = {
      head: '頭部', body: '身體', arm: '手臂', leg: '腿部',
      tail: '尾巴', wing: '翅膀', horn: '角', ear: '耳朵',
      eye: '眼睛', mouth: '嘴巴', custom: '自訂方塊',
    };
    const sizeMap = {
      head: { w: 8, h: 8, d: 8 },
      body: { w: 8, h: 12, d: 4 },
      arm: { w: 4, h: 12, d: 4 },
      leg: { w: 4, h: 12, d: 4 },
      tail: { w: 3, h: 8, d: 3 },
      wing: { w: 2, h: 8, d: 8 },
      horn: { w: 2, h: 4, d: 2 },
      ear: { w: 2, h: 4, d: 2 },
      eye: { w: 1, h: 1, d: 1 },
      mouth: { w: 2, h: 1, d: 1 },
      custom: { w: 4, h: 4, d: 4 },
    };
    const size = sizeMap[type] || { w: 4, h: 4, d: 4 };
    const canvas = createPixelCanvas(project.width, project.height);
    const newPart = {
      id,
      name: nameMap[type] || '自訂方塊',
      type,
      parent: null,
      pos: { x: 0, y: 4, z: 0 },
      rot: { x: 0, y: 0, z: 0 },
      scale: { x: 1, y: 1, z: 1 },
      pivot: { x: 0, y: 0, z: 0 },
      size,
      texture: { canvas, name: nameMap[type] || '自訂方塊', width: project.width, height: project.height },
      visible: true,
    };
    createPartMesh(newPart, rootGroupRef.current);
    setParts(prev => [...prev, newPart]);
    setActivePartId(id);
    showToast(`已新增 ${nameMap[type] || type}`, 'success');
  };

  // 刪除部件
  const deletePart = (partId) => {
    if (parts.length <= 1) {
      showToast('至少需要一個部件', 'warning');
      return;
    }
    pushUndo();
    const ref = meshesRef.current[partId];
    if (ref) {
      ref.mesh.geometry.dispose();
      ref.material.dispose();
      ref.texture.dispose();
      ref.group.parent.remove(ref.group);
      delete meshesRef.current[partId];
    }
    const newParts = parts.filter(p => p.id !== partId);
    setParts(newParts);
    if (activePartId === partId) {
      setActivePartId(newParts[0].id);
    }
    showToast('已刪除部件', 'info');
  };

  // 複製部件
  const duplicatePart = (partId) => {
    if (!partId) return;
    pushUndo();
    const src = parts.find(p => p.id === partId);
    if (!src) return;
    const id = uid();
    const newCanvas = copyCanvas(src.texture.canvas);
    const newPart = {
      ...src,
      id,
      name: src.name + ' 副本',
      pos: { x: src.pos.x + 2, y: src.pos.y, z: src.pos.z },
      texture: { ...src.texture, canvas: newCanvas, name: src.name + ' 副本' },
    };
    createPartMesh(newPart, rootGroupRef.current);
    setParts(prev => [...prev, newPart]);
    setActivePartId(id);
    showToast('已複製部件', 'success');
  };

  // ---- 動畫 ----
  const tickAnimation = () => {
    const anim = animations[currentAnim];
    if (!anim) return;
    const now = performance.now();
    const dt = (now - lastTimeRef.current) / 1000;
    lastTimeRef.current = now;
    const frameInc = dt * (anim.fps || 20);
    let newFrame = currentFrame + frameInc;
    if (newFrame > anim.endFrame) {
      newFrame = anim.startFrame;
    }
    setCurrentFrame(newFrame);
    applyAnimationFrame(newFrame);
  };

  const applyAnimationFrame = (frame) => {
    const anim = animations[currentAnim];
    if (!anim) return;

    // 對每個部件插值
    parts.forEach(part => {
      const kfs = anim.keyframes[part.id] || [];
      if (kfs.length === 0) return;
      // 找到前後兩個關鍵影格
      let prevKf = null, nextKf = null;
      for (const kf of kfs) {
        if (kf.frame <= frame && (!prevKf || kf.frame > prevKf.frame)) prevKf = kf;
        if (kf.frame >= frame && (!nextKf || kf.frame < nextKf.frame)) nextKf = kf;
      }
      const ref = meshesRef.current[part.id];
      if (!ref) return;

      if (!prevKf && !nextKf) return;
      const kf = prevKf || nextKf;

      if (prevKf && nextKf && prevKf !== nextKf) {
        const t = (frame - prevKf.frame) / (nextKf.frame - prevKf.frame);
        const rot = {
          x: prevKf.rot.x + (nextKf.rot.x - prevKf.rot.x) * t,
          y: prevKf.rot.y + (nextKf.rot.y - prevKf.rot.y) * t,
          z: prevKf.rot.z + (nextKf.rot.z - prevKf.rot.z) * t,
        };
        ref.group.rotation.set(rot.x * Math.PI / 180, rot.y * Math.PI / 180, rot.z * Math.PI / 180);
      } else {
        ref.group.rotation.set(kf.rot.x * Math.PI / 180, kf.rot.y * Math.PI / 180, kf.rot.z * Math.PI / 180);
      }
    });
  };

  // 新增關鍵影格
  const addKeyframe = () => {
    if (!activePartId) return;
    const anim = animations[currentAnim];
    if (!anim) return;
    const frame = Math.round(currentFrame);
    const part = parts.find(p => p.id === activePartId);
    if (!part) return;

    pushUndo();
    const ref = meshesRef.current[activePartId];
    const rot = ref ? {
      x: ref.group.rotation.x * 180 / Math.PI,
      y: ref.group.rotation.y * 180 / Math.PI,
      z: ref.group.rotation.z * 180 / Math.PI,
    } : { x: 0, y: 0, z: 0 };

    const partKfs = [...(anim.keyframes[activePartId] || [])];
    const existingIdx = partKfs.findIndex(k => k.frame === frame);
    const newKf = { frame, rot };
    if (existingIdx >= 0) {
      partKfs[existingIdx] = newKf;
    } else {
      partKfs.push(newKf);
      partKfs.sort((a, b) => a.frame - b.frame);
    }

    setAnimations(prev => ({
      ...prev,
      [currentAnim]: {
        ...prev[currentAnim],
        keyframes: { ...prev[currentAnim].keyframes, [activePartId]: partKfs },
      }
    }));
    setSelectedKeyframe({ partId: activePartId, frame });
    showToast('已新增關鍵影格', 'success');
  };

  // 刪除關鍵影格
  const deleteKeyframe = () => {
    if (!selectedKeyframe) return;
    pushUndo();
    const { partId, frame } = selectedKeyframe;
    setAnimations(prev => {
      const anim = prev[currentAnim];
      const partKfs = (anim.keyframes[partId] || []).filter(k => k.frame !== frame);
      return {
        ...prev,
        [currentAnim]: {
          ...prev[currentAnim],
          keyframes: { ...prev[currentAnim].keyframes, [partId]: partKfs },
        },
      };
    });
    setSelectedKeyframe(null);
    showToast('已刪除關鍵影格', 'success');
  };

  // ---- 2D 材質編輯 ----
  const activePart = parts.find(p => p.id === activePartId);

  const get2dPixelPos = (e) => {
    const canvas = editCanvasRef.current;
    if (!canvas || !activePart) return { x: 0, y: 0 };
    const rect = canvas.getBoundingClientRect();
    const px = (e.clientX - rect.left) / rect.width * activePart.texture.width;
    const py = (e.clientY - rect.top) / rect.height * activePart.texture.height;
    return { x: Math.floor(px), y: Math.floor(py) };
  };

  const texPointerDown = (e) => {
    e.preventDefault();
    const canvas = editCanvasRef.current;
    if (!canvas || !activePart) return;
    try { canvas.setPointerCapture(e.pointerId); } catch (err) {}

    const pos = get2dPixelPos(e);
    if (pos.x < 0 || pos.y < 0 || pos.x >= activePart.texture.width || pos.y >= activePart.texture.height) return;

    pushUndo();
    drawingRef.current = true;
    lastPosRef.current = pos;
    setIsDrawing2d(true);
    setStrokeStarted2d(true);
    setLastPos2d(pos);
    setShapeStart2d(pos);

    const c = tool2d === 'eraser' ? '#00000000' : color2d;
    if (tool2d === 'pencil' || tool2d === 'eraser') {
      PixelCanvasCore.putPixel(activePart, pos.x, pos.y, c, activePart.texture.width, activePart.texture.height);
    } else if (tool2d === 'fill') {
      PixelCanvasCore.fill(activePart, pos.x, pos.y, color2d, activePart.texture.width, activePart.texture.height);
      drawingRef.current = false;
      setIsDrawing2d(false);
    }
    refreshMeshTexture(activePartId);
  };

  const texPointerMove = (e) => {
    e.preventDefault();
    if (!drawingRef.current || !activePart) return;
    const pos = get2dPixelPos(e);
    const last = lastPosRef.current;
    if (pos.x === last.x && pos.y === last.y) return;

    const c = tool2d === 'eraser' ? '#00000000' : color2d;
    if (tool2d === 'pencil' || tool2d === 'eraser') {
      PixelCanvasCore.line(activePart, last.x, last.y, pos.x, pos.y, c, activePart.texture.width, activePart.texture.height);
    }
    lastPosRef.current = pos;
    setLastPos2d(pos);
    refreshMeshTexture(activePartId);
  };

  const texPointerUp = (e) => {
    e.preventDefault();
    drawingRef.current = false;
    setIsDrawing2d(false);
    setStrokeStarted2d(false);
    refreshMeshTexture(activePartId);
  };

  const texWheel = (e) => {
    e.preventDefault();
    const delta = e.deltaY > 0 ? 0.8 : 1.25;
    setZoom2d(z => Math.max(1, Math.min(64, Math.round(z * delta))));
  };

  const refreshMeshTexture = (partId) => {
    const ref = meshesRef.current[partId];
    if (ref && ref.texture) {
      markTextureDirty(ref.texture);
      if (ref.material) ref.material.needsUpdate = true;
    }
    const part = (parts || []).find(p => p.id === partId)
      || (AppState.creatureParts || []).find(p => p.id === partId);
    if (part && part.texture && part.texture.canvas) {
      syncPreviewCanvas(editCanvasRef.current, part.texture.canvas);
    }
  };

  React.useEffect(() => {
    const src = activePart && activePart.texture && activePart.texture.canvas;
    const id = requestAnimationFrame(() => syncPreviewCanvas(editCanvasRef.current, src));
    return () => cancelAnimationFrame(id);
  }, [activePartId, drawerMode, activePart]);

  // ---- 範本載入 ----
  const loadTemplate = (variant) => {
    pushUndo();
    const { parts: newParts, animations: newAnims } = createHumanoidTemplate(project.width, variant);

    // 清理舊網格
    Object.values(meshesRef.current).forEach(ref => {
      ref.mesh.geometry.dispose();
      ref.material.dispose();
      ref.texture.dispose();
      if (ref.group.parent) ref.group.parent.remove(ref.group);
    });
    meshesRef.current = {};

    // 重建
    newParts.forEach(part => {
      createPartMesh(part, rootGroupRef.current);
    });

    setParts(newParts);
    setAnimations(newAnims);
    setActivePartId(newParts[0].id);
    setCurrentAnim('Idle');
    setIsPlaying(false);
    setCurrentFrame(0);
    const labels = {
      human: '人類', zombie: '殭屍', skeleton: '骷髏', villager: '村民',
      animal: '動物', monster: '怪物', dragon: '龍',
    };
    showToast(`已載入 ${labels[variant] || variant} 範本`, 'success');
  };

  // ---- AI 生成基礎模型 ----
  const generateFromReference = (creatureType) => {
    // 根據生物類型載入對應範本
    const typeMap = {
      human: 'human',
      animal: 'animal',
      monster: 'monster',
      dragon: 'dragon',
    };
    const template = typeMap[creatureType] || 'human';
    loadTemplate(template);

    // 如果有上傳參考圖，根據圖片比例調整模型尺寸
    if (referenceImage) {
      const img = new Image();
      img.onload = () => {
        const aspect = img.width / img.height;
        // 根據參考圖的長寬比調整模型的 Y 軸縮放
        // 簡單的比例調整：如果圖片比較高，模型比較高
        const scaleFactor = Math.max(0.6, Math.min(1.5, aspect * 1.2));
        // 這裡我們不做太複雜的調整，保留模板的預設尺寸
        // 使用者可以自己對照背景調整
        showToast('已根據參考圖比例生成基礎模型', 'success');
      };
      img.src = referenceImage;
    }
  };

  // ---- 上傳參考圖 ----
  const handleReferenceUpload = (file) => {
    const reader = new FileReader();
    reader.onload = (e) => {
      setReferenceImage(e.target.result);
      setShowReference(true);
      updateReferenceSprite(e.target.result, referenceOpacity, referenceView);
      showToast('參考圖已載入', 'success');
    };
    reader.readAsDataURL(file);
  };

  // ---- 匯出 ----
  const exportTexturePNG = () => {
    if (!activePart) return;
    canvasToBlob(activePart.texture.canvas).then(blob => {
      downloadBlob(blob, `${activePart.name}.png`);
      showToast('PNG 已匯出', 'success');
    });
  };

  const exportModelJSON = () => {
    const modelData = {
      format_version: '1.16.0',
      'minecraft:geometry': [{
        description: {
          identifier: `geometry.${(project?.name || 'creature').toLowerCase().replace(/\s+/g, '_')}`,
          texture_width: project?.width || 16,
          texture_height: project?.height || 16,
        },
        bones: parts.map(p => ({
          name: p.name.toLowerCase().replace(/\s+/g, '_'),
          pivot: [p.pivot.x, p.pivot.y, p.pivot.z],
          cubes: [{
            origin: [p.pos.x - p.size.w / 2, p.pos.y - p.size.h / 2, p.pos.z - p.size.d / 2],
            size: [p.size.w, p.size.h, p.size.d],
            uv: [0, 0],
          }],
        })),
      }],
    };
    const blob = new Blob([JSON.stringify(modelData, null, 2)], { type: 'application/json' });
    downloadBlob(blob, `${project?.name || 'model'}.geo.json`);
    showToast('模型 JSON 已匯出', 'success');
  };

  const exportCreaturePack = async () => {
    if (typeof JSZip === 'undefined') {
      showToast('JSZip 未載入，請稍後重試', 'error');
      return;
    }
    showToast('正在打包生物資源包...', 'info');
    try {
      const zip = new JSZip();
      const name = (project?.name || 'creature_pack').replace(/\s+/g, '_').toLowerCase();

      zip.file('pack.mcmeta', JSON.stringify({
        pack: { pack_format: packFormatFor(project?.mcVersion), description: `${project?.name || 'Creature'} · MC ${project?.mcVersion || '26.2'} · MC Studio` }
      }, null, 2));

      const addedTex = new Set();
      for (const part of parts) {
        const texName = part.texture.name;
        if (addedTex.has(texName)) continue;
        addedTex.add(texName);
        const blob = await canvasToBlob(part.texture.canvas);
        zip.file(`assets/minecraft/textures/entity/${texName}.png`, blob);
      }

      const modelData = {
        format_version: '1.16.0',
        'minecraft:geometry': [{
          description: {
            identifier: `geometry.${name}`,
            texture_width: project?.width || 16,
            texture_height: project?.height || 16,
          },
          bones: parts.map(p => ({
            name: p.name.toLowerCase().replace(/\s+/g, '_'),
            pivot: [p.pivot.x, p.pivot.y, p.pivot.z],
            cubes: [{
              origin: [p.pos.x - p.size.w / 2, p.pos.y - p.size.h / 2, p.pos.z - p.size.d / 2],
              size: [p.size.w, p.size.h, p.size.d],
              uv: [0, 0],
            }],
          })),
        }],
      };
      zip.file(`assets/minecraft/models/entity/${name}.json`, JSON.stringify(modelData, null, 2));

      const content = await zip.generateAsync({ type: 'blob' });
      downloadBlob(content, `${name}.zip`);
      showToast('生物資源包 ZIP 已匯出 ✓', 'success');
    } catch (e) {
      console.error(e);
      showToast('匯出失敗：' + e.message, 'error');
    }
  };

  // ---- 快捷鍵 ----
  React.useEffect(() => {
    const handler = (e) => {
      if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') return;
      const cmd = e.ctrlKey || e.metaKey;
      if (cmd && e.key === 'z' && !e.shiftKey) { e.preventDefault(); undo(); }
      else if (cmd && (e.key === 'Z' || (e.shiftKey && e.key === 'z'))) { e.preventDefault(); redo(); }
      else if (e.key === ' ') { e.preventDefault(); setIsPlaying(p => !p); }
    };
    window.addEventListener('keydown', handler);
    return () => window.removeEventListener('keydown', handler);
  });

  // 註冊全域函式
  React.useEffect(() => {
    window.triggerExport = exportTexturePNG;
    window.exportModelJSON = exportModelJSON;
    window.exportResourcePack = exportCreaturePack;
    window.loadCreatureTemplate = loadTemplate;
    window.resetView = resetCamera;
    window.triggerCanvasResize = () => {
      const container = containerRef.current;
      const renderer = rendererRef.current;
      const camera = cameraRef.current;
      if (!container || !renderer || !camera) return;
      const w = container.clientWidth;
      const h = container.clientHeight;
      if (w === 0 || h === 0) return;
      camera.aspect = w / h;
      camera.updateProjectionMatrix();
      renderer.setSize(w, h);
    };
    return () => {
      if (window.triggerExport === exportTexturePNG) window.triggerExport = null;
      if (window.exportModelJSON === exportModelJSON) window.exportModelJSON = null;
      if (window.exportResourcePack === exportCreaturePack) window.exportResourcePack = null;
      if (window.loadCreatureTemplate === loadTemplate) window.loadCreatureTemplate = null;
      window.resetView = null;
      window.triggerCanvasResize = null;
    };
  }, [exportTexturePNG, exportModelJSON, exportCreaturePack, loadTemplate]);

  const editDisplaySize = (activePart?.texture.width || 16) * zoom2d;
  const anim = animations[currentAnim];

  return React.createElement('div', { className: 'creature-view', style: { display: 'flex', width: '100%', height: '100%' } },
    // 左側：3D 視圖 + 時間軸
    React.createElement('div', { style: { flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0, position: 'relative' } },
      // 3D 視圖
      React.createElement('div', {
        className: 'creature-viewport',
        style: { flex: 1, position: 'relative', minHeight: 0 },
      },
        React.createElement('div', {
          ref: containerRef,
          style: { width: '100%', height: '100%' },
          onPointerDown: handlePointerDown,
          onPointerMove: handlePointerMove,
          onPointerUp: handlePointerUp,
          onPointerCancel: handlePointerCancel,
          onPointerLeave: handlePointerCancel,
          onWheel: handleWheel,
          onContextMenu: (e) => e.preventDefault(),
        }),
        // 視圖工具列
        React.createElement('div', { className: 'viewport-toolbar' },
          React.createElement('button', { className: 'view-btn', onClick: resetCamera, title: '重設相機' },
            React.createElement(SvgIcon, { name: 'Reset', size: 14 }),
          ),
          React.createElement('button', {
            className: 'view-btn' + (isPlaying ? ' active' : ''),
            onClick: () => setIsPlaying(p => !p),
            title: isPlaying ? '暫停 (空白鍵)' : '播放 (空白鍵)',
          },
            React.createElement(SvgIcon, { name: isPlaying ? 'Pause' : 'Play', size: 14 }),
          ),
        ),
        // 右側視圖切換
        React.createElement('div', { className: 'viewport-toolbar-right' },
          [['front', '正'], ['back', '背'], ['left', '左'], ['right', '右'], ['top', '頂'], ['bottom', '底']].map(([v, l]) =>
            React.createElement('button', {
              key: v,
              className: 'view-btn',
              onClick: () => setCameraView(v),
              title: ({
                front: '正面視圖', back: '背面視圖',
                left: '左側視圖', right: '右側視圖',
                top: '頂部視圖', bottom: '底部視圖',
              })[v],
            }, l)
          ),
        ),
        // 部件資訊覆蓋層
        activePart && React.createElement('div', {
          style: {
            position: 'absolute',
            bottom: '10px',
            left: '10px',
            padding: '6px 10px',
            background: 'var(--bg-surface)',
            border: '1px solid var(--border)',
            borderRadius: 'var(--radius-md)',
            fontSize: '11px',
            fontFamily: 'var(--font-mono)',
            color: 'var(--text-secondary)',
            zIndex: 10,
          }
        },
          React.createElement('div', { style: { fontWeight: 600, color: 'var(--text-primary)' } }, activePart.name),
          React.createElement('div', null, `位置: ${activePart.pos.x.toFixed(1)}, ${activePart.pos.y.toFixed(1)}, ${activePart.pos.z.toFixed(1)}`),
          React.createElement('div', null, `旋轉: ${activePart.rot.x.toFixed(0)}°, ${activePart.rot.y.toFixed(0)}°, ${activePart.rot.z.toFixed(0)}°`),
          React.createElement('div', null, `尺寸: ${activePart.size.w}×${activePart.size.h}×${activePart.size.d}`),
        ),
      ),

      // 底部抽屜：時間軸 / 材質編輯
      React.createElement('div', { className: 'creature-timeline', style: { flexShrink: 0 } },
        React.createElement('div', { className: 'timeline-toolbar' },
          // 分頁切換
          React.createElement('div', { style: { display: 'flex', gap: '4px', marginRight: '12px' } },
            React.createElement('button', {
              className: 'mc-helper-btn' + (drawerMode === 'timeline' ? ' active' : ''),
              onClick: () => setDrawerMode('timeline'),
            }, '動畫時間軸'),
            React.createElement('button', {
              className: 'mc-helper-btn' + (drawerMode === 'texture' ? ' active' : ''),
              onClick: () => setDrawerMode('texture'),
            }, '材質編輯'),
          ),

          drawerMode === 'timeline' && anim && React.createElement(React.Fragment, null,
            React.createElement('select', {
              className: 'anim-select',
              value: currentAnim,
              onChange: (e) => { setCurrentAnim(e.target.value); setCurrentFrame(0); setIsPlaying(false); },
            },
              Object.keys(animations).map(name =>
                React.createElement('option', { key: name, value: name }, ({
                  Idle: '閒置', Walk: '行走', Run: '跑步', Attack: '攻擊', Hurt: '受傷', Death: '死亡',
                })[name] || name)
              ),
            ),
            React.createElement('button', {
              className: 'view-btn', style: { width: 24, height: 24 },
              onClick: () => setIsPlaying(p => !p), title: '播放/暫停'
            },
              React.createElement(SvgIcon, { name: isPlaying ? 'Pause' : 'Play', size: 12 }),
            ),
            React.createElement('button', {
              className: 'view-btn', style: { width: 24, height: 24 },
              onClick: () => { setCurrentFrame(anim.startFrame || 0); applyAnimationFrame(anim.startFrame || 0); },
              title: '回到起點'
            }, '⏮'),
            React.createElement('button', {
              className: 'view-btn', style: { width: 24, height: 24 },
              onClick: addKeyframe, title: '新增關鍵影格'
            },
              React.createElement(SvgIcon, { name: 'Plus', size: 12 }),
            ),
            React.createElement('button', {
              className: 'view-btn', style: { width: 24, height: 24 },
              onClick: deleteKeyframe, title: '刪除關鍵影格', disabled: !selectedKeyframe
            },
              React.createElement(SvgIcon, { name: 'Trash', size: 12 }),
            ),
            React.createElement('span', { style: { color: 'var(--text-tertiary)', fontFamily: 'var(--font-mono)', marginLeft: '8px' } },
              `影格: ${Math.round(currentFrame)} / ${anim.endFrame || 0}`
            ),
            React.createElement('span', { style: { color: 'var(--text-muted)', fontFamily: 'var(--font-mono)', marginLeft: '8px' } },
              `${anim.fps || 20} FPS`
            ),
          ),

          drawerMode === 'texture' && activePart && React.createElement(React.Fragment, null,
            React.createElement('span', { style: { color: 'var(--text-secondary)' } },
              '編輯: ',
              React.createElement('strong', { style: { color: 'var(--text-primary)' } }, activePart.name),
              ' 材質'
            ),
            React.createElement('div', { style: { display: 'flex', gap: '4px' } },
              ['pencil', 'eraser', 'fill'].map(t =>
                React.createElement('button', {
                  key: t,
                  className: 'mc-helper-btn' + (tool2d === t ? ' active' : ''),
                  onClick: () => setTool2d(t),
                  style: { fontSize: '10px', padding: '3px 6px' },
                }, t === 'pencil' ? '鉛筆' : t === 'eraser' ? '橡皮擦' : '油漆桶')
              ),
            ),
            React.createElement('button', { className: 'btn btn--primary btn--sm', onClick: exportTexturePNG },
              React.createElement(SvgIcon, { name: 'Download', size: 10 }),
              ' 匯出 PNG',
            ),
          ),
        ),

        // 時間軸軌道 / 材質編輯區
        drawerMode === 'timeline' && anim && React.createElement(React.Fragment, null,
          React.createElement('div', { className: 'timeline-ruler' },
            React.createElement('div', {
              style: {
                position: 'absolute',
                top: 0, bottom: 0, left: 120,
                right: 0,
                backgroundImage: `repeating-linear-gradient(to right, var(--border-subtle) 0, var(--border-subtle) 1px, transparent 1px, transparent ${100 / (anim.endFrame - anim.startFrame + 1)}%)`,
              },
            }),
            React.createElement('div', {
              className: 'playhead',
              style: { left: `calc(120px + ${(currentFrame / (anim.endFrame - anim.startFrame + 1)) * 100}%)` },
            }),
          ),
          React.createElement('div', { className: 'timeline-tracks' },
            parts.map(part => {
              const partKfs = anim.keyframes[part.id] || [];
              return React.createElement('div', {
                key: part.id,
                className: 'timeline-track',
                onClick: () => setActivePartId(part.id),
                style: {
                  background: activePartId === part.id ? 'var(--bg-active)' : 'transparent',
                }
              },
                React.createElement('div', { className: 'timeline-track__name' }, part.name),
                React.createElement('div', { className: 'timeline-track__keyframes' },
                  partKfs.map((kf, i) => {
                    const pct = ((kf.frame - anim.startFrame) / (anim.endFrame - anim.startFrame + 1)) * 100;
                    const isSel = selectedKeyframe?.partId === part.id && selectedKeyframe?.frame === kf.frame;
                    return React.createElement('div', {
                      key: i,
                      className: 'keyframe-dot' + (isSel ? ' selected' : ''),
                      style: { left: `${pct}%` },
                      onClick: (e) => {
                        e.stopPropagation();
                        setSelectedKeyframe({ partId: part.id, frame: kf.frame });
                        setCurrentFrame(kf.frame);
                        applyAnimationFrame(kf.frame);
                      },
                    });
                  }),
                ),
              );
            }),
          ),
        ),

        drawerMode === 'texture' && activePart && React.createElement('div', {
          style: {
            flex: 1,
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            backgroundImage: `
              linear-gradient(45deg, #2a2a2a 25%, transparent 25%),
              linear-gradient(-45deg, #2a2a2a 25%, transparent 25%),
              linear-gradient(45deg, transparent 75%, #2a2a2a 75%),
              linear-gradient(-45deg, transparent 75%, #2a2a2a 75%)
            `,
            backgroundSize: '16px 16px',
            backgroundPosition: '0 0, 0 8px, 8px -8px, -8px 0px',
            backgroundColor: '#1e1e1e',
            overflow: 'auto',
            padding: '10px',
            touchAction: 'none',
          },
          onPointerDown: texPointerDown,
          onPointerMove: texPointerMove,
          onPointerUp: texPointerUp,
          onPointerCancel: texPointerUp,
          onWheel: texWheel,
        },
          React.createElement('div', {
            style: {
              position: 'relative',
              width: editDisplaySize,
              height: editDisplaySize,
              cursor: tool2d === 'eyedropper' ? 'copy' : 'crosshair',
              transform: `translate(${offset2d.x}px, ${offset2d.y}px)`,
            }
          },
            React.createElement('canvas', {
              ref: editCanvasRef,
              style: {
                width: editDisplaySize,
                height: editDisplaySize,
                imageRendering: 'pixelated',
                position: 'absolute',
                top: 0, left: 0,
              },
            }),
            React.createElement('canvas', {
              ref: editOverlayRef,
              style: {
                width: editDisplaySize,
                height: editDisplaySize,
                imageRendering: 'pixelated',
                position: 'absolute',
                top: 0, left: 0,
                pointerEvents: 'none',
              },
            }),
            showGrid && zoom2d >= 4 && React.createElement('div', {
              style: {
                position: 'absolute',
                inset: 0,
                pointerEvents: 'none',
                backgroundImage: `
                  linear-gradient(to right, rgba(255,255,255,0.08) 1px, transparent 1px),
                  linear-gradient(to bottom, rgba(255,255,255,0.08) 1px, transparent 1px)
                `,
                backgroundSize: `${zoom2d}px ${zoom2d}px`,
              },
            }),
          ),
        ),
      ),
    ),

    // 右側面板切換按鈕
    rightPanelCollapsed && React.createElement('button', {
      className: 'panel-toggle-btn right-panel-toggle',
      onClick: onToggleRightPanel,
      title: '展開右側面板',
    }, '◀'),

    // 右側面板：階層 + 屬性 + 範本 + AI 參考
    !rightPanelCollapsed && React.createElement('div', { className: 'right-panel' },
      React.createElement(HierarchyPanel, {
        parts, activePartId, setActivePartId,
        onAddPart: addPart,
        onDeletePart: deletePart,
        onDuplicatePart: duplicatePart,
      }),
      activePart && React.createElement(PropertiesPanel, {
        part: activePart,
        onChange: (c) => updatePart(activePart.id, c),
      }),
      React.createElement(TemplatePanel, { onApply: loadTemplate }),
      React.createElement(ReferencePanel, {
        showReference, setShowReference,
        referenceImage, setReferenceImage,
        referenceOpacity, setReferenceOpacity,
        referenceView, setReferenceView,
        onUpload: handleReferenceUpload,
        onGenerate: generateFromReference,
      }),
      React.createElement(MCToolsPanel, { view: 'creature', project, onExport: exportTexturePNG }),
    ),
  );
}

// ============================================================
// 模型階層面板
// ============================================================
function HierarchyPanel({ parts, activePartId, setActivePartId, onAddPart, onDeletePart, onDuplicatePart }) {
  const [expanded, setExpanded] = React.useState({});
  const [showAddMenu, setShowAddMenu] = React.useState(false);

  const toggle = (id) => setExpanded(prev => ({ ...prev, [id]: !prev[id] }));
  const getChildren = (parentId) => parts.filter(p => p.parent === parentId);

  const partTypes = [
    { id: 'head', label: '頭部' },
    { id: 'body', label: '身體' },
    { id: 'arm', label: '手臂' },
    { id: 'leg', label: '腿部' },
    { id: 'tail', label: '尾巴' },
    { id: 'wing', label: '翅膀' },
    { id: 'horn', label: '角' },
    { id: 'ear', label: '耳朵' },
    { id: 'eye', label: '眼睛' },
    { id: 'mouth', label: '嘴巴' },
    { id: 'custom', label: '自訂方塊' },
  ];

  const renderNode = (part, depth) => {
    const children = getChildren(part.id);
    const hasChildren = children.length > 0;
    const isExpanded = expanded[part.id] !== false;

    return React.createElement('div', { key: part.id },
      React.createElement('div', {
        className: 'hierarchy-node' + (part.id === activePartId ? ' selected' : ''),
        style: { paddingLeft: `${depth * 12 + 4}px` },
        onClick: () => setActivePartId(part.id),
      },
        hasChildren && React.createElement('span', {
          className: 'hierarchy-node__toggle',
          onClick: (e) => { e.stopPropagation(); toggle(part.id); },
          style: { transform: isExpanded ? 'rotate(90deg)' : 'none' },
        }, '▶'),
        !hasChildren && React.createElement('span', { className: 'hierarchy-node__toggle' }, ''),
        React.createElement('span', { className: 'hierarchy-node__icon' },
          React.createElement(SvgIcon, { name: 'Cube', size: 12 }),
        ),
        React.createElement('span', { className: 'hierarchy-node__name' }, part.name),
      ),
      hasChildren && isExpanded && React.createElement('div', null,
        children.map(c => renderNode(c, depth + 1))
      ),
    );
  };

  const roots = getChildren(null);

  return React.createElement('div', { className: 'panel-section', style: { maxHeight: '260px', display: 'flex', flexDirection: 'column', minHeight: 0 } },
    React.createElement('div', { className: 'panel-section__header' },
      React.createElement('span', null, `模型階層 (${parts.length})`),
      React.createElement('div', { style: { display: 'flex', gap: '4px' } },
        React.createElement('button', {
          style: { position: 'relative' },
          className: 'layer-icon-btn',
          onClick: (e) => { e.stopPropagation(); setShowAddMenu(v => !v); },
          title: '新增部件',
        },
          React.createElement(SvgIcon, { name: 'Plus', size: 12 }),
          showAddMenu && React.createElement('div', {
            style: {
              position: 'absolute',
              top: '24px',
              right: 0,
              background: 'var(--bg-surface)',
              border: '1px solid var(--border)',
              borderRadius: 'var(--radius-sm)',
              padding: '4px',
              zIndex: 100,
              minWidth: '120px',
              boxShadow: 'var(--shadow-md)',
            },
            onClick: (e) => e.stopPropagation(),
          },
            partTypes.map(t =>
              React.createElement('div', {
                key: t.id,
                style: {
                  padding: '4px 8px',
                  fontSize: '11px',
                  color: 'var(--text-secondary)',
                  cursor: 'pointer',
                  borderRadius: 'var(--radius-sm)',
                },
                onMouseEnter: (e) => e.target.style.background = 'var(--bg-hover)',
                onMouseLeave: (e) => e.target.style.background = 'transparent',
                onClick: () => { onAddPart(t.id); setShowAddMenu(false); },
              }, t.label)
            ),
          ),
        ),
        React.createElement('button', {
          className: 'layer-icon-btn',
          onClick: () => onDuplicatePart(activePartId),
          title: '複製',
          disabled: !activePartId,
        }, React.createElement(SvgIcon, { name: 'Copy', size: 12 })),
        React.createElement('button', {
          className: 'layer-icon-btn',
          onClick: () => onDeletePart(activePartId),
          title: '刪除',
          disabled: !activePartId || parts.length <= 1,
        }, React.createElement(SvgIcon, { name: 'Trash', size: 12 })),
      ),
    ),
    React.createElement('div', {
      className: 'panel-section__body',
      style: { overflowY: 'auto', flex: 1, minHeight: 0, padding: '4px 6px' }
    },
      React.createElement('div', { className: 'hierarchy-tree' },
        roots.map(p => renderNode(p, 0)),
      ),
    ),
  );
}

// ============================================================
// 屬性面板
// ============================================================
function PropertiesPanel({ part, onChange }) {
  const updateProp = (group, axis, value) => {
    const v = parseFloat(value);
    if (isNaN(v)) return;
    onChange({ [group]: { ...part[group], [axis]: v } });
  };

  const updateSize = (axis, value) => {
    const v = parseFloat(value);
    if (isNaN(v) || v <= 0) return;
    onChange({ size: { ...part.size, [axis]: v } });
  };

  const propGroup = (title, group, keys = ['x', 'y', 'z']) =>
    React.createElement('div', { className: 'props-group', key: group },
      React.createElement('div', { className: 'props-group__title' }, title),
      React.createElement('div', { className: 'prop-row' },
        React.createElement('span', { className: 'prop-label' }, title.substring(0, 1).toLowerCase()),
        ...keys.map(k =>
          React.createElement('input', {
            key: k,
            className: 'prop-input',
            type: 'number',
            step: 0.5,
            value: part[group][k],
            onChange: (e) => updateProp(group, k, e.target.value),
          })
        ),
      ),
    );

  return React.createElement('div', { className: 'panel-section' },
    React.createElement('div', { className: 'panel-section__header' },
      React.createElement('span', null, '屬性'),
    ),
    React.createElement('div', { className: 'panel-section__body' },
      React.createElement('div', { className: 'props-form', style: { gap: '10px' } },
        React.createElement('div', { className: 'props-group' },
          React.createElement('div', { className: 'props-group__title' }, '名稱'),
          React.createElement('input', {
            className: 'form-input',
            value: part.name,
            onChange: (e) => onChange({ name: e.target.value }),
          }),
        ),
        propGroup('位置', 'pos'),
        propGroup('旋轉', 'rot'),
        propGroup('縮放', 'scale'),
        React.createElement('div', { className: 'props-group' },
          React.createElement('div', { className: 'props-group__title' }, '尺寸'),
          React.createElement('div', { className: 'prop-row' },
            React.createElement('span', { className: 'prop-label' }, 'size'),
            React.createElement('input', {
              className: 'prop-input',
              type: 'number',
              min: 1, step: 1,
              value: part.size.w,
              onChange: (e) => updateSize('w', e.target.value),
            }),
            React.createElement('input', {
              className: 'prop-input',
              type: 'number',
              min: 1, step: 1,
              value: part.size.h,
              onChange: (e) => updateSize('h', e.target.value),
            }),
            React.createElement('input', {
              className: 'prop-input',
              type: 'number',
              min: 1, step: 1,
              value: part.size.d,
              onChange: (e) => updateSize('d', e.target.value),
            }),
          ),
        ),
        React.createElement('div', { className: 'props-group' },
          React.createElement('div', { className: 'props-group__title' }, '樞紐點'),
          React.createElement('div', { className: 'prop-row' },
            React.createElement('span', { className: 'prop-label' }, 'pivot'),
            React.createElement('input', {
              className: 'prop-input', type: 'number', step: 0.5,
              value: part.pivot.x, onChange: (e) => updateProp('pivot', 'x', e.target.value),
            }),
            React.createElement('input', {
              className: 'prop-input', type: 'number', step: 0.5,
              value: part.pivot.y, onChange: (e) => updateProp('pivot', 'y', e.target.value),
            }),
            React.createElement('input', {
              className: 'prop-input', type: 'number', step: 0.5,
              value: part.pivot.z, onChange: (e) => updateProp('pivot', 'z', e.target.value),
            }),
          ),
          React.createElement('button', {
            className: 'mc-helper-btn',
            style: { marginTop: '4px', fontSize: '10px' },
            onClick: () => onChange({ pivot: { x: 0, y: 0, z: 0 } }),
          }, '重設樞紐點至中心'),
        ),
        React.createElement('div', { className: 'toggle-row' },
          React.createElement('span', { className: 'toggle-label' }, '顯示'),
          React.createElement('div', {
            className: 'toggle-switch' + (part.visible ? ' on' : ''),
            onClick: () => onChange({ visible: !part.visible }),
          }),
        ),
      ),
    ),
  );
}

// ============================================================
// 範本面板
// ============================================================
function TemplatePanel({ onApply }) {
  const [isOpen, setIsOpen] = React.useState(true);
  const templates = [
    { key: 'human', label: '人類', color: '#E8B894' },
    { key: 'zombie', label: '殭屍', color: '#5A7A3A' },
    { key: 'skeleton', label: '骷髏', color: '#D8D8C0' },
    { key: 'villager', label: '村民', color: '#7A3A3A' },
    { key: 'animal', label: '動物', color: '#8B5A2B' },
    { key: 'monster', label: '怪物', color: '#6B2A6B' },
    { key: 'dragon', label: '龍', color: '#228B22' },
  ];

  return React.createElement('div', { className: 'panel-section' },
    React.createElement('div', {
      className: 'panel-section__header',
      style: { cursor: 'pointer' },
      onClick: () => setIsOpen(v => !v),
    },
      React.createElement('span', null, '生物範本'),
      React.createElement('span', { style: { fontSize: '9px', color: 'var(--text-muted)' } }, isOpen ? '−' : '+'),
    ),
    isOpen && React.createElement('div', { className: 'panel-section__body' },
      React.createElement('div', {
        style: { display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '4px' }
      },
        templates.map(t =>
          React.createElement('button', {
            key: t.key,
            className: 'mc-helper-btn',
            style: {
              display: 'flex', alignItems: 'center', gap: '6px',
              padding: '4px 6px', fontSize: '10px',
            },
            onClick: () => onApply && onApply(t.key),
          },
            React.createElement('div', {
              style: {
                width: 10, height: 10, borderRadius: 2,
                background: t.color, border: '1px solid rgba(0,0,0,0.3)',
              },
            }),
            t.label,
          )
        ),
      ),
    ),
  );
}

// ============================================================
// AI 參考圖面板
// ============================================================
function ReferencePanel({
  showReference, setShowReference,
  referenceImage, setReferenceImage,
  referenceOpacity, setReferenceOpacity,
  referenceView, setReferenceView,
  onUpload, onGenerate,
}) {
  const [isOpen, setIsOpen] = React.useState(false);
  const [creatureType, setCreatureType] = React.useState('human');
  const fileInputRef = React.useRef(null);

  const handleUpload = (e) => {
    const file = e.target.files?.[0];
    if (file) {
      onUpload?.(file);
    }
    e.target.value = '';
  };

  const generateFromReference = () => {
    if (!referenceImage) {
      showToast('請先上傳參考圖', 'warning');
      return;
    }
    showToast('AI 分析參考圖並生成基礎模型...', 'info');
    setTimeout(() => {
      onGenerate?.(creatureType);
    }, 300);
  };

  const typeOptions = [
    { key: 'human', label: '人物' },
    { key: 'animal', label: '動物' },
    { key: 'monster', label: '怪物' },
    { key: 'dragon', label: '龍' },
  ];

  return React.createElement('div', { className: 'panel-section' },
    React.createElement('div', {
      className: 'panel-section__header',
      style: { cursor: 'pointer' },
      onClick: () => setIsOpen(v => !v),
    },
      React.createElement('span', null,
        React.createElement(SvgIcon, { name: 'Sparkles', size: 10, style: { marginRight: 4, color: 'var(--accent-purple)' } }),
        'AI 參考模式',
      ),
      React.createElement('span', { style: { fontSize: '9px', color: 'var(--text-muted)' } }, isOpen ? '−' : '+'),
    ),
    isOpen && React.createElement('div', {
      className: 'panel-section__body',
      style: { gap: '8px', display: 'flex', flexDirection: 'column' }
    },
      // 上傳按鈕
      React.createElement('input', {
        ref: fileInputRef,
        type: 'file', accept: 'image/*',
        style: { display: 'none' },
        onChange: handleUpload,
      }),
      React.createElement('button', {
        className: 'btn btn--secondary',
        style: { width: '100%', justifyContent: 'center' },
        onClick: () => fileInputRef.current?.click(),
      },
        React.createElement(SvgIcon, { name: 'Image', size: 12 }),
        referenceImage ? '更換參考圖' : '上傳參考圖',
      ),
      // 參考圖預覽縮圖
      referenceImage && React.createElement('div', {
        style: {
          width: '100%', aspectRatio: '1', background: 'var(--bg-base)',
          border: '1px solid var(--border)', borderRadius: 'var(--radius-sm)',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          overflow: 'hidden',
        },
      },
        React.createElement('img', {
          src: referenceImage,
          style: { maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' },
        }),
      ),
      // 顯示切換
      React.createElement('div', { className: 'toggle-row' },
        React.createElement('span', { className: 'toggle-label' }, '顯示參考圖'),
        React.createElement('div', {
          className: 'toggle-switch' + (showReference ? ' on' : ''),
          onClick: () => setShowReference(v => !v),
        }),
      ),
      // 視圖選擇
      React.createElement('div', { style: { display: 'flex', gap: '4px' } },
        ['front', 'side'].map(v =>
          React.createElement('button', {
            key: v,
            className: 'mc-helper-btn' + (referenceView === v ? ' active' : ''),
            style: { flex: 1 },
            onClick: () => setReferenceView(v),
          }, v === 'front' ? '前視圖' : '側視圖')
        ),
      ),
      // 透明度滑桿
      React.createElement('div', { className: 'slider-row' },
        React.createElement('span', { style: { fontSize: '10px', color: 'var(--text-tertiary)', width: '48px' } }, '透明度'),
        React.createElement('input', {
          type: 'range', min: 10, max: 100, value: referenceOpacity,
          onChange: (e) => setReferenceOpacity(parseInt(e.target.value)),
        }),
        React.createElement('span', { className: 'slider-value' }, `${referenceOpacity}%`),
      ),
      // 生物類型選擇
      React.createElement('div', null,
        React.createElement('div', { style: { fontSize: '10px', color: 'var(--text-tertiary)', marginBottom: '4px' } }, '生物類型'),
        React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '4px' } },
          typeOptions.map(t =>
            React.createElement('button', {
              key: t.key,
              className: 'mc-helper-btn' + (creatureType === t.key ? ' active' : ''),
              style: { fontSize: '10px', padding: '3px 6px' },
              onClick: () => setCreatureType(t.key),
            }, t.label)
          ),
        ),
      ),
      // AI 生成按鈕
      React.createElement('button', {
        className: 'btn btn--primary',
        style: { width: '100%', justifyContent: 'center' },
        onClick: generateFromReference,
      },
        React.createElement(SvgIcon, { name: 'Sparkles', size: 12 }),
        ' 一鍵生成基礎模型',
      ),
      React.createElement('div', {
        style: { fontSize: '9px', color: 'var(--text-muted)', lineHeight: 1.4 },
      }, '根據參考圖與生物類型，自動建立對應比例的 Cube 部件作為建模起點。'),
    ),
  );
}

// ============================================================
// 人形 / 生物範本建立器
// ============================================================
function createHumanoidTemplate(textureSize, variant) {
  const ts = textureSize || 64;

  const makePart = (id, name, type, size, pos, pivot, parent = null, textureOffset = { u: 0, v: 0 }) => {
    const canvas = createPixelCanvas(ts, ts);
    // 填充基本顏色（依類型不同）
    const ctx = canvas.getContext('2d');
    const baseColor = getVariantBaseColor(variant, type);
    ctx.fillStyle = baseColor;
    ctx.fillRect(0, 0, ts, ts);
    // 加一些簡單的細節
    ctx.fillStyle = getVariantShadeColor(variant, type);
    // 簡單的邊緣陰影
    for (let i = 0; i < ts; i++) {
      ctx.fillRect(i, ts - 1, 1, 1);
      ctx.fillRect(ts - 1, i, 1, 1);
    }
    return {
      id, name, type, parent,
      pos: { x: pos[0], y: pos[1], z: pos[2] },
      rot: { x: 0, y: 0, z: 0 },
      scale: { x: 1, y: 1, z: 1 },
      pivot: { x: pivot[0], y: pivot[1], z: pivot[2] },
      size: { w: size[0], h: size[1], d: size[2] },
      texture: { canvas, name, width: ts, height: ts, offset: textureOffset },
      visible: true,
    };
  };

  let parts = [];
  let animations = {};

  if (variant === 'human' || variant === 'zombie' || variant === 'skeleton' || variant === 'villager') {
    // 人形模板
    const skinColor = variant === 'zombie' ? '#5A7A3A'
      : variant === 'skeleton' ? '#D8D8C0'
        : variant === 'villager' ? '#B08050'
          : '#E8B894';
    const shirtColor = variant === 'zombie' ? '#4A6830'
      : variant === 'skeleton' ? '#C0C0A8'
        : variant === 'villager' ? '#7A3A3A'
          : '#3A6EB5';
    const pantsColor = variant === 'zombie' ? '#3A5A28'
      : variant === 'skeleton' ? '#B0B098'
        : variant === 'villager' ? '#5A2A2A'
          : '#2A4A8A';

    // 頭部
    parts.push(makePart('head', '頭部', 'head', [8, 8, 8], [0, 16, 0], [0, 12, 0], null));
    // 身體
    parts.push(makePart('body', '身體', 'body', [8, 12, 4], [0, 6, 0], [0, 12, 0], null));
    // 左臂
    parts.push(makePart('leftArm', '左臂', 'arm', [4, 12, 4], [-6, 6, 0], [-6, 12, 0], null));
    // 右臂
    parts.push(makePart('rightArm', '右臂', 'arm', [4, 12, 4], [6, 6, 0], [6, 12, 0], null));
    // 左腿
    parts.push(makePart('leftLeg', '左腿', 'leg', [4, 12, 4], [-2, -6, 0], [-2, 0, 0], null));
    // 右腿
    parts.push(makePart('rightLeg', '右腿', 'leg', [4, 12, 4], [2, -6, 0], [2, 0, 0], null));

    animations = {
      Idle: {
        startFrame: 0, endFrame: 20, fps: 20,
        keyframes: {
          leftArm: [{ frame: 0, rot: { x: 0, y: 0, z: 5 } }, { frame: 10, rot: { x: 0, y: 0, z: -5 } }, { frame: 20, rot: { x: 0, y: 0, z: 5 } }],
          rightArm: [{ frame: 0, rot: { x: 0, y: 0, z: -5 } }, { frame: 10, rot: { x: 0, y: 0, z: 5 } }, { frame: 20, rot: { x: 0, y: 0, z: -5 } }],
          head: [{ frame: 0, rot: { x: 0, y: 0, z: 0 } }, { frame: 20, rot: { x: 0, y: 0, z: 0 } }],
        },
      },
      Walk: {
        startFrame: 0, endFrame: 16, fps: 20,
        keyframes: {
          leftLeg: [{ frame: 0, rot: { x: -20, y: 0, z: 0 } }, { frame: 8, rot: { x: 20, y: 0, z: 0 } }, { frame: 16, rot: { x: -20, y: 0, z: 0 } }],
          rightLeg: [{ frame: 0, rot: { x: 20, y: 0, z: 0 } }, { frame: 8, rot: { x: -20, y: 0, z: 0 } }, { frame: 16, rot: { x: 20, y: 0, z: 0 } }],
          leftArm: [{ frame: 0, rot: { x: 20, y: 0, z: 0 } }, { frame: 8, rot: { x: -20, y: 0, z: 0 } }, { frame: 16, rot: { x: 20, y: 0, z: 0 } }],
          rightArm: [{ frame: 0, rot: { x: -20, y: 0, z: 0 } }, { frame: 8, rot: { x: 20, y: 0, z: 0 } }, { frame: 16, rot: { x: -20, y: 0, z: 0 } }],
        },
      },
      Run: {
        startFrame: 0, endFrame: 12, fps: 30,
        keyframes: {
          leftLeg: [{ frame: 0, rot: { x: -40, y: 0, z: 0 } }, { frame: 6, rot: { x: 40, y: 0, z: 0 } }, { frame: 12, rot: { x: -40, y: 0, z: 0 } }],
          rightLeg: [{ frame: 0, rot: { x: 40, y: 0, z: 0 } }, { frame: 6, rot: { x: -40, y: 0, z: 0 } }, { frame: 12, rot: { x: 40, y: 0, z: 0 } }],
          leftArm: [{ frame: 0, rot: { x: 40, y: 0, z: 0 } }, { frame: 6, rot: { x: -40, y: 0, z: 0 } }, { frame: 12, rot: { x: 40, y: 0, z: 0 } }],
          rightArm: [{ frame: 0, rot: { x: -40, y: 0, z: 0 } }, { frame: 6, rot: { x: 40, y: 0, z: 0 } }, { frame: 12, rot: { x: -40, y: 0, z: 0 } }],
        },
      },
      Attack: {
        startFrame: 0, endFrame: 10, fps: 20,
        keyframes: {
          rightArm: [{ frame: 0, rot: { x: 0, y: 0, z: -30 } }, { frame: 5, rot: { x: -90, y: 0, z: -30 } }, { frame: 10, rot: { x: 0, y: 0, z: -30 } }],
        },
      },
      Hurt: {
        startFrame: 0, endFrame: 8, fps: 20,
        keyframes: {
          body: [{ frame: 0, rot: { x: 0, y: 0, z: 10 } }, { frame: 4, rot: { x: 0, y: 0, z: -10 } }, { frame: 8, rot: { x: 0, y: 0, z: 0 } }],
          head: [{ frame: 0, rot: { x: 0, y: 0, z: -5 } }, { frame: 4, rot: { x: 0, y: 0, z: 5 } }, { frame: 8, rot: { x: 0, y: 0, z: 0 } }],
        },
      },
      Death: {
        startFrame: 0, endFrame: 30, fps: 20,
        keyframes: {
          body: [{ frame: 0, rot: { x: 0, y: 0, z: 0 } }, { frame: 30, rot: { x: -90, y: 0, z: 0 } }],
          head: [{ frame: 0, rot: { x: 0, y: 0, z: 0 } }, { frame: 30, rot: { x: -90, y: 0, z: 0 } }],
        },
      },
    };
  } else if (variant === 'animal') {
    // 動物模板（四足）
    parts.push(makePart('head', '頭部', 'head', [6, 6, 8], [0, 12, 8], [0, 9, 6], null));
    parts.push(makePart('body', '身體', 'body', [10, 8, 14], [0, 10, 0], [0, 6, 0], null));
    parts.push(makePart('frontLeftLeg', '左前腿', 'leg', [3, 8, 3], [-3, 2, 5], [-3, 6, 5], null));
    parts.push(makePart('frontRightLeg', '右前腿', 'leg', [3, 8, 3], [3, 2, 5], [3, 6, 5], null));
    parts.push(makePart('backLeftLeg', '左後腿', 'leg', [3, 8, 3], [-3, 2, -5], [-3, 6, -5], null));
    parts.push(makePart('backRightLeg', '右後腿', 'leg', [3, 8, 3], [3, 2, -5], [3, 6, -5], null));
    parts.push(makePart('tail', '尾巴', 'tail', [2, 8, 2], [0, 10, -8], [0, 10, -7], null));

    animations = {
      Idle: {
        startFrame: 0, endFrame: 20, fps: 20,
        keyframes: {
          tail: [{ frame: 0, rot: { x: 0, y: 15, z: 0 } }, { frame: 10, rot: { x: 0, y: -15, z: 0 } }, { frame: 20, rot: { x: 0, y: 15, z: 0 } }],
          head: [{ frame: 0, rot: { x: 0, y: 0, z: 0 } }, { frame: 10, rot: { x: 5, y: 0, z: 0 } }, { frame: 20, rot: { x: 0, y: 0, z: 0 } }],
        },
      },
      Walk: {
        startFrame: 0, endFrame: 16, fps: 20,
        keyframes: {
          frontLeftLeg: [{ frame: 0, rot: { x: -20, y: 0, z: 0 } }, { frame: 8, rot: { x: 20, y: 0, z: 0 } }, { frame: 16, rot: { x: -20, y: 0, z: 0 } }],
          frontRightLeg: [{ frame: 0, rot: { x: 20, y: 0, z: 0 } }, { frame: 8, rot: { x: -20, y: 0, z: 0 } }, { frame: 16, rot: { x: 20, y: 0, z: 0 } }],
          backLeftLeg: [{ frame: 0, rot: { x: 20, y: 0, z: 0 } }, { frame: 8, rot: { x: -20, y: 0, z: 0 } }, { frame: 16, rot: { x: 20, y: 0, z: 0 } }],
          backRightLeg: [{ frame: 0, rot: { x: -20, y: 0, z: 0 } }, { frame: 8, rot: { x: 20, y: 0, z: 0 } }, { frame: 16, rot: { x: -20, y: 0, z: 0 } }],
        },
      },
      Run: {
        startFrame: 0, endFrame: 12, fps: 30,
        keyframes: {
          frontLeftLeg: [{ frame: 0, rot: { x: -35, y: 0, z: 0 } }, { frame: 6, rot: { x: 35, y: 0, z: 0 } }, { frame: 12, rot: { x: -35, y: 0, z: 0 } }],
          frontRightLeg: [{ frame: 0, rot: { x: 35, y: 0, z: 0 } }, { frame: 6, rot: { x: -35, y: 0, z: 0 } }, { frame: 12, rot: { x: 35, y: 0, z: 0 } }],
          backLeftLeg: [{ frame: 0, rot: { x: 35, y: 0, z: 0 } }, { frame: 6, rot: { x: -35, y: 0, z: 0 } }, { frame: 12, rot: { x: 35, y: 0, z: 0 } }],
          backRightLeg: [{ frame: 0, rot: { x: -35, y: 0, z: 0 } }, { frame: 6, rot: { x: 35, y: 0, z: 0 } }, { frame: 12, rot: { x: -35, y: 0, z: 0 } }],
          tail: [{ frame: 0, rot: { x: 0, y: 25, z: 0 } }, { frame: 6, rot: { x: 0, y: -25, z: 0 } }, { frame: 12, rot: { x: 0, y: 25, z: 0 } }],
        },
      },
      Attack: {
        startFrame: 0, endFrame: 10, fps: 20,
        keyframes: {
          head: [{ frame: 0, rot: { x: 0, y: 0, z: 0 } }, { frame: 5, rot: { x: 30, y: 0, z: 0 } }, { frame: 10, rot: { x: 0, y: 0, z: 0 } }],
        },
      },
      Hurt: {
        startFrame: 0, endFrame: 8, fps: 20,
        keyframes: {
          body: [{ frame: 0, rot: { x: 0, y: 0, z: 5 } }, { frame: 4, rot: { x: 0, y: 0, z: -5 } }, { frame: 8, rot: { x: 0, y: 0, z: 0 } }],
        },
      },
      Death: {
        startFrame: 0, endFrame: 30, fps: 20,
        keyframes: {
          body: [{ frame: 0, rot: { x: 0, y: 0, z: 0 } }, { frame: 30, rot: { x: -90, y: 0, z: 0 } }],
        },
      },
    };
  } else if (variant === 'monster') {
    // 怪物模板（人形 + 角 + 翅膀）
    parts.push(makePart('head', '頭部', 'head', [10, 10, 10], [0, 18, 0], [0, 13, 0], null));
    parts.push(makePart('leftHorn', '左角', 'horn', [2, 4, 2], [-4, 24, 0], [-4, 22, 0], null));
    parts.push(makePart('rightHorn', '右角', 'horn', [2, 4, 2], [4, 24, 0], [4, 22, 0], null));
    parts.push(makePart('body', '身體', 'body', [10, 14, 6], [0, 6, 0], [0, 13, 0], null));
    parts.push(makePart('leftArm', '左臂', 'arm', [4, 14, 4], [-7, 6, 0], [-7, 13, 0], null));
    parts.push(makePart('rightArm', '右臂', 'arm', [4, 14, 4], [7, 6, 0], [7, 13, 0], null));
    parts.push(makePart('leftWing', '左翼', 'wing', [2, 8, 12], [-9, 8, 0], [-8, 12, 0], null));
    parts.push(makePart('rightWing', '右翼', 'wing', [2, 8, 12], [9, 8, 0], [8, 12, 0], null));
    parts.push(makePart('leftLeg', '左腿', 'leg', [4, 12, 4], [-3, -6, 0], [-3, 0, 0], null));
    parts.push(makePart('rightLeg', '右腿', 'leg', [4, 12, 4], [3, -6, 0], [3, 0, 0], null));

    animations = {
      Idle: {
        startFrame: 0, endFrame: 20, fps: 20,
        keyframes: {
          leftWing: [{ frame: 0, rot: { x: 0, y: 0, z: 10 } }, { frame: 10, rot: { x: 0, y: 0, z: -5 } }, { frame: 20, rot: { x: 0, y: 0, z: 10 } }],
          rightWing: [{ frame: 0, rot: { x: 0, y: 0, z: -10 } }, { frame: 10, rot: { x: 0, y: 0, z: 5 } }, { frame: 20, rot: { x: 0, y: 0, z: -10 } }],
        },
      },
      Walk: {
        startFrame: 0, endFrame: 16, fps: 20,
        keyframes: {
          leftLeg: [{ frame: 0, rot: { x: -20, y: 0, z: 0 } }, { frame: 8, rot: { x: 20, y: 0, z: 0 } }, { frame: 16, rot: { x: -20, y: 0, z: 0 } }],
          rightLeg: [{ frame: 0, rot: { x: 20, y: 0, z: 0 } }, { frame: 8, rot: { x: -20, y: 0, z: 0 } }, { frame: 16, rot: { x: 20, y: 0, z: 0 } }],
          leftArm: [{ frame: 0, rot: { x: 20, y: 0, z: 0 } }, { frame: 8, rot: { x: -20, y: 0, z: 0 } }, { frame: 16, rot: { x: 20, y: 0, z: 0 } }],
          rightArm: [{ frame: 0, rot: { x: -20, y: 0, z: 0 } }, { frame: 8, rot: { x: 20, y: 0, z: 0 } }, { frame: 16, rot: { x: -20, y: 0, z: 0 } }],
          leftWing: [{ frame: 0, rot: { x: 0, y: 0, z: 15 } }, { frame: 8, rot: { x: 0, y: 0, z: -10 } }, { frame: 16, rot: { x: 0, y: 0, z: 15 } }],
          rightWing: [{ frame: 0, rot: { x: 0, y: 0, z: -15 } }, { frame: 8, rot: { x: 0, y: 0, z: 10 } }, { frame: 16, rot: { x: 0, y: 0, z: -15 } }],
        },
      },
      Run: {
        startFrame: 0, endFrame: 12, fps: 30,
        keyframes: {
          leftLeg: [{ frame: 0, rot: { x: -40, y: 0, z: 0 } }, { frame: 6, rot: { x: 40, y: 0, z: 0 } }, { frame: 12, rot: { x: -40, y: 0, z: 0 } }],
          rightLeg: [{ frame: 0, rot: { x: 40, y: 0, z: 0 } }, { frame: 6, rot: { x: -40, y: 0, z: 0 } }, { frame: 12, rot: { x: 40, y: 0, z: 0 } }],
          leftWing: [{ frame: 0, rot: { x: 0, y: 0, z: 30 } }, { frame: 6, rot: { x: 0, y: 0, z: -20 } }, { frame: 12, rot: { x: 0, y: 0, z: 30 } }],
          rightWing: [{ frame: 0, rot: { x: 0, y: 0, z: -30 } }, { frame: 6, rot: { x: 0, y: 0, z: 20 } }, { frame: 12, rot: { x: 0, y: 0, z: -30 } }],
        },
      },
      Attack: {
        startFrame: 0, endFrame: 10, fps: 20,
        keyframes: {
          rightArm: [{ frame: 0, rot: { x: 0, y: 0, z: -30 } }, { frame: 5, rot: { x: -90, y: 0, z: -30 } }, { frame: 10, rot: { x: 0, y: 0, z: -30 } }],
          head: [{ frame: 0, rot: { x: 0, y: 0, z: 0 } }, { frame: 5, rot: { x: 15, y: 0, z: 0 } }, { frame: 10, rot: { x: 0, y: 0, z: 0 } }],
        },
      },
      Hurt: {
        startFrame: 0, endFrame: 8, fps: 20,
        keyframes: {
          body: [{ frame: 0, rot: { x: 0, y: 0, z: 10 } }, { frame: 4, rot: { x: 0, y: 0, z: -10 } }, { frame: 8, rot: { x: 0, y: 0, z: 0 } }],
        },
      },
      Death: {
        startFrame: 0, endFrame: 30, fps: 20,
        keyframes: {
          body: [{ frame: 0, rot: { x: 0, y: 0, z: 0 } }, { frame: 30, rot: { x: -90, y: 0, z: 0 } }],
        },
      },
    };
  } else if (variant === 'dragon') {
    // 龍模板
    parts.push(makePart('head', '頭部', 'head', [8, 8, 12], [0, 20, 18], [0, 16, 12], null));
    parts.push(makePart('leftHorn', '左角', 'horn', [2, 6, 3], [-3, 26, 24], [-3, 23, 22], null));
    parts.push(makePart('rightHorn', '右角', 'horn', [2, 6, 3], [3, 26, 24], [3, 23, 22], null));
    parts.push(makePart('neck', '頸部', 'body', [6, 8, 8], [0, 16, 10], [0, 12, 6], null));
    parts.push(makePart('body', '身體', 'body', [12, 10, 18], [0, 12, -2], [0, 7, 7], null));
    parts.push(makePart('tail1', '尾巴第一節', 'tail', [6, 6, 8], [0, 12, -12], [0, 9, -8], null));
    parts.push(makePart('tail2', '尾巴第二節', 'tail', [4, 4, 8], [0, 12, -18], [0, 10, -14], null));
    parts.push(makePart('tailTip', '尾尖', 'tail', [3, 3, 4], [0, 12, -23], [0, 10.5, -21], null));
    parts.push(makePart('leftWing', '左翼', 'wing', [3, 10, 20], [-10, 16, 0], [-8, 21, 0], null));
    parts.push(makePart('rightWing', '右翼', 'wing', [3, 10, 20], [10, 16, 0], [8, 21, 0], null));
    parts.push(makePart('frontLeftLeg', '左前爪', 'leg', [4, 8, 5], [-5, 4, 6], [-5, 8, 6], null));
    parts.push(makePart('frontRightLeg', '右前爪', 'leg', [4, 8, 5], [5, 4, 6], [5, 8, 6], null));
    parts.push(makePart('backLeftLeg', '左後爪', 'leg', [4, 8, 5], [-5, 4, -6], [-5, 8, -6], null));
    parts.push(makePart('backRightLeg', '右後爪', 'leg', [4, 8, 5], [5, 4, -6], [5, 8, -6], null));

    animations = {
      Idle: {
        startFrame: 0, endFrame: 30, fps: 20,
        keyframes: {
          leftWing: [{ frame: 0, rot: { x: 0, y: 0, z: 15 } }, { frame: 15, rot: { x: 0, y: 0, z: -10 } }, { frame: 30, rot: { x: 0, y: 0, z: 15 } }],
          rightWing: [{ frame: 0, rot: { x: 0, y: 0, z: -15 } }, { frame: 15, rot: { x: 0, y: 0, z: 10 } }, { frame: 30, rot: { x: 0, y: 0, z: -15 } }],
          tail1: [{ frame: 0, rot: { x: 0, y: 10, z: 0 } }, { frame: 15, rot: { x: 0, y: -10, z: 0 } }, { frame: 30, rot: { x: 0, y: 10, z: 0 } }],
          tail2: [{ frame: 0, rot: { x: 0, y: 15, z: 0 } }, { frame: 15, rot: { x: 0, y: -15, z: 0 } }, { frame: 30, rot: { x: 0, y: 15, z: 0 } }],
          head: [{ frame: 0, rot: { x: 5, y: 0, z: 0 } }, { frame: 15, rot: { x: -5, y: 0, z: 0 } }, { frame: 30, rot: { x: 5, y: 0, z: 0 } }],
        },
      },
      Walk: {
        startFrame: 0, endFrame: 20, fps: 20,
        keyframes: {
          frontLeftLeg: [{ frame: 0, rot: { x: -20, y: 0, z: 0 } }, { frame: 10, rot: { x: 20, y: 0, z: 0 } }, { frame: 20, rot: { x: -20, y: 0, z: 0 } }],
          frontRightLeg: [{ frame: 0, rot: { x: 20, y: 0, z: 0 } }, { frame: 10, rot: { x: -20, y: 0, z: 0 } }, { frame: 20, rot: { x: 20, y: 0, z: 0 } }],
          backLeftLeg: [{ frame: 0, rot: { x: 20, y: 0, z: 0 } }, { frame: 10, rot: { x: -20, y: 0, z: 0 } }, { frame: 20, rot: { x: 20, y: 0, z: 0 } }],
          backRightLeg: [{ frame: 0, rot: { x: -20, y: 0, z: 0 } }, { frame: 10, rot: { x: 20, y: 0, z: 0 } }, { frame: 20, rot: { x: -20, y: 0, z: 0 } }],
          leftWing: [{ frame: 0, rot: { x: 0, y: 0, z: 10 } }, { frame: 10, rot: { x: 0, y: 0, z: -5 } }, { frame: 20, rot: { x: 0, y: 0, z: 10 } }],
          rightWing: [{ frame: 0, rot: { x: 0, y: 0, z: -10 } }, { frame: 10, rot: { x: 0, y: 0, z: 5 } }, { frame: 20, rot: { x: 0, y: 0, z: -10 } }],
        },
      },
      Run: {
        startFrame: 0, endFrame: 16, fps: 30,
        keyframes: {
          frontLeftLeg: [{ frame: 0, rot: { x: -40, y: 0, z: 0 } }, { frame: 8, rot: { x: 40, y: 0, z: 0 } }, { frame: 16, rot: { x: -40, y: 0, z: 0 } }],
          frontRightLeg: [{ frame: 0, rot: { x: 40, y: 0, z: 0 } }, { frame: 8, rot: { x: -40, y: 0, z: 0 } }, { frame: 16, rot: { x: 40, y: 0, z: 0 } }],
          backLeftLeg: [{ frame: 0, rot: { x: 40, y: 0, z: 0 } }, { frame: 8, rot: { x: -40, y: 0, z: 0 } }, { frame: 16, rot: { x: 40, y: 0, z: 0 } }],
          backRightLeg: [{ frame: 0, rot: { x: -40, y: 0, z: 0 } }, { frame: 8, rot: { x: 40, y: 0, z: 0 } }, { frame: 16, rot: { x: -40, y: 0, z: 0 } }],
          leftWing: [{ frame: 0, rot: { x: 0, y: 0, z: 30 } }, { frame: 8, rot: { x: 0, y: 0, z: -20 } }, { frame: 16, rot: { x: 0, y: 0, z: 30 } }],
          rightWing: [{ frame: 0, rot: { x: 0, y: 0, z: -30 } }, { frame: 8, rot: { x: 0, y: 0, z: 20 } }, { frame: 16, rot: { x: 0, y: 0, z: -30 } }],
        },
      },
      Attack: {
        startFrame: 0, endFrame: 12, fps: 20,
        keyframes: {
          head: [{ frame: 0, rot: { x: 0, y: 0, z: 0 } }, { frame: 6, rot: { x: 30, y: 0, z: 0 } }, { frame: 12, rot: { x: 0, y: 0, z: 0 } }],
          neck: [{ frame: 0, rot: { x: 0, y: 0, z: 0 } }, { frame: 6, rot: { x: 20, y: 0, z: 0 } }, { frame: 12, rot: { x: 0, y: 0, z: 0 } }],
        },
      },
      Hurt: {
        startFrame: 0, endFrame: 8, fps: 20,
        keyframes: {
          body: [{ frame: 0, rot: { x: 0, y: 0, z: 8 } }, { frame: 4, rot: { x: 0, y: 0, z: -8 } }, { frame: 8, rot: { x: 0, y: 0, z: 0 } }],
        },
      },
      Death: {
        startFrame: 0, endFrame: 40, fps: 20,
        keyframes: {
          body: [{ frame: 0, rot: { x: 0, y: 0, z: 0 } }, { frame: 40, rot: { x: -90, y: 0, z: 0 } }],
          leftWing: [{ frame: 0, rot: { x: 0, y: 0, z: 15 } }, { frame: 40, rot: { x: 0, y: 0, z: 90 } }],
          rightWing: [{ frame: 0, rot: { x: 0, y: 0, z: -15 } }, { frame: 40, rot: { x: 0, y: 0, z: -90 } }],
        },
      },
    };
  }

  return { parts, animations };
}

// 取得變體的基本顏色
function getVariantBaseColor(variant, type) {
  const colors = {
    human: { head: '#E8B894', body: '#3A6EB5', arm: '#E8B894', leg: '#2A4A8A' },
    zombie: { head: '#5A7A3A', body: '#4A6830', arm: '#5A7A3A', leg: '#3A5A28' },
    skeleton: { head: '#D8D8C0', body: '#C0C0A8', arm: '#D8D8C0', leg: '#B0B098' },
    villager: { head: '#B08050', body: '#7A3A3A', arm: '#B08050', leg: '#5A2A2A' },
    animal: { head: '#8B5A2B', body: '#8B5A2B', leg: '#6B3A1B', tail: '#8B5A2B' },
    monster: { head: '#6B2A6B', body: '#4A1A4A', arm: '#6B2A6B', leg: '#3A0A3A', wing: '#2A0A2A', horn: '#5A1A5A' },
    dragon: {
      head: '#228B22', neck: '#1E6B1E', body: '#228B22',
      tail: '#1A6B1A', wing: '#1A5A1A', leg: '#1A4A1A', horn: '#3A8B3A',
    },
  };
  return colors[variant]?.[type] || '#888888';
}

function getVariantShadeColor(variant, type) {
  const colors = {
    human: { head: '#C89874', body: '#2A4E95', arm: '#C89874', leg: '#1A2A6A' },
    zombie: { head: '#3A5A1A', body: '#2A4810', arm: '#3A5A1A', leg: '#1A3A08' },
    skeleton: { head: '#B8B8A0', body: '#A0A088', arm: '#B8B8A0', leg: '#909078' },
    villager: { head: '#906030', body: '#5A1A1A', arm: '#906030', leg: '#3A0A0A' },
    animal: { head: '#6B3A0B', body: '#6B3A0B', leg: '#4B1A0B', tail: '#6B3A0B' },
    monster: { head: '#4A0A4A', body: '#2A002A', arm: '#4A0A4A', leg: '#1A001A', wing: '#1A001A', horn: '#3A0A3A' },
    dragon: {
      head: '#1A6B1A', neck: '#145114', body: '#1A6B1A',
      tail: '#144B14', wing: '#144A14', leg: '#143A14', horn: '#2A7B2A',
    },
  };
  return colors[variant]?.[type] || '#555555';
}

window.CreatureView = CreatureView;
window.HierarchyPanel = HierarchyPanel;
window.PropertiesPanel = PropertiesPanel;
window.TemplatePanel = TemplatePanel;
window.ReferencePanel = ReferencePanel;
window.createHumanoidTemplate = createHumanoidTemplate;
