/* ============================================================
   State Management & Project Store
   Centralized state with undo/redo for the whole app.
   ============================================================ */

const AppState = {
  // View state
  currentView: 'home', // 'home' | 'pixel' | 'block' | 'creature'

  // 目前登入帳號；雲端專案一律綁這個 user.id
  user: null,
  dirty: false,
  saving: false,

  // Project metadata
  project: null,
  // Project shape:
  // {
  //   id: string,
  //   name: string,
  //   type: 'pixel' | 'item' | 'gui' | 'block' | 'creature',
  //   mcVersion: string,
  //   width: number,
  //   height: number,
  //   createdAt: number,
  //   updatedAt: number,
  // }

  // Layers (shared across 2D contexts)
  // Each layer: { id, name, visible, locked, opacity, alphaLock, canvas }
  // canvas is an HTMLCanvasElement stored in the data layer
  activeLayerId: null,

  // For block mode — faces and textures
  // faces: { top, bottom, north, south, east, west } each has textureId
  // textures: { id → canvas + name }
  blockData: null,

  // For creature mode
  creatureData: null,

  // UI state
  showExplorer: true,
  beginnerMode: false,

  // Recent projects
  recentProjects: [],

  // Toasts
  toasts: [],

  // Resource pack file tree (virtual)
  packTree: null,
};

const Listeners = new Set();

function setState(updater) {
  if (typeof updater === 'function') {
    updater(AppState);
  } else {
    Object.assign(AppState, updater);
  }
  Listeners.forEach(fn => fn(AppState));
}

function subscribe(fn) {
  Listeners.add(fn);
  return () => Listeners.delete(fn);
}

// ---------- Undo/Redo Stack ----------
// We store snapshots of the current editing context per project.
// The snapshot mechanism works per "active editor": for 2D pixel mode we snapshot all layer imageData.
// For block mode we snapshot all texture canvases.
// For creature mode we snapshot the model hierarchy + textures.

const UndoStack = {
  past: [],
  future: [],
};

function snapshotCurrentEditor() {
  if (AppState.currentView === 'pixel' || AppState.currentView === 'block') {
    // snapshot the textures dict
    const textures = AppState.textures || {};
    const snap = {};
    for (const id in textures) {
      const t = textures[id];
      snap[id] = {
        data: t.canvas.getContext('2d').getImageData(0, 0, t.canvas.width, t.canvas.height),
        name: t.name,
      };
    }
    // also snapshot layers order if pixel mode
    if (AppState.currentView === 'pixel') {
      snap.__layers = (AppState.layers || []).map(l => ({
        id: l.id,
        name: l.name,
        visible: l.visible,
        locked: l.locked,
        opacity: l.opacity,
        alphaLock: l.alphaLock,
      }));
      snap.__activeLayerId = AppState.activeLayerId;
      snap.__guiWidgets = JSON.parse(JSON.stringify(AppState.guiWidgets || []));
      snap.__guiSelectedId = AppState.guiSelectedId || null;
    }
    if (AppState.currentView === 'block') {
      snap.__faces = { ...(AppState.faces || {}) };
      snap.__activeFace = AppState.activeFace;
    }
    return snap;
  }
  if (AppState.currentView === 'creature') {
    // Deep clone creature parts
    return JSON.parse(JSON.stringify({
      parts: AppState.creatureParts,
      activePartId: AppState.activePartId,
      textures: {}, // would need texture snapshot too
    }));
  }
  return null;
}

