/* ============================================================
   3D 方塊材質編輯器 — Block Texture Editor
   六面獨立/連結材質、2D ↔ 3D 即時同步、Pointer Events 輸入
   ============================================================ */

function BlockView({ 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 cubeRef = React.useRef(null);
  const raycasterRef = React.useRef(null);
  const mouseRef = React.useRef(new THREE.Vector2());
  const materialsRef = React.useRef([]);
  const texturesRef = React.useRef([]);

  // 六面材質狀態
  const facesRef = React.useRef(null);
  const [faces, setFaces] = React.useState(null);
  const [activeFace, setActiveFace] = React.useState('north');
  const [showGrid, setShowGrid] = React.useState(true);
  const [, forceUpdate] = React.useReducer(x => x + 1, 0);

  // 2D 編輯器狀態
  const [tool2d, setTool2d] = React.useState('pencil');
  const [color2d, setColor2d] = React.useState('#4CAA3AFF');
  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 [symmetry, setSymmetry] = React.useState('none');

  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 cameraStateRef = React.useRef({
    theta: Math.PI / 4,
    phi: Math.PI / 4,
    radius: 5,
    target: new THREE.Vector3(0, 0, 0),
    isOrtho: false,
    isRotating: false,
    isPanning: false,
  });

  // Pointer 事件狀態
  const pointersRef = React.useRef(new Map());
  const pinchRef = React.useRef(null);
  const activePointerRef = React.useRef(null);
  const gestureModeRef = React.useRef(null);

  // ---- 初始化 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));
    container.appendChild(renderer.domElement);
    rendererRef.current = renderer;

    raycasterRef.current = new THREE.Raycaster();

    // 光照
    const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
    scene.add(ambientLight);
    const dirLight = new THREE.DirectionalLight(0xffffff, 0.6);
    dirLight.position.set(5, 10, 7);
    scene.add(dirLight);
    const backLight = new THREE.DirectionalLight(0x8888aa, 0.3);
    backLight.position.set(-5, 5, -5);
    scene.add(backLight);

    // 建立六面材質
    const faceDefs = [
      { id: 'east', name: '東面(East)', color: '#4CAA3A' },
      { id: 'west', name: '西面(West)', color: '#3A8A28' },
      { id: 'top', name: '頂面(Top)', color: '#6BCC4A' },
      { id: 'bottom', name: '底面(Bottom)', color: '#8B6B3A' },
      { id: 'south', name: '南面(South)', color: '#5CBA4A' },
      { id: 'north', name: '北面(North)', color: '#5CBA4A' },
    ];

    const faceData = {};
    const texSize = project.width || 16;
    const savedFaces = AppState.faces;

    if (savedFaces && Object.keys(savedFaces).length) {
      faceDefs.forEach(def => {
        const src = savedFaces[def.id];
        faceData[def.id] = src || {
          id: def.id,
          name: def.name,
          textureId: def.id,
          canvas: createPixelCanvas(texSize, texSize),
          width: texSize,
          height: texSize,
        };
      });
    } else {
      // 建立預設草地材質
      const defaultTextures = {
        top: createGrassTopTexture(texSize),
        bottom: createDirtTexture(texSize),
        north: createGrassSideTexture(texSize),
        south: createGrassSideTexture(texSize),
        east: createGrassSideTexture(texSize),
        west: createGrassSideTexture(texSize),
      };

      const linkedGroups = {
        top: 'top',
        bottom: 'bottom',
        north: 'side',
        south: 'side',
        east: 'side',
        west: 'side',
      };

      faceDefs.forEach(def => {
        const canvas = defaultTextures[def.id];
        faceData[def.id] = {
          id: def.id,
          name: ({
            top: '草色頂面', bottom: '泥土底面',
            north: '草色側面(北)', south: '草色側面(南)',
            east: '草色側面(東)', west: '草色側面(西)',
          })[def.id] || def.name,
          textureId: linkedGroups[def.id],
          canvas,
          width: texSize,
          height: texSize,
        };
      });
      const shared = {};
      Object.values(faceData).forEach(f => {
        if (!shared[f.textureId]) shared[f.textureId] = f.canvas;
        else f.canvas = shared[f.textureId];
      });
    }

    facesRef.current = faceData;
    setFaces(faceData);

    // 建立材質陣列（Three.js 順序：+x, -x, +y, -y, +z, -z）
    const faceOrder = ['east', 'west', 'top', 'bottom', 'south', 'north'];
    const materials = [];
    const textures = [];

    faceOrder.forEach(faceId => {
      const tex = new THREE.CanvasTexture(faceData[faceId].canvas);
      tex.magFilter = THREE.NearestFilter;
      tex.minFilter = THREE.NearestFilter;
      tex.generateMipmaps = false;
      tex.needsUpdate = true;
      textures.push(tex);
      materials.push(new THREE.MeshStandardMaterial({ map: tex, transparent: true }));
    });

    materialsRef.current = materials;
    texturesRef.current = textures;

    // 立方體
    const geometry = new THREE.BoxGeometry(2, 2, 2);
    const cube = new THREE.Mesh(geometry, materials);
    scene.add(cube);
    cubeRef.current = cube;

    // 邊框
    const edges = new THREE.LineSegments(
      new THREE.EdgesGeometry(geometry),
      new THREE.LineBasicMaterial({ color: 0x000000, transparent: true, opacity: 0.3 })
    );
    cube.add(edges);

    // 更新相機
    updateCamera();

    // 渲染迴圈
    let rafId;
    const animate = () => {
      rafId = requestAnimationFrame(animate);
      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(rafId);
      sizeWatch.disconnect();
      window.removeEventListener('resize', handleResize);
      window.removeEventListener('mcstudio:resize', handleResize);
      renderer.dispose();
      if (renderer.domElement.parentNode) {
        renderer.domElement.parentNode.removeChild(renderer.domElement);
      }
      geometry.dispose();
      materials.forEach(m => m.dispose());
      textures.forEach(t => t.dispose());
    };
    // eslint-disable-next-line
  }, []);

  React.useEffect(() => {
    AppState.faces = faces;
    AppState.activeFace = activeFace;
    AppState.textures = {};
    if (faces) {
      Object.values(faces).forEach(f => {
        AppState.textures[f.id] = { canvas: f.canvas, name: f.name };
      });
    }
  }, [faces, activeFace]);

  // ---- 相機控制 ----
  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;
    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 = 5;
    cs.target.set(0, 0, 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 });

    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;
    }

    if (e.button === 0 && !e.altKey && !e.shiftKey) {
      // 嘗試選取面
      const hitFace = pickFace(e);
      if (hitFace !== null) {
        setActiveFace(hitFace);
        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 || 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(2, Math.min(30, 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;
      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.15 : 0.87;
    cs.radius = Math.max(2, Math.min(30, cs.radius * factor));
    updateCamera();
  };

  // Raycasting 選取面
  const pickFace = (e) => {
    const container = containerRef.current;
    if (!container || !cubeRef.current) 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 intersects = raycaster.intersectObject(cubeRef.current);

    if (intersects.length > 0) {
      const normal = intersects[0].face.normal;
      if (normal.x > 0.5) return 'east';
      if (normal.x < -0.5) return 'west';
      if (normal.y > 0.5) return 'top';
      if (normal.y < -0.5) return 'bottom';
      if (normal.z > 0.5) return 'south';
      if (normal.z < -0.5) return 'north';
    }
    return null;
  };

  // ---- 2D 材質編輯 ----
  const activeFaceData = faces ? faces[activeFace] : null;

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

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

    const pos = get2dPixelPos(e);
    if (pos.x < 0 || pos.y < 0 || pos.x >= activeFaceData.width || pos.y >= activeFaceData.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') {
      drawPixel(pos.x, pos.y, c);
    } else if (tool2d === 'fill') {
      fillPixel(pos.x, pos.y, c);
      drawingRef.current = false;
      setIsDrawing2d(false);
    }
  };

  const texPointerMove = (e) => {
    e.preventDefault();
    if (!drawingRef.current || !activeFaceData) 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({ texture: { canvas: activeFaceData.canvas } }, last.x, last.y, pos.x, pos.y, c, activeFaceData.width, activeFaceData.height);
      updateLinkedFaceTexture(activeFace);
    }
    lastPosRef.current = pos;
    setLastPos2d(pos);
  };

  const texPointerUp = (e) => {
    e.preventDefault();
    drawingRef.current = false;
    setIsDrawing2d(false);
    setStrokeStarted2d(false);
    refreshAllTextures();
    updateLinkedFaceTexture(activeFace);
  };

  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 drawPixel = (x, y, color) => {
    if (!activeFaceData) return;
    const w = activeFaceData.width;
    const h = activeFaceData.height;
    if (x < 0 || y < 0 || x >= w || y >= h) return;

    const ctx = activeFaceData.canvas.getContext('2d');
    ctx.fillStyle = color;
    ctx.fillRect(x, y, 1, 1);

    if (symmetry === 'horizontal' || symmetry === 'both') {
      ctx.fillRect(w - 1 - x, y, 1, 1);
    }
    if (symmetry === 'vertical' || symmetry === 'both') {
      ctx.fillRect(x, h - 1 - y, 1, 1);
    }
    if (symmetry === 'both') {
      ctx.fillRect(w - 1 - x, h - 1 - y, 1, 1);
    }

    // 更新連結的面
    updateLinkedFaceTexture(activeFace);
  };

  // 油漆桶填充
  const fillPixel = (x, y, color) => {
    if (!activeFaceData) return;
    const canvas = activeFaceData.canvas;
    const w = activeFaceData.width;
    const h = activeFaceData.height;
    const ctx = canvas.getContext('2d');
    const imgData = ctx.getImageData(0, 0, w, h);
    const data = imgData.data;

    const targetIdx = (y * w + x) * 4;
    const targetR = data[targetIdx], targetG = data[targetIdx + 1], targetB = data[targetIdx + 2], targetA = data[targetIdx + 3];

    const fillColor = PixelCanvasCore.hexToRgba(color);
    if (targetR === fillColor.r && targetG === fillColor.g && targetB === fillColor.b && targetA === fillColor.a) return;

    // flood fill
    const stack = [[x, y]];
    const visited = new Set();
    while (stack.length > 0) {
      const [cx, cy] = stack.pop();
      const key = cx + ',' + cy;
      if (visited.has(key)) continue;
      if (cx < 0 || cy < 0 || cx >= w || cy >= h) continue;
      const idx = (cy * w + cx) * 4;
      if (data[idx] !== targetR || data[idx + 1] !== targetG || data[idx + 2] !== targetB || data[idx + 3] !== targetA) continue;
      visited.add(key);
      data[idx] = fillColor.r;
      data[idx + 1] = fillColor.g;
      data[idx + 2] = fillColor.b;
      data[idx + 3] = fillColor.a;
      stack.push([cx + 1, cy], [cx - 1, cy], [cx, cy + 1], [cx, cy - 1]);
    }
    ctx.putImageData(imgData, 0, 0);
    updateLinkedFaceTexture(activeFace);
  };

  // 更新連結面的 3D 紋理與 2D 預覽
  const updateLinkedFaceTexture = (changedFace) => {
    const all = facesRef.current || faces;
    if (!all || !changedFace) return;
    const sourceFace = all[changedFace];
    if (!sourceFace) return;
    const faceOrder = ['east', 'west', 'top', 'bottom', 'south', 'north'];

    faceOrder.forEach((faceId, idx) => {
      const f = all[faceId];
      if (!f) return;
      if (f.textureId === sourceFace.textureId && f.canvas !== sourceFace.canvas) {
        const ctx = f.canvas.getContext('2d');
        ctx.imageSmoothingEnabled = false;
        ctx.clearRect(0, 0, f.canvas.width, f.canvas.height);
        ctx.drawImage(sourceFace.canvas, 0, 0);
      }
      if (f.textureId === sourceFace.textureId || f.canvas === sourceFace.canvas) {
        markTextureDirty(texturesRef.current[idx]);
        if (materialsRef.current[idx]) materialsRef.current[idx].needsUpdate = true;
      }
    });
    syncPreviewCanvas(editCanvasRef.current, sourceFace.canvas);
  };

  const refreshAllTextures = () => {
    texturesRef.current.forEach(t => markTextureDirty(t));
    const src = (facesRef.current || faces || {})[activeFace];
    if (src) syncPreviewCanvas(editCanvasRef.current, src.canvas);
  };

  // ---- 連結/獨立材質 ----
  const linkFaces = (sourceFace, targetFace) => {
    if (!faces) return;
    pushUndo();
    const src = faces[sourceFace];
    const tgt = faces[targetFace];
    const newFaces = { ...faces };
    newFaces[targetFace] = {
      ...tgt,
      textureId: src.textureId,
      canvas: src.canvas,
    };
    facesRef.current = newFaces;
    setFaces(newFaces);
    refreshAllTextures();
    showToast(`已連結 ${tgt.name} 到 ${src.name}`, 'success');
  };

  const unlinkFace = (faceId) => {
    if (!faces) return;
    pushUndo();
    const f = faces[faceId];
    const newCanvas = copyCanvas(f.canvas);
    const newFaces = { ...faces };
    newFaces[faceId] = {
      ...f,
      textureId: uid(),
      canvas: newCanvas,
    };
    facesRef.current = newFaces;
    setFaces(newFaces);

    // 更新對應材質
    const faceOrder = ['east', 'west', 'top', 'bottom', 'south', 'north'];
    const idx = faceOrder.indexOf(faceId);
    if (idx >= 0) {
      const oldTex = texturesRef.current[idx];
      const newTex = new THREE.CanvasTexture(newCanvas);
      newTex.magFilter = THREE.NearestFilter;
      newTex.minFilter = THREE.NearestFilter;
      newTex.needsUpdate = true;
      texturesRef.current[idx] = newTex;
      materialsRef.current[idx].map = newTex;
      materialsRef.current[idx].needsUpdate = true;
      oldTex.dispose();
    }
    showToast('已解除連結，現在為獨立材質', 'success');
  };

  const isFaceLinked = (faceId) => {
    if (!faces) return false;
    const tgtTextureId = faces[faceId].textureId;
    return Object.values(faces).filter(f => f.textureId === tgtTextureId).length > 1;
  };

  const getLinkedFaces = (faceId) => {
    if (!faces) return [];
    const tgtTextureId = faces[faceId].textureId;
    return Object.values(faces).filter(f => f.textureId === tgtTextureId && f.id !== faceId);
  };

  // ---- 快捷鍵 ----
  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(); }
    };
    window.addEventListener('keydown', handler);
    return () => window.removeEventListener('keydown', handler);
  });

  // ---- 初始化編輯器 canvas（當切換 activeFace 時刷新顯示）----
  React.useEffect(() => {
    const src = activeFaceData && activeFaceData.canvas;
    const id = requestAnimationFrame(() => syncPreviewCanvas(editCanvasRef.current, src));
    return () => cancelAnimationFrame(id);
  }, [activeFace, activeFaceData]);

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

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

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

      const addedTextures = new Map(); // textureId -> filename
      const faceNames = {};

      for (const [faceId, face] of Object.entries(faces)) {
        if (!addedTextures.has(face.textureId)) {
          const fn = `${blockName}_${faceId}`;
          addedTextures.set(face.textureId, fn);
          const blob = await canvasToBlob(face.canvas);
          zip.file(`assets/minecraft/textures/block/${fn}.png`, blob);
        }
        faceNames[faceId] = addedTextures.get(face.textureId);
      }

      const blockState = {
        variants: { '': { model: `minecraft:block/${blockName}` } }
      };
      zip.file(`assets/minecraft/blockstates/${blockName}.json`, JSON.stringify(blockState, null, 2));

      const model = {
        parent: 'minecraft:block/cube_all',
        textures: {
          particle: `minecraft:block/${faceNames['north'] || faceNames['top'] || blockName}`,
          up: `minecraft:block/${faceNames['top'] || blockName}`,
          down: `minecraft:block/${faceNames['bottom'] || blockName}`,
          north: `minecraft:block/${faceNames['north'] || blockName}`,
          south: `minecraft:block/${faceNames['south'] || blockName}`,
          east: `minecraft:block/${faceNames['east'] || blockName}`,
          west: `minecraft:block/${faceNames['west'] || blockName}`,
        },
      };
      zip.file(`assets/minecraft/models/block/${blockName}.json`, JSON.stringify(model, null, 2));

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

  // 註冊全域函式
  React.useEffect(() => {
    window.triggerExport = exportFacePNG;
    window.exportResourcePack = exportBlockPack;
    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 === exportFacePNG) window.triggerExport = null;
      if (window.exportResourcePack === exportBlockPack) window.exportResourcePack = null;
      window.resetView = null;
      window.triggerCanvasResize = null;
    };
  }, [exportFacePNG, exportBlockPack]);

  const editDisplaySize = (activeFaceData?.width || 16) * zoom2d;
  const faceOrder = ['top', 'bottom', 'north', 'south', 'east', 'west'];
  const faceLabels = {
    top: '頂面', bottom: '底面', north: '北面',
    south: '南面', east: '東面', west: '西面',
  };

  return React.createElement('div', { className: 'block-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: 'block-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', onClick: () => setCameraView('front'), title: '正面視圖' }, '正'),
          React.createElement('button', { className: 'view-btn', onClick: () => setCameraView('top'), title: '頂部視圖' }, '頂'),
          React.createElement('button', { className: 'view-btn', onClick: () => setCameraView('left'), title: '左側視圖' }, '左'),
        ),
        // 右側視圖切換
        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)
          ),
        ),
        // 選取面提示
        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)' } },
            faceLabels[activeFace] || activeFace,
          ),
          isFaceLinked(activeFace) && React.createElement('div', null,
            '連結到: ',
            getLinkedFaces(activeFace).map(f => faceLabels[f.id]).join('、'),
          ),
        ),
      ),

      // 底部：材質編輯區
      React.createElement('div', { className: 'block-texture-editor' },
        React.createElement('div', { className: 'texture-editor-toolbar' },
          React.createElement('span', { style: { color: 'var(--text-secondary)', fontSize: '11px' } },
            '編輯: ',
            React.createElement('strong', { style: { color: 'var(--text-primary)' } },
              faceLabels[activeFace] || activeFace,
            ),
            ' 材質',
            activeFaceData && `  (${activeFaceData.width}×${activeFaceData.height})`,
          ),
          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 8px' },
              }, t === 'pencil' ? '鉛筆' : t === 'eraser' ? '橡皮擦' : '油漆桶')
            ),
          ),
          // 對稱選項
          React.createElement('select', {
            value: symmetry,
            onChange: (e) => setSymmetry(e.target.value),
            className: 'form-input form-input--sm',
            style: { fontSize: '10px' },
          },
            React.createElement('option', { value: 'none' }, '無對稱'),
            React.createElement('option', { value: 'horizontal' }, '水平對稱'),
            React.createElement('option', { value: 'vertical' }, '垂直對稱'),
            React.createElement('option', { value: 'both' }, '雙向對稱'),
          ),
          React.createElement('div', { style: { display: 'flex', gap: '4px' } },
            React.createElement('button', { className: 'zoom-btn', onClick: () => setZoom2d(z => Math.max(1, z / 2)) }, '−'),
            React.createElement('span', { className: 'zoom-level' }, `${zoom2d * 100 / 16}%`),
            React.createElement('button', { className: 'zoom-btn', onClick: () => setZoom2d(z => Math.min(64, z * 2)) }, '+'),
          ),
          React.createElement('button', { className: 'btn btn--primary btn--sm', onClick: exportFacePNG },
            React.createElement(SvgIcon, { name: 'Download', size: 10 }),
            ' 匯出 PNG',
          ),
        ),
        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,
              },
            }),
            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: '展開右側面板',
    }, '◀'),

    // 右側面板
    !rightPanelCollapsed && React.createElement('div', { className: 'right-panel' },
      // 六面材質清單
      React.createElement(FaceListPanel, {
        faces, activeFace, setActiveFace,
        isFaceLinked, getLinkedFaces,
        onLink: linkFaces,
        onUnlink: unlinkFace,
        faceLabels,
      }),
      // 接縫檢查
      React.createElement(SeamCheckPanel, { faces }),
      // 變體生成
      React.createElement(VariantPanel, {
        activeFaceData,
        onApply: () => { refreshAllTextures(); forceUpdate(); },
      }),
      // 顏色選擇器
      React.createElement(ColorPickerPanel, {
        color: color2d.replace('#', ''),
        onChange: setColor2d,
      }),
      // 匯出工具
      React.createElement(MCToolsPanel, { view: 'block', project, onExport: exportFacePNG }),
    ),
  );
}