function restoreSnapshot(snap) {
  if (!snap) return;
  if (AppState.currentView === 'pixel' || AppState.currentView === 'block') {
    const textures = AppState.textures || {};
    for (const id in snap) {
      if (id.startsWith('__')) continue;
      const t = textures[id];
      if (t) {
        t.canvas.getContext('2d').putImageData(snap[id].data, 0, 0);
        t.name = snap[id].name;
      }
    }
    if (snap.__layers && AppState.layers) {
      // Update layer meta (not the data which is in textures)
      snap.__layers.forEach((l, i) => {
        const existing = AppState.layers.find(x => x.id === l.id);
        if (existing) {
          Object.assign(existing, l);
        }
      });
    }
    if (snap.__faces) {
      AppState.faces = snap.__faces;
    }
    if (snap.__activeLayerId) {
      AppState.activeLayerId = snap.__activeLayerId;
    }
    if (snap.__guiWidgets) {
      AppState.guiWidgets = JSON.parse(JSON.stringify(snap.__guiWidgets));
      AppState.guiSelectedId = snap.__guiSelectedId || null;
    }
    if (snap.__activeFace !== undefined) {
      AppState.activeFace = snap.__activeFace;
    }
  }
}

function pushUndo() {
  AppState.dirty = true;
  const snap = snapshotCurrentEditor();
  if (!snap) return;
  UndoStack.past.push(snap);
  if (UndoStack.past.length > 50) UndoStack.past.shift();
  UndoStack.future.length = 0;
}

function undo() {
  if (UndoStack.past.length === 0) return false;
  const current = snapshotCurrentEditor();
  if (current) UndoStack.future.push(current);
  const prev = UndoStack.past.pop();
  restoreSnapshot(prev);
  setState({});
  return true;
}

function redo() {
  if (UndoStack.future.length === 0) return false;
  const current = snapshotCurrentEditor();
  if (current) UndoStack.past.push(current);
  const next = UndoStack.future.pop();
  restoreSnapshot(next);
  setState({});
  return true;
}

function clearUndoStack() {
  UndoStack.past.length = 0;
  UndoStack.future.length = 0;
}

// ---------- Helpers ----------

function uid() {
  return Math.random().toString(36).substring(2, 10);
}

function createPixelCanvas(width, height, transparent = true) {
  const c = document.createElement('canvas');
  c.width = width;
  c.height = height;
  const ctx = c.getContext('2d');
  ctx.imageSmoothingEnabled = false;
  if (!transparent) {
    ctx.fillStyle = '#ffffff';
    ctx.fillRect(0, 0, width, height);
  }
  return c;
}

function copyCanvas(src) {
  const c = document.createElement('canvas');
  c.width = src.width;
  c.height = src.height;
  const ctx = c.getContext('2d');
  ctx.imageSmoothingEnabled = false;
  ctx.drawImage(src, 0, 0);
  return c;
}

// Load PNG from file -> canvas
function loadImageToCanvas(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = (e) => {
      const img = new Image();
      img.onload = () => {
        const c = document.createElement('canvas');
        c.width = img.width;
        c.height = img.height;
        const ctx = c.getContext('2d');
        ctx.imageSmoothingEnabled = false;
        ctx.drawImage(img, 0, 0);
        resolve(c);
      };
      img.onerror = reject;
      img.src = e.target.result;
    };
    reader.onerror = reject;
    reader.readAsDataURL(file);
  });
}

// Canvas to PNG blob
function canvasFromDataUrl(dataUrl) {
  return new Promise((resolve, reject) => {
    if (!dataUrl) {
      resolve(createPixelCanvas(16, 16));
      return;
    }
    const img = new Image();
    img.onload = () => {
      const c = document.createElement('canvas');
      c.width = img.width;
      c.height = img.height;
      const ctx = c.getContext('2d');
      ctx.imageSmoothingEnabled = false;
      ctx.drawImage(img, 0, 0);
      resolve(c);
    };
    img.onerror = reject;
    img.src = dataUrl;
  });
}

function canvasToDataUrl(canvas) {
  if (!canvas) return null;
  try {
    return canvas.toDataURL('image/png');
  } catch (e) {
    return null;
  }
}

function clearEditorState() {
  AppState.layers = null;
  AppState.textures = null;
  AppState.faces = null;
  AppState.activeLayerId = null;
  AppState.activeFace = null;
  AppState.creatureParts = null;
  AppState.creatureAnimations = null;
  AppState.guiWidgets = [];
  AppState.guiSelectedId = null;
  AppState.guiMode = false;
  AppState.guiSelectionLabel = null;
  AppState.dirty = false;
  clearUndoStack();
}