// ============================================================
// 六面材質清單面板
// ============================================================
function FaceListPanel({ faces, activeFace, setActiveFace, isFaceLinked, getLinkedFaces, onLink, onUnlink, faceLabels }) {
  const [linkMenu, setLinkMenu] = React.useState(null);
  const faceOrder = ['top', 'bottom', 'north', 'south', 'east', 'west'];

  if (!faces) return null;

  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' },
      faceOrder.map(faceId => {
        const face = faces[faceId];
        const linked = isFaceLinked(faceId);
        const isActive = activeFace === faceId;
        const linkedNames = getLinkedFaces(faceId).map(f => faceLabels[f.id]).join('、');

        return React.createElement('div', {
          key: faceId,
          className: 'face-item' + (isActive ? ' active' : ''),
          onClick: () => setActiveFace(faceId),
          style: { position: 'relative' },
        },
          // 縮圖
          React.createElement('div', {
            className: 'face-item__thumb',
            style: {
              width: 32, height: 32,
              backgroundImage: `url(${face.canvas.toDataURL()})`,
              backgroundSize: 'cover',
              imageRendering: 'pixelated',
              border: '1px solid var(--border)',
              borderRadius: 'var(--radius-sm)',
            },
          }),
          React.createElement('div', { className: 'face-item__info' },
            React.createElement('div', { className: 'face-item__name' }, faceLabels[faceId]),
            React.createElement('div', { className: 'face-item__meta' },
              linked ? `連結: ${linkedNames}` : '獨立材質',
            ),
          ),
          // 操作按鈕
          React.createElement('div', {
            className: 'face-item__actions',
            onClick: (e) => e.stopPropagation(),
            style: { position: 'relative' },
          },
            React.createElement('button', {
              className: 'layer-icon-btn',
              onClick: (e) => {
                e.stopPropagation();
                setLinkMenu(linkMenu === faceId ? null : faceId);
              },
              title: '連結設定',
            },
              React.createElement(SvgIcon, { name: 'Link', size: 10 }),
            ),
            linkMenu === faceId && React.createElement('div', {
              style: {
                position: 'absolute',
                top: '20px',
                right: 0,
                background: 'var(--bg-surface)',
                border: '1px solid var(--border)',
                borderRadius: 'var(--radius-sm)',
                padding: '4px',
                zIndex: 100,
                minWidth: '130px',
                boxShadow: 'var(--shadow-md)',
              },
              onClick: (e) => e.stopPropagation(),
            },
              React.createElement('div', {
                style: {
                  padding: '3px 6px',
                  fontSize: '10px',
                  color: 'var(--text-tertiary)',
                  borderBottom: '1px solid var(--border-subtle)',
                  marginBottom: '2px',
                },
              }, '連結到...'),
              faceOrder.filter(f => f !== faceId).map(f =>
                React.createElement('div', {
                  key: f,
                  style: {
                    padding: '3px 6px',
                    fontSize: '10px',
                    color: 'var(--text-secondary)',
                    cursor: 'pointer',
                    borderRadius: 'var(--radius-xs)',
                  },
                  onMouseEnter: (e) => e.target.style.background = 'var(--bg-hover)',
                  onMouseLeave: (e) => e.target.style.background = 'transparent',
                  onClick: () => { onLink(f, faceId); setLinkMenu(null); },
                }, faceLabels[f])
              ),
              linked && React.createElement('div', {
                style: {
                  padding: '3px 6px',
                  fontSize: '10px',
                  color: 'var(--accent-orange)',
                  cursor: 'pointer',
                  borderRadius: 'var(--radius-xs)',
                  borderTop: '1px solid var(--border-subtle)',
                  marginTop: '2px',
                },
                onMouseEnter: (e) => e.target.style.background = 'var(--bg-hover)',
                onMouseLeave: (e) => e.target.style.background = 'transparent',
                onClick: () => { onUnlink(faceId); setLinkMenu(null); },
              }, '解除連結'),
            ),
          ),
        );
      }),
    ),
  );
}

// ============================================================
// 接縫檢查面板
// ============================================================
function SeamCheckPanel({ faces }) {
  const [isOpen, setIsOpen] = React.useState(false);
  const [result, setResult] = React.useState(null);

  const checkSeams = () => {
    if (!faces) return;
    const issues = [];

    // 檢查每個面是否為 16×16（或專案尺寸）的整數倍
    Object.values(faces).forEach(face => {
      const w = face.width, h = face.height;
      if (w <= 0 || h <= 0) {
        issues.push({ level: 'error', msg: `${face.name}: 尺寸無效` });
      }
      if (w & (w - 1) || h & (h - 1)) {
        issues.push({ level: 'warning', msg: `${face.name}: 尺寸非 2 的次方 (${w}×${h})` });
      }
    });

    // 檢查頂部與側面的邊緣顏色是否連續（簡單檢查）
    const topCanvas = faces['top']?.canvas;
    const sideFaces = ['north', 'south', 'east', 'west'];
    sideFaces.forEach(sideId => {
      const sideCanvas = faces[sideId]?.canvas;
      if (!topCanvas || !sideCanvas) return;
      const topCtx = topCanvas.getContext('2d');
      const sideCtx = sideCanvas.getContext('2d');
      const w = topCanvas.width;
      // 檢查頂面底部列與側面頂部列的顏色差異
      let diffCount = 0;
      for (let x = 0; x < w; x++) {
        const topPx = topCtx.getImageData(x, w - 1, 1, 1).data;
        const sidePx = sideCtx.getImageData(x, 0, 1, 1).data;
        const d = Math.abs(topPx[0] - sidePx[0]) + Math.abs(topPx[1] - sidePx[1]) + Math.abs(topPx[2] - sidePx[2]);
        if (d > 100) diffCount++;
      }
      if (diffCount > w * 0.5) {
        issues.push({ level: 'warning', msg: `${faces['top'].name} 與 ${faces[sideId].name} 邊緣差異大 (${diffCount}/${w})` });
      }
    });

    // 檢查邊緣透明像素
    Object.values(faces).forEach(face => {
      const ctx = face.canvas.getContext('2d');
      const w = face.width, h = face.height;
      let edgeTransparent = 0;
      for (let x = 0; x < w; x++) {
        const top = ctx.getImageData(x, 0, 1, 1).data;
        const bottom = ctx.getImageData(x, h - 1, 1, 1).data;
        if (top[3] === 0) edgeTransparent++;
        if (bottom[3] === 0) edgeTransparent++;
      }
      for (let y = 0; y < h; y++) {
        const left = ctx.getImageData(0, y, 1, 1).data;
        const right = ctx.getImageData(w - 1, y, 1, 1).data;
        if (left[3] === 0) edgeTransparent++;
        if (right[3] === 0) edgeTransparent++;
      }
      if (edgeTransparent > (w + h) * 2 * 0.3) {
        issues.push({ level: 'info', msg: `${face.name}: 邊緣有大量透明像素 (${edgeTransparent})` });
      }
    });

    if (issues.length === 0) {
      setResult({ status: 'ok', issues: [] });
      showToast('接縫檢查通過 ✓', 'success');
    } else {
      setResult({ status: issues.some(i => i.level === 'error') ? 'error' : 'warning', issues });
      showToast(`發現 ${issues.length} 個問題`, 'warning');
    }
  };

  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',
      style: { gap: '6px', display: 'flex', flexDirection: 'column' }
    },
      React.createElement('button', { className: 'btn btn--secondary', style: { width: '100%', justifyContent: 'center' }, onClick: checkSeams },
        React.createElement(SvgIcon, { name: 'Check', size: 12 }),
        ' 執行接縫檢查',
      ),
      result && result.issues.length > 0 && React.createElement('div', {
        style: { maxHeight: 120, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: '3px' }
      },
        result.issues.map((issue, i) =>
          React.createElement('div', {
            key: i,
            style: {
              fontSize: '10px',
              padding: '3px 6px',
              borderRadius: 'var(--radius-xs)',
              background: issue.level === 'error' ? 'rgba(220, 60, 60, 0.15)'
                : issue.level === 'warning' ? 'rgba(240, 160, 40, 0.15)'
                  : 'rgba(60, 120, 220, 0.15)',
              color: issue.level === 'error' ? 'var(--accent-red)'
                : issue.level === 'warning' ? 'var(--accent-orange)'
                  : 'var(--accent-blue)',
            },
          }, issue.msg)
        ),
      ),
      result && result.issues.length === 0 && React.createElement('div', {
        style: {
          fontSize: '11px', padding: '6px', textAlign: 'center',
          color: 'var(--accent-green)', background: 'rgba(60, 200, 80, 0.1)',
          borderRadius: 'var(--radius-sm)',
        },
      }, '✓ 所有檢查通過'),
    ),
  );
}