function serializeEditorState() {
  const project = AppState.project;
  if (!project) return null;
  const layers = (AppState.layers || []).map(l => ({
    id: l.id,
    name: l.name,
    visible: l.visible,
    locked: l.locked,
    opacity: l.opacity,
    alphaLock: l.alphaLock,
    dataUrl: canvasToDataUrl(l.texture?.canvas || l.canvas),
  }));
  const faces = {};
  if (AppState.faces) {
    for (const id of Object.keys(AppState.faces)) {
      const f = AppState.faces[id];
      faces[id] = {
        id: f.id,
        name: f.name,
        textureId: f.textureId,
        width: f.width,
        height: f.height,
        dataUrl: canvasToDataUrl(f.canvas),
      };
    }
  }
  const creatureParts = (AppState.creatureParts || []).map(p => ({
    id: p.id,
    name: p.name,
    type: p.type,
    parent: p.parent,
    pos: p.pos,
    rot: p.rot,
    scale: p.scale,
    pivot: p.pivot,
    size: p.size,
    visible: p.visible,
    texture: {
      name: p.texture?.name,
      width: p.texture?.width,
      height: p.texture?.height,
      offset: p.texture?.offset,
      dataUrl: canvasToDataUrl(p.texture?.canvas),
    },
  }));
  return {
    version: 1,
    view: AppState.currentView,
    project: { ...project, updatedAt: Date.now() },
    layers: layers.length ? layers : null,
    activeLayerId: AppState.activeLayerId || null,
    faces: Object.keys(faces).length ? faces : null,
    activeFace: AppState.activeFace || null,
    creature: creatureParts.length ? {
      parts: creatureParts,
      animations: AppState.creatureAnimations || {},
      activePartId: AppState.activePartId || null,
    } : null,
    guiWidgets: AppState.guiWidgets || [],
    guiMode: !!AppState.guiMode,
    guiSelectedId: AppState.guiSelectedId || null,
  };
}

async function hydrateEditorState(payload) {
  clearEditorState();
  if (!payload) return;
  if (payload.layers && payload.layers.length) {
    const layers = [];
    for (const l of payload.layers) {
      const canvas = await canvasFromDataUrl(l.dataUrl);
      layers.push({
        id: l.id,
        name: l.name,
        visible: l.visible !== false,
        locked: !!l.locked,
        opacity: l.opacity ?? 1,
        alphaLock: !!l.alphaLock,
        texture: { canvas, name: l.name },
      });
    }
    AppState.layers = layers;
    AppState.activeLayerId = payload.activeLayerId || layers[0]?.id;
    AppState.textures = {};
    layers.forEach(l => { AppState.textures[l.id] = l.texture; });
  }
  if (payload.faces) {
    const faces = {};
    const shared = {};
    for (const id of Object.keys(payload.faces)) {
      const f = payload.faces[id];
      const key = f.textureId || id;
      if (!shared[key]) shared[key] = await canvasFromDataUrl(f.dataUrl);
      faces[id] = {
        id: f.id || id,
        name: f.name,
        textureId: f.textureId || id,
        width: f.width,
        height: f.height,
        canvas: shared[key],
      };
    }
    AppState.faces = faces;
    AppState.activeFace = payload.activeFace || 'north';
  }
  if (payload.creature && payload.creature.parts) {
    const parts = [];
    for (const p of payload.creature.parts) {
      const canvas = await canvasFromDataUrl(p.texture?.dataUrl);
      parts.push({
        ...p,
        texture: {
          canvas,
          name: p.texture?.name || p.name,
          width: p.texture?.width || canvas.width,
          height: p.texture?.height || canvas.height,
          offset: p.texture?.offset,
        },
      });
    }
    AppState.creatureParts = parts;
    AppState.creatureAnimations = payload.creature.animations || {};
    AppState.activePartId = payload.creature.activePartId || parts[0]?.id;
  }
  if (payload.guiWidgets) {
    AppState.guiWidgets = payload.guiWidgets;
    AppState.guiSelectedId = payload.guiSelectedId || null;
    AppState.guiMode = !!payload.guiMode || (payload.project && payload.project.type === 'gui');
  }
  AppState.dirty = false;
}

function canvasToBlob(canvas) {
  return new Promise((resolve) => {
    canvas.toBlob(resolve, 'image/png');
  });
}

function downloadBlob(blob, filename) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  a.click();
  setTimeout(() => URL.revokeObjectURL(url), 1000);
}

// ---------- Local Storage (Recent Projects) ----------
const STORAGE_KEY = 'mcstudio.recent';

function loadRecentProjects() {
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    return raw ? JSON.parse(raw) : [];
  } catch (e) { return []; }
}

function saveRecentProjects(list) {
  try {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(list.slice(0, 10)));
  } catch (e) {}
}

// ---------- Toast ----------
let toastCounter = 0;
function showToast(message, type = 'info', duration = 2500) {
  const id = ++toastCounter;
  setState(s => { s.toasts.push({ id, message, type }); });
  setTimeout(() => {
    setState(s => { s.toasts = s.toasts.filter(t => t.id !== id); });
  }, duration);
}

// ---------- Minecraft Palette ----------
const MINECRAFT_PALETTE = [
  // Grass / foliage
  '#4C7A34', '#5A8E3C', '#67A044', '#74B24C', '#82C454', '#8FD65C', '#9CE864',
  '#3D6128', '#2E481C', '#559155', '#6BAF6B',
  // Dirt / earth
  '#5C3A1E', '#6E4826', '#80562E', '#926436', '#A4723E', '#B68046', '#7B5A3A',
  '#4A2E18', '#3B2513',
  // Stone
  '#505050', '#5A5A5A', '#646464', '#6E6E6E', '#787878', '#828282', '#8C8C8C',
  '#969696', '#A0A0A0', '#AAAAAA', '#3C3C3C', '#323232',
  // Wood
  '#5D4037', '#6D4C41', '#795548', '#8D6E63', '#A1887F', '#BCAAA4',
  '#3E2723', '#4E342E',
  // Sand
  '#C2B280', '#D4C492', '#E6D6A4', '#F0E0B0', '#B8A878', '#A89868',
  // Water / ice
  '#2E5A8C', '#3D6BA0', '#4C7CB4', '#5B8DC8', '#6A9EDC', '#79AFF0',
  '#1E3A5C', '#10243F',
  // Lava / fire
  '#CC3300', '#E04400', '#F45500', '#FF6600', '#FF8800', '#FFAA00',
  '#FFCC00', '#FFEE00', '#881100', '#550800',
  // Ore / gems
  '#4A90D9', '#5A9FE8', '#6AAEF7', '#E8E8E8', '#C8C8C8', '#A8A8A8',
  '#30D5C8', '#4EE2D5', '#6CEFE2', '#A855F7', '#C084FC', '#E879F9',
  // Reds / nether
  '#8B0000', '#A52A2A', '#B22222', '#CD5C5C', '#DC143C', '#FF4444',
  '#7B1FA2', '#9C27B0', '#BA68C8',
  // Skin tones
  '#F5CBA7', '#E8B894', '#D4A574', '#C49060', '#B07C4C', '#8B5E3C',
  '#6B4226', '#4A2C1A',
  // White / light
  '#FFFFFF', '#EEEEEE', '#DDDDDD', '#CCCCCC', '#BBBBBB', '#AAAAAA',
  '#F0E6D2', '#E8DCC0', '#DED0A8',
  // Black / dark
  '#000000', '#111111', '#222222', '#333333', '#444444', '#555555',
];