// ============================================================
// 變體生成面板
// ============================================================
function VariantPanel({ activeFaceData, onApply }) {
  const [isOpen, setIsOpen] = React.useState(false);
  const [brightness, setBrightness] = React.useState(0);
  const [saturation, setSaturation] = React.useState(0);
  const [noise, setNoise] = React.useState(10);

  const applyVariant = () => {
    if (!activeFaceData) return;
    pushUndo();
    const canvas = activeFaceData.canvas;
    const ctx = canvas.getContext('2d');
    const w = canvas.width, h = canvas.height;
    const imgData = ctx.getImageData(0, 0, w, h);
    const data = imgData.data;

    const noiseAmount = parseInt(noise);

    for (let i = 0; i < data.length; i += 4) {
      if (data[i + 3] === 0) continue; // 跳過透明像素
      // 亮度調整
      const bShift = parseInt(brightness);
      data[i] = Math.max(0, Math.min(255, data[i] + bShift));
      data[i + 1] = Math.max(0, Math.min(255, data[i + 1] + bShift));
      data[i + 2] = Math.max(0, Math.min(255, data[i + 2] + bShift));
      // 飽和度調整（簡化版）
      if (saturation !== 0) {
        const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
        const sat = parseInt(saturation) / 100;
        data[i] = Math.max(0, Math.min(255, gray + (data[i] - gray) * (1 + sat)));
        data[i + 1] = Math.max(0, Math.min(255, gray + (data[i + 1] - gray) * (1 + sat)));
        data[i + 2] = Math.max(0, Math.min(255, gray + (data[i + 2] - gray) * (1 + sat)));
      }
      // 雜訊
      if (noiseAmount > 0) {
        const n = (Math.random() - 0.5) * noiseAmount * 2;
        data[i] = Math.max(0, Math.min(255, data[i] + n));
        data[i + 1] = Math.max(0, Math.min(255, data[i + 1] + n));
        data[i + 2] = Math.max(0, Math.min(255, data[i + 2] + n));
      }
    }
    ctx.putImageData(imgData, 0, 0);
    onApply?.();
    showToast('已套用變體效果', 'success');
  };

  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',
      style: { gap: '8px', display: 'flex', flexDirection: 'column' }
    },
      React.createElement('div', { className: 'slider-row' },
        React.createElement('span', { style: { fontSize: '10px', color: 'var(--text-tertiary)', width: '48px' } }, '亮度'),
        React.createElement('input', { type: 'range', min: -50, max: 50, value: brightness, onChange: (e) => setBrightness(e.target.value) }),
        React.createElement('span', { className: 'slider-value' }, `${brightness > 0 ? '+' : ''}${brightness}`),
      ),
      React.createElement('div', { className: 'slider-row' },
        React.createElement('span', { style: { fontSize: '10px', color: 'var(--text-tertiary)', width: '48px' } }, '飽和'),
        React.createElement('input', { type: 'range', min: -50, max: 50, value: saturation, onChange: (e) => setSaturation(e.target.value) }),
        React.createElement('span', { className: 'slider-value' }, `${saturation > 0 ? '+' : ''}${saturation}`),
      ),
      React.createElement('div', { className: 'slider-row' },
        React.createElement('span', { style: { fontSize: '10px', color: 'var(--text-tertiary)', width: '48px' } }, '雜訊'),
        React.createElement('input', { type: 'range', min: 0, max: 50, value: noise, onChange: (e) => setNoise(e.target.value) }),
        React.createElement('span', { className: 'slider-value' }, noise),
      ),
      React.createElement('button', {
        className: 'btn btn--secondary btn--sm',
        style: { width: '100%', justifyContent: 'center' },
        onClick: applyVariant,
      }, '套用至目前材質'),
    ),
  );
}

// 預設材質建立函式
function createGrassTopTexture(size) {
  const canvas = document.createElement('canvas');
  canvas.width = size;
  canvas.height = size;
  const ctx = canvas.getContext('2d');

  // 基底綠色
  const baseGreen = '#5A9A3A';
  const darkGreen = '#3A7A1A';
  const lightGreen = '#7ABA5A';

  ctx.fillStyle = baseGreen;
  ctx.fillRect(0, 0, size, size);

  // 像素雜訊，模擬草地
  for (let y = 0; y < size; y++) {
    for (let x = 0; x < size; x++) {
      const r = Math.random();
      if (r < 0.25) ctx.fillStyle = darkGreen;
      else if (r < 0.5) ctx.fillStyle = lightGreen;
      else continue;
      ctx.fillRect(x, y, 1, 1);
    }
  }
  return canvas;
}

function createGrassSideTexture(size) {
  const canvas = document.createElement('canvas');
  canvas.width = size;
  canvas.height = size;
  const ctx = canvas.getContext('2d');

  const dirtBase = '#8B6B3A';
  const dirtDark = '#6B4B2A';
  const dirtLight = '#AB8B5A';
  const grassGreen = '#5A9A3A';
  const grassDark = '#3A7A1A';

  // 泥土基底
  ctx.fillStyle = dirtBase;
  ctx.fillRect(0, 0, size, size);

  // 泥土雜訊
  for (let y = 0; y < size; y++) {
    for (let x = 0; x < size; x++) {
      const r = Math.random();
      if (r < 0.2) ctx.fillStyle = dirtDark;
      else if (r < 0.35) ctx.fillStyle = dirtLight;
      else continue;
      ctx.fillRect(x, y, 1, 1);
    }
  }

  // 上方草帶 (約 1/4 高度)
  const grassHeight = Math.floor(size / 4);
  for (let y = 0; y < grassHeight; y++) {
    for (let x = 0; x < size; x++) {
      const r = Math.random();
      if (r < 0.3) ctx.fillStyle = grassDark;
      else ctx.fillStyle = grassGreen;
      ctx.fillRect(x, y, 1, 1);
    }
  }

  // 草地邊緣不規則
  for (let x = 0; x < size; x++) {
    if (Math.random() < 0.4) {
      ctx.fillStyle = grassGreen;
      ctx.fillRect(x, grassHeight, 1, 1);
    }
    if (Math.random() < 0.2) {
      ctx.fillStyle = grassDark;
      ctx.fillRect(x, grassHeight + 1, 1, 1);
    }
  }

  return canvas;
}

function createDirtTexture(size) {
  const canvas = document.createElement('canvas');
  canvas.width = size;
  canvas.height = size;
  const ctx = canvas.getContext('2d');

  const dirtBase = '#8B6B3A';
  const dirtDark = '#6B4B2A';
  const dirtLight = '#AB8B5A';

  ctx.fillStyle = dirtBase;
  ctx.fillRect(0, 0, size, size);

  for (let y = 0; y < size; y++) {
    for (let x = 0; x < size; x++) {
      const r = Math.random();
      if (r < 0.25) ctx.fillStyle = dirtDark;
      else if (r < 0.4) ctx.fillStyle = dirtLight;
      else continue;
      ctx.fillRect(x, y, 1, 1);
    }
  }
  return canvas;
}

window.BlockView = BlockView;
window.FaceListPanel = FaceListPanel;
window.SeamCheckPanel = SeamCheckPanel;
window.VariantPanel = VariantPanel;