// ---- 下載完整原始碼 ----
async function downloadSourceCode() {
  if (typeof JSZip === 'undefined') {
    showToast('JSZip 未載入，請稍後重試', 'error');
    return;
  }

  showToast('正在打包原始碼...', 'info');

  const files = [
    { path: 'index.html', url: 'index.html' },
    { path: 'styles.css', url: 'styles.css' },
    { path: 'src/state.jsx', url: 'src/state.jsx' },
    { path: 'src/pixel-canvas.jsx', url: 'src/pixel-canvas.jsx' },
    { path: 'src/tools.jsx', url: 'src/tools.jsx' },
    { path: 'src/color-picker.jsx', url: 'src/color-picker.jsx' },
    { path: 'src/layers-panel.jsx', url: 'src/layers-panel.jsx' },
    { path: 'src/menubar.jsx', url: 'src/menubar.jsx' },
    { path: 'src/export-panel.jsx', url: 'src/export-panel.jsx' },
    { path: 'src/explorer-panel.jsx', url: 'src/explorer-panel.jsx' },
    { path: 'src/home-view.jsx', url: 'src/home-view.jsx' },
    { path: 'src/pixel-view.jsx', url: 'src/pixel-view.jsx' },
    { path: 'src/block-view.jsx', url: 'src/block-view.jsx' },
    { path: 'src/creature-view.jsx', url: 'src/creature-view.jsx' },
    { path: 'src/gui-mode.jsx', url: 'src/gui-mode.jsx' },
    { path: 'src/app.jsx', url: 'src/app.jsx' },
  ];

  try {
    const zip = new JSZip();

    // 並行 fetch 所有檔案，失敗就跳過
    const results = await Promise.all(
      files.map(async (f) => {
        try {
          const resp = await fetch(f.url, { cache: 'no-cache' });
          if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
          const text = await resp.text();
          return { path: f.path, content: text, ok: true };
        } catch (err) {
          console.warn(`[downloadSourceCode] 無法取得 ${f.path}:`, err.message);
          return { path: f.path, ok: false };
        }
      })
    );

    let count = 0;
    for (const r of results) {
      if (r.ok) {
        zip.file(r.path, r.content);
        count++;
      }
    }

    // 動態產生 package.json
    const packageJson = {
      name: 'mc-studio',
      version: '1.0.0',
      description: 'Minecraft Texture & Model Studio — Minecraft 專用素材與模型工作室',
      main: 'index.html',
      scripts: {
        start: 'python3 -m http.server 8080',
        dev: 'python3 -m http.server 8080',
      },
      keywords: ['minecraft', 'pixel-art', 'texture', 'model'],
      license: 'MIT',
    };
    zip.file('package.json', JSON.stringify(packageJson, null, 2) + '\n');

    // 動態產生 README.md
    const readme = `# MC Studio — Minecraft 素材 & 模型工作室

Minecraft × Aseprite × Blockbench 的專用素材工作室。
讓 Minecraft Java Edition 伺服器開發者可以直接製作資源包素材。

## 功能特色

- **2D 像素編輯器** — 鉛筆、橡皮擦、油漆桶、直線、矩形、橢圓、對稱繪製
- **3D 方塊材質編輯器** — 六面材質即時預覽、接縫檢查、多種方塊變體
- **3D 生物建模器** — 方塊式骨骼建模、關鍵格動畫、7 種生物範本
- **圖層系統** — 多圖層、不透明度、可見性、複製與刪除
- **復原 / 重做** — 最多 50 步復原
- **MC 色板** — Minecraft 經典調色板
- **圖片轉像素圖** — 降取樣、顏色量化、有序抖動、自動外框
- **壓感支援** — 透過 Pointer Events 支援觸控筆壓力感應
- **雙指縮放** — 觸控裝置上雙指手勢縮放平移
- **資源包匯出** — 直接匯出 PNG 與資源包 ZIP

## 檔案結構

\`\`\`
index.html                    入口 HTML
package.json                  專案設定
styles.css                    完整樣式（含響應式與觸控優化）
src/
├── state.jsx                 全域狀態、Undo/Redo、工具函式
├── pixel-canvas.jsx          像素繪圖核心
├── tools.jsx                 工具列 + SVG 圖示組件
├── color-picker.jsx          顏色選擇器面板
├── layers-panel.jsx          圖層面板
├── menubar.jsx               頂部功能表列
├── export-panel.jsx          MC 工具 / 匯出面板
├── explorer-panel.jsx        資源包檔案總管
├── home-view.jsx             首頁 / 歡迎畫面
├── pixel-view.jsx            2D 像素編輯器
├── block-view.jsx            3D 方塊材質編輯器
├── creature-view.jsx         3D 生物建模器
└── app.jsx                   主應用外殼
\`\`\`

## 部署方式

### 方式一：本機執行

\`\`\`bash
python3 -m http.server 8080
\`\`\`

然後在瀏覽器打開 http://localhost:8080

### 方式二：任何靜態網站伺服器

把所有檔案上傳到任何靜態網站託管服務即可：
- GitHub Pages
- Netlify
- Vercel
- Cloudflare Pages
- 或你自己的 Nginx / Apache 伺服器

**注意：** 因為使用了 \`type="text/babel"\` 與 fetch 載入外部 JSX 檔案，
必須透過 HTTP 伺服器存取，直接用 \`file://\` 打開會因為 CORS 政策失敗。

## 依賴說明

所有第三方函式庫皆透過 CDN 載入，無需 npm install：

- **React 18.3.1** — UI 框架
- **React DOM 18.3.1** — DOM 渲染
- **Babel Standalone 7.29.0** — 瀏覽器內 JSX 轉譯
- **Three.js r128** — 3D 渲染（方塊與生物編輯器）
- **JSZip 3.10.1** — ZIP 打包（資源包匯出、原始碼下載）
- **Google Fonts** — Noto Sans TC（繁體中文）、JetBrains Mono（等寬字體）

## 瀏覽器相容性

支援所有現代瀏覽器：
- Chrome / Edge 90+
- Firefox 88+
- Safari 14+

建議使用最新版 Chrome 以獲得最佳效能。

## 快速鍵

| 功能 | 快速鍵 |
|------|--------|
| 鉛筆 | P |
| 橡皮擦 | E |
| 油漆桶 | G |
| 吸管 | I |
| 直線 | L |
| 矩形 | R |
| 橢圓 | O |
| 移動 | V |
| 選取 | M |
| 復原 | Ctrl+Z |
| 重做 | Ctrl+Shift+Z |
| 匯出 PNG | Ctrl+E |
| 放大 | Ctrl + + |
| 縮小 | Ctrl + - |
| 實際大小 | Ctrl + 0 |
| 顯示格線 | ' |
| 播放 / 暫停 | Space |

## License

MIT
`;
    zip.file('README.md', readme);

    // 產生並下載
    const content = await zip.generateAsync({ type: 'blob' });
    downloadBlob(content, 'mc-studio-source.zip');
    showToast(`原始碼已下載（${count} 個檔案）`, 'success');
  } catch (e) {
    console.error(e);
    showToast('打包失敗：' + e.message, 'error');
  }
}

const MC_VERSIONS = [
  { id: '26.2', packFormat: 88 },
  { id: '1.21.11', packFormat: 69 },
  { id: '1.21.4', packFormat: 46 },
  { id: '1.21', packFormat: 34 },
  { id: '1.20.4', packFormat: 22 },
  { id: '1.20.1', packFormat: 15 },
  { id: '1.19.4', packFormat: 13 },
];

function packFormatFor(version) {
  const found = MC_VERSIONS.find(v => v.id === version);
  return found ? found.packFormat : 88;
}

Object.assign(window, {
  AppState,
  setState,
  subscribe,
  pushUndo,
  undo,
  redo,
  clearUndoStack,
  uid,
  createPixelCanvas,
  copyCanvas,
  loadImageToCanvas,
  canvasToBlob,
  downloadBlob,
  loadRecentProjects,
  saveRecentProjects,
  showToast,
  MINECRAFT_PALETTE,
  downloadSourceCode,
  serializeEditorState,
  hydrateEditorState,
  clearEditorState,
  MC_VERSIONS,
  packFormatFor,
});
