(() => {
  'use strict';

  let clamp, clamp01, shineStyle, imageUrlToDataUrl, FLAT_COLORS;

  function ensureShared() {
    if (clamp) return true;
    if (!window.shared) {
      console.error('Wall.js requires shared functions from script.js');
      return false;
    }
    ({ clamp, clamp01, shineStyle, imageUrlToDataUrl, FLAT_COLORS } = window.shared);
    return true;
  }

  const WALL_STATE_KEY = 'wallDesigner:state:v2';
  const WALL_FALLBACK_COLOR = '#e5e7eb';

  let wallState = null;
  let selectedColorIdx = 0; // This should be synced with organic's selectedColorIdx
  let wallToolMode = 'paint';

  // DOM elements
  const wallDisplay = document.getElementById('wall-display');
  const wallPaletteEl = document.getElementById('wall-palette');
  const wallRowsInput = document.getElementById('wall-rows');
  const wallColsInput = document.getElementById('wall-cols');
  const wallPatternSelect = document.getElementById('wall-pattern');
  const wallGridLabel = document.getElementById('wall-grid-label');
  const wallShowWireCb = document.getElementById('wall-show-wire');
  const wallOutlineCb = document.getElementById('wall-outline');
  const wallClearBtn = document.getElementById('wall-clear');
  const wallFillAllBtn = document.getElementById('wall-fill-all');
  const wallUsedPaletteEl = document.getElementById('wall-used-palette');
  const wallRemoveUnusedBtn = document.getElementById('wall-remove-unused');
  const wallReplaceFromSel = document.getElementById('wall-replace-from');
  const wallReplaceToSel = document.getElementById('wall-replace-to');
  const wallReplaceBtn = document.getElementById('wall-replace-btn');
  const wallReplaceMsg = document.getElementById('wall-replace-msg');
  const wallReplaceFromChip = document.getElementById('wall-replace-from-chip');
  const wallReplaceToChip = document.getElementById('wall-replace-to-chip');
  const wallReplaceCount = document.getElementById('wall-replace-count');
  const wallToolPaintBtn = document.getElementById('wall-tool-paint');
  const wallToolEraseBtn = document.getElementById('wall-tool-erase');
  let wallReplaceFromIdx = null;
  let wallReplaceToIdx = null;
  const wallSpacingLabel = document.getElementById('wall-spacing-label');
  const wallSizeLabel = document.getElementById('wall-size-label');
  const wallPaintLinksBtn = document.getElementById('wall-paint-links');
  const wallPaintSmallBtn = document.getElementById('wall-paint-small');
  const wallPaintGapsBtn = document.getElementById('wall-paint-gaps');
  const wallActiveChip = document.getElementById('wall-active-color-chip');
  const wallActiveLabel = document.getElementById('wall-active-color-label');
  
  const patternKey = () => (wallState.pattern === 'x' ? 'x' : 'grid');

  const autoGapColorIdx = () =>
    Number.isInteger(wallState?.activeColorIdx) && wallState.activeColorIdx >= 0
      ? wallState.activeColorIdx
      : 0;

  const ensurePatternStore = () => {
      if (!wallState.patternStore || typeof wallState.patternStore !== 'object') wallState.patternStore = {};
      ['grid', 'x'].forEach(k => {
        if (!wallState.patternStore[k]) {
          wallState.patternStore[k] = { colors: [], customColors: {}, fillGaps: false, showWireframes: false, outline: false };
        }
      });
    };

  const cloneColors = (colors = []) => colors.map(row => Array.isArray(row) ? [...row] : []);

  function saveActivePatternState() {
    ensurePatternStore();
    const key = patternKey();
    wallState.patternStore[key] = {
      colors: cloneColors(wallState.colors),
      customColors: { ...(wallState.customColors || {}) },
      showWireframes: wallState.showWireframes,
      outline: wallState.outline
    };
  }

  function loadPatternState(key) {
    ensurePatternStore();
    const st = wallState.patternStore[key] || {};
    wallState.colors = Array.isArray(st.colors) ? cloneColors(st.colors) : [];
    wallState.customColors = (st.customColors && typeof st.customColors === 'object') ? { ...st.customColors } : {};
    if (typeof st.showWireframes === 'boolean') wallState.showWireframes = st.showWireframes;
    if (typeof st.outline === 'boolean') wallState.outline = st.outline;
  }
  
  function wallDefaultState() {
    // Default to wireframes on so empty cells are visible/clickable.
    return { rows: 7, cols: 9, spacing: 75, bigSize: 52, pattern: 'grid', fillGaps: false, showWireframes: true, outline: true, colors: [], customColors: {}, patternStore: {}, activeColorIdx: 0 };
  }

  // Build FLAT_COLORS locally if shared failed to populate (e.g., palette not ready)
  function ensureFlatColors() {
    if (Array.isArray(FLAT_COLORS) && FLAT_COLORS.length > 0) return;
    if (!Array.isArray(window.PALETTE)) return;
    console.warn('[Wall] FLAT_COLORS missing; rebuilding from window.PALETTE');
    let idx = 0;
    window.PALETTE.forEach(group => {
      (group.colors || []).forEach(c => {
        if (!c?.hex) return;
        const item = { ...c, family: group.family, _idx: idx++ };
        FLAT_COLORS.push(item);
      });
    });
  }

  function loadWallState() {
    const base = wallDefaultState();
    try {
      const saved = JSON.parse(localStorage.getItem(WALL_STATE_KEY));
      if (saved && typeof saved === 'object') {
        base.rows = clamp(saved.rows ?? base.rows, 2, 20);
        base.cols = clamp(saved.cols ?? base.cols, 2, 20);
        base.spacing = 75; // fixed
        base.bigSize = 52; // fixed
        base.pattern = saved.pattern === 'x' ? 'x' : 'grid';
        base.fillGaps = false;
        base.showWireframes = saved.showWireframes !== false;
        base.patternStore = (saved.patternStore && typeof saved.patternStore === 'object') ? saved.patternStore : {};
        base.customColors = (saved.customColors && typeof saved.customColors === 'object') ? saved.customColors : {};
        if (Number.isInteger(saved.activeColorIdx)) base.activeColorIdx = saved.activeColorIdx;
        if (Array.isArray(saved.colors)) base.colors = cloneColors(saved.colors);
        if (typeof saved.outline === 'boolean') base.outline = saved.outline;
      }
    } catch {}
    return base;
  }
  function saveWallState() {
    try { localStorage.setItem(WALL_STATE_KEY, JSON.stringify(wallState)); } catch {}
  }
  function ensureWallGridSize(rows, cols) {
    const r = clamp(Math.round(rows || 0), 2, 20);
    const c = clamp(Math.round(cols || 0), 2, 20);
    wallState.rows = r;
    wallState.cols = c;

    const mainRows = wallState.pattern === 'grid' ? Math.max(1, r - 1) : r;
    const mainCols = wallState.pattern === 'grid' ? Math.max(1, c - 1) : c;

    if (!Array.isArray(wallState.colors)) wallState.colors = [];
    while (wallState.colors.length < mainRows) wallState.colors.push([]);
    if (wallState.colors.length > mainRows) wallState.colors.length = mainRows;
    for (let i = 0; i < mainRows; i++) {
      const row = wallState.colors[i] || [];
      while (row.length < mainCols) row.push(-1);
      if (row.length > mainCols) row.length = mainCols;
      wallState.colors[i] = row;
    }

    if (!wallState.customColors || typeof wallState.customColors !== 'object') wallState.customColors = {};
    const keys = Object.keys(wallState.customColors);
    keys.forEach(k => {
      const parts = k.split('-');
      const type = parts[0];
      const parseRC = (ri, ci) => ({ rVal: parseInt(ri, 10), cVal: parseInt(ci, 10) });
      if (type === 'g' && (parts[1] === 'h' || parts[1] === 'v')) {
        const { rVal, cVal } = parseRC(parts[2], parts[3]);
        if (!Number.isInteger(rVal) || !Number.isInteger(cVal)) { delete wallState.customColors[k]; return; }
        if (parts[1] === 'h' && (rVal >= r || cVal >= c - 1)) delete wallState.customColors[k];
        else if (parts[1] === 'v' && (rVal >= r - 1 || cVal >= c)) delete wallState.customColors[k];
        return;
      }
      if (type === 'f') {
        // f-h/f-v/f-x
        if (parts[1] === 'h' || parts[1] === 'v') {
          const { rVal, cVal } = parseRC(parts[2], parts[3]);
          if (!Number.isInteger(rVal) || !Number.isInteger(cVal)) { delete wallState.customColors[k]; return; }
          if (parts[1] === 'h' && (rVal >= r - 1 || cVal >= c - 1)) delete wallState.customColors[k];
          else if (parts[1] === 'v' && (rVal >= r - 1 || cVal >= c - 1)) delete wallState.customColors[k];
          return;
        }
        if (parts[1] === 'x') {
          const { rVal, cVal } = parseRC(parts[2], parts[3]);
          if (!Number.isInteger(rVal) || !Number.isInteger(cVal)) { delete wallState.customColors[k]; return; }
          if (rVal <= 0 || cVal <= 0 || rVal >= r - 1 || cVal >= c - 1) delete wallState.customColors[k];
          return;
        }
      }
      const { rVal, cVal } = parseRC(parts[1], parts[2]);
      if (!Number.isInteger(rVal) || !Number.isInteger(cVal)) { delete wallState.customColors[k]; return; }
      if (type === 'h' && (rVal >= r || cVal >= c - 1)) delete wallState.customColors[k];
      else if (type === 'v' && (rVal >= r - 1 || cVal >= c)) delete wallState.customColors[k];
      else if (type === 'g' && (rVal >= r - 1 || cVal >= c - 1)) delete wallState.customColors[k];
      else if ((type === 'c' || type.startsWith('l') || type === 'f') && (rVal >= r - 1 || cVal >= c - 1)) delete wallState.customColors[k];
    });
  }
  const wallColorMeta = (idx) => {
    const meta = (Number.isInteger(idx) && idx >= 0 && FLAT_COLORS[idx]) ? FLAT_COLORS[idx] : { hex: WALL_FALLBACK_COLOR };
    return meta;
  };
  
  async function buildWallSvgPayload(forExport = false, customColorsOverride = null) {
    ensureFlatColors();
    const customColors = customColorsOverride || wallState.customColors;

    if (!ensureShared()) throw new Error('Wall designer shared helpers missing.');
    if (!wallState) wallState = loadWallState();
    ensurePatternStore();
    ensureWallGridSize(wallState.rows, wallState.cols);
    const rows = wallState.rows;
    const cols = wallState.cols;
    const r11 = Math.max(12, (Number(wallState.bigSize) || 54) / 2);
    const r5 = Math.max(6, Math.round(r11 * 0.42));
    const isGrid = wallState.pattern === 'grid';
    const isX = wallState.pattern === 'x';
    
    const spacingBase = Number(wallState.spacing) || 75;
    const spacing = clamp(
      isGrid
        ? Math.max(2 * r11 + r5 * 0.15, spacingBase * 0.7)
        : spacingBase,
      30,
      140
    ); // tighter for grid, nearly touching
    const bigR = r11;
    const smallR = r5;
    
    const linkDims = { rx: bigR * 0.8, ry: bigR * 0.6 }; // Fatter oval
    const fiveInchDims = { rx: smallR, ry: smallR };

    const labelPad = 30;
    const margin = Math.max(bigR + smallR + 18, 28);
    const offsetX = margin + labelPad;
    const offsetY = margin + labelPad;
    const showWireframes = !!wallState.showWireframes;
    const showOutline = !!wallState.outline;
    const colSpacing = spacing;
    const rowStep = spacing;
    const showGaps = false;

    const uniqueImages = new Set();
    wallState.colors.forEach(row => row.forEach(idx => {
      const meta = wallColorMeta(idx);
      if (meta.image) uniqueImages.add(meta.image);
    }));
    Object.values(customColors || {}).forEach(idx => {
      const meta = wallColorMeta(idx);
      if (meta.image) uniqueImages.add(meta.image);
    });

    const dataUrlMap = new Map();
    if (forExport && uniqueImages.size) {
      await Promise.all([...uniqueImages].map(async (url) => dataUrlMap.set(url, await imageUrlToDataUrl(url))));
    }

    const defs = [];
    const patterns = new Map();
    const SVG_PATTERN_ZOOM = 2.5;
    const offset = (1 - SVG_PATTERN_ZOOM) / 2;
    const ensurePattern = (meta) => {
      if (!meta?.image) return null;
      const key = `${meta.image}|${meta.hex}`;
      if (patterns.has(key)) return patterns.get(key);
      const href = forExport ? (dataUrlMap.get(meta.image) || null) : meta.image;
      if (!href) return null;
      const id = `wallp-${patterns.size}`;
      patterns.set(key, id);
      defs.push(`<pattern id="${id}" patternContentUnits="objectBoundingBox" width="1" height="1"><image href="${href}" x="${offset}" y="${offset}" width="${SVG_PATTERN_ZOOM}" height="${SVG_PATTERN_ZOOM}" preserveAspectRatio="xMidYMid slice" /></pattern>`);
      return id;
    };

    const shadowFilters = new Map();
    const ensureShadowFilter = (dx, dy, blurPx, alpha) => {
      const key = `${dx}|${dy}|${blurPx}|${alpha}`;
      if (!shadowFilters.has(key)) {
        const id = `wall-shadow-${shadowFilters.size}`;
        const stdDev = Math.max(0.01, blurPx * 0.5);
        const clampedAlpha = clamp01(alpha);
        const flood = `<feFlood flood-color="#000000" flood-opacity="${clampedAlpha}" />`;
        const blur = `<feGaussianBlur in="SourceAlpha" stdDeviation="${stdDev}" result="blur" />`;
        const offsetNode = `<feOffset dx="${dx}" dy="${dy}" in="blur" result="shadow" />`;
        const composite = `<feComposite in="shadow" in2="SourceAlpha" operator="in" result="shadow" />`;
        const merge = `<feMerge><feMergeNode in="shadow" /><feMergeNode in="SourceGraphic" /></feMerge>`;
        defs.push(`<filter id="${id}" x="-50%" y="-50%" width="200%" height="200%" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">${flood}${blur}${offsetNode}${composite}${merge}</filter>`);
        shadowFilters.set(key, id);
      }
      return shadowFilters.get(key);
    };
    const bigShadow = ensureShadowFilter(0, 3, 8, 0.18);
    const smallShadow = ensureShadowFilter(0, 2, 4, 0.14);
    const shineShadow = ensureShadowFilter(0, 0, 3, 0.08);

    const positions = new Map();
    let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
    for (let r = 0; r < rows; r++) {
      for (let c = 0; c < cols; c++) {
        const gx = offsetX + c * colSpacing;
        const gy = offsetY + r * rowStep;
        positions.set(`${r}-${c}`, { x: gx, y: gy });
        minX = Math.min(minX, gx - bigR);
        maxX = Math.max(maxX, gx + bigR);
        minY = Math.min(minY, gy - bigR);
        maxY = Math.max(maxY, gy + bigR);
      }
    }

    const width = maxX + margin;
    const height = maxY + margin;
    const vb = `0 0 ${width} ${height}`;
    const smallNodes = [];
    const bigNodes = [];
    const labels = [];
    
    for (let c = 0; c < cols; c++) {
      const x = offsetX + c * colSpacing;
      labels.push(`<text x="${x}" y="${labelPad - 6}" text-anchor="middle" font-size="18" font-family="Inter, system-ui, -apple-system, sans-serif" fill="#111827">${c + 1}</text>`);
    }
    for (let r = 0; r < rows; r++) {
      const y = offsetY + r * rowStep;
      labels.push(`<text x="${labelPad - 8}" y="${y + 6}" text-anchor="end" font-size="18" font-family="Inter, system-ui, -apple-system, sans-serif" fill="#111827">${r + 1}</text>`);
    }

    const customOverride = (key) => {
      const raw = customColors?.[key];
      const parsed = Number.isInteger(raw) ? raw : Number.parseInt(raw, 10);
      if (parsed === -1) return { mode: 'empty' };
      if (Number.isInteger(parsed) && parsed >= 0) {
        return { mode: 'color', idx: normalizeColorIdx(parsed) };
      }
      return { mode: 'auto' };
    };

    // Helper to create a shine ellipse with coordinates relative to (0,0)
    const shineNodeRelative = (rx, ry, hex, rot = -20) => {
        const shine = shineStyle(hex || WALL_FALLBACK_COLOR);
        const sx_relative = -rx * 0.25;
        const sy_relative = -ry * 0.25;
        const shineRx = rx * 0.48;
        const shineRy = ry * 0.28;
        const stroke = shine.stroke ? `stroke="${shine.stroke}" stroke-width="1"` : '';
        const shineFilter = shineShadow ? `filter="url(#${shineShadow})"` : '';
        return `<ellipse cx="${sx_relative}" cy="${sy_relative}" rx="${shineRx}" ry="${shineRy}" fill="${shine.fill}" opacity="${shine.opacity ?? 1}" transform="rotate(${rot} ${sx_relative} ${sy_relative})" ${stroke}${shineFilter} />`;
    };

    if (isGrid) {
      for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
          const pos = positions.get(`${r}-${c}`);
          const keyId = `p-${r}-${c}`;
          const override = customOverride(keyId);
          const customIdx = override.mode === 'color' ? override.idx : null;
          const isEmpty = override.mode === 'empty' || customIdx === null;
          const invisible = isEmpty && !showWireframes;
          const hitFill = 'rgba(0,0,0,0.001)';
          
          const meta = wallColorMeta(customIdx);
          const patId = ensurePattern(meta);
          const fill = invisible ? hitFill : (isEmpty ? hitFill : (patId ? `url(#${patId})` : meta.hex));
          const stroke = invisible ? 'none' : (isEmpty ? (showWireframes ? '#cbd5e1' : 'none') : (showOutline ? '#111827' : 'none'));
          const strokeW = invisible ? 0 : (isEmpty ? (showWireframes ? 1.4 : 0) : (showOutline ? 0.6 : 0));
          const filter = (isEmpty || invisible) ? '' : `filter="url(#${smallShadow})"`;
          const shine = isEmpty ? '' : shineNodeRelative(fiveInchDims.rx, fiveInchDims.ry, meta.hex);

          smallNodes.push(`<g data-wall-cell="1" data-wall-key="${keyId}" style="cursor:pointer; pointer-events:all;" transform="translate(${pos.x},${pos.y})">
            <circle cx="0" cy="0" r="${fiveInchDims.rx}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeW}" ${filter} pointer-events="all" />
            ${shine}
          </g>`);
        }
      }
      
      // Gap 11" balloons between centers (horizontal/vertical midpoints) inside the grid (exclude only right edge for horizontals, bottom edge for verticals)
      for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols - 1; c++) {
          const p1 = positions.get(`${r}-${c}`);
          const p2 = positions.get(`${r}-${c+1}`);
          const mid = { x: (p1.x + p2.x) / 2, y: p1.y };
          
          const keyId = `h-${r}-${c}`;
          const override = customOverride(keyId);
          const customIdx = override.mode === 'color' ? override.idx : null;
          const isEmpty = override.mode === 'empty' || customIdx === null;
          const invisible = isEmpty && !showWireframes;
          const hitFill = 'rgba(0,0,0,0.001)';

          const meta = wallColorMeta(customIdx);
          const patId = ensurePattern(meta);
          const fill = invisible ? hitFill : (isEmpty ? hitFill : (patId ? `url(#${patId})` : meta.hex));
          console.log(`h-r-c: keyId: ${keyId}, customIdx: ${customIdx}, isEmpty: ${isEmpty}, invisible: ${invisible}, fill: ${fill}, meta:`, meta);
          const stroke = invisible ? 'none' : (isEmpty ? '#cbd5e1' : (showOutline ? '#111827' : 'none'));
          const strokeW = invisible ? 0 : (isEmpty ? 1.4 : (showOutline ? 0.6 : 0));
          const filter = invisible || isEmpty ? '' : `filter="url(#${bigShadow})"`;
          const shine = isEmpty ? '' : shineNodeRelative(linkDims.rx, linkDims.ry, meta.hex);

          bigNodes.push(`<g data-wall-cell="1" data-wall-key="${keyId}" style="cursor:pointer; pointer-events:all;" transform="translate(${mid.x},${mid.y})">
            <ellipse cx="0" cy="0" rx="${linkDims.rx}" ry="${linkDims.ry}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeW}" ${filter} pointer-events="all" />
            ${shine}
          </g>`);
        }
      }

      for (let r = 0; r < rows - 1; r++) {
        for (let c = 0; c < cols; c++) {
          const p1 = positions.get(`${r}-${c}`);
          const p2 = positions.get(`${r+1}-${c}`);
          const mid = { x: p1.x, y: (p1.y + p2.y) / 2 };

          const keyId = `v-${r}-${c}`;
          const override = customOverride(keyId);
          const customIdx = override.mode === 'color' ? override.idx : null;
          const isEmpty = override.mode === 'empty' || customIdx === null;
          const invisible = isEmpty && !showWireframes;
          const hitFill = 'rgba(0,0,0,0.001)';
          
          const meta = wallColorMeta(customIdx);
          const patId = ensurePattern(meta);
          const fill = invisible ? hitFill : (isEmpty ? hitFill : (patId ? `url(#${patId})` : meta.hex));
          const stroke = invisible ? 'none' : (isEmpty ? '#cbd5e1' : (showOutline ? '#111827' : 'none'));
          const strokeW = invisible ? 0 : (isEmpty ? 1.4 : (showOutline ? 0.6 : 0));
          const filter = invisible || isEmpty ? '' : `filter="url(#${bigShadow})"`;
          const shine = isEmpty ? '' : shineNodeRelative(linkDims.rx, linkDims.ry, meta.hex);
          
          bigNodes.push(`<g data-wall-cell="1" data-wall-key="${keyId}" style="cursor:pointer; pointer-events:all;" transform="translate(${mid.x},${mid.y}) rotate(90)">
            <ellipse cx="0" cy="0" rx="${linkDims.rx}" ry="${linkDims.ry}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeW}" ${filter} pointer-events="all" />
            ${shine}
          </g>`);
        }
      }
      // Gap 11" balloons at square centers
      for (let r = 0; r < rows - 1; r++) {
        for (let c = 0; c < cols - 1; c++) {
          const pTL = positions.get(`${r}-${c}`);
          const pBR = positions.get(`${r+1}-${c+1}`);
          const center = { x: (pTL.x + pBR.x) / 2, y: (pTL.y + pBR.y) / 2 };
          const gapKey = `g-${r}-${c}`;
          const override = customOverride(gapKey);
          const gapIdx = override.mode === 'color'
            ? override.idx
            : (override.mode === 'empty' ? null : (showGaps ? autoGapColorIdx() : null));
          const isEmpty = gapIdx === null;
          const hitFill = 'rgba(0,0,0,0.001)';
          // Hide wireframes for 11" gap balloons; keep them clickable with a hit target.
          const invisible = isEmpty;
          const meta = wallColorMeta(gapIdx);
          const patId = ensurePattern(meta);
          const fill = invisible ? hitFill : (patId ? `url(#${patId})` : meta.hex);
          const stroke = invisible || isEmpty ? 'none' : (showOutline ? '#111827' : 'none');
          const strokeW = invisible || isEmpty ? 0 : (showOutline ? 0.6 : 0);
          const filter = invisible || isEmpty ? '' : `filter="url(#${bigShadow})"`;
          const rGap = bigR * 0.82; // slightly smaller 11" gap balloon
          const shineGap = isEmpty ? '' : shineNodeRelative(rGap, rGap, meta.hex);
          bigNodes.push(`<g data-wall-gap="1" data-wall-key="${gapKey}" style="cursor:pointer; pointer-events:all; cursor:crosshair;" transform="translate(${center.x},${center.y})">
            <circle cx="0" cy="0" r="${rGap}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeW}" ${filter} pointer-events="all" />
            ${shineGap}
          </g>`);
        }
      }
    } else if (isX) {
      for (let r = 0; r < rows - 1; r++) {
        for (let c = 0; c < cols - 1; c++) {
          const p1 = positions.get(`${r}-${c}`);
          const p2 = positions.get(`${r}-${c+1}`);
          const p3 = positions.get(`${r+1}-${c}`);
          const p4 = positions.get(`${r+1}-${c+1}`);
          const center = { x: (p1.x + p4.x) / 2, y: (p1.y + p4.y) / 2 };

          const centerKey = `c-${r}-${c}`;
          const centerOverride = customOverride(centerKey);
          const centerCustomIdx = centerOverride.mode === 'color' ? centerOverride.idx : null;
          const centerIsEmpty = centerOverride.mode === 'empty' || centerCustomIdx === null;
          const invisible = centerIsEmpty && !showWireframes;
          
          const meta = wallColorMeta(centerCustomIdx);
          const patId = ensurePattern(meta);
          const fill = invisible ? 'rgba(0,0,0,0.001)' : (centerIsEmpty ? 'none' : (patId ? `url(#${patId})` : meta.hex));
          console.log(`c-r-c: keyId: ${centerKey}, customIdx: ${centerCustomIdx}, isEmpty: ${centerIsEmpty}, invisible: ${invisible}, fill: ${fill}, meta:`, meta);
          const stroke = invisible ? 'none' : (centerIsEmpty ? '#cbd5e1' : (showOutline ? '#111827' : 'none'));
          const strokeW = invisible ? 0 : (centerIsEmpty ? 1.4 : (showOutline ? 0.6 : 0));
          const filter = centerIsEmpty || invisible ? '' : `filter="url(#${smallShadow})"`;
          const shine = centerIsEmpty ? '' : shineNodeRelative(fiveInchDims.rx, fiveInchDims.ry, meta.hex);

          smallNodes.push(`<g data-wall-cell="1" data-wall-key="${centerKey}" style="cursor:pointer; pointer-events:all;" transform="translate(${center.x},${center.y})">
            <circle cx="0" cy="0" r="${fiveInchDims.rx}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeW}" ${filter} pointer-events="all" />
            ${shine}
          </g>`);
          
          const targets = [p1, p2, p4, p3];
          const linkKeys = [`l1-${r}-${c}`, `l2-${r}-${c}`, `l3-${r}-${c}`, `l4-${r}-${c}`];

          for (let i = 0; i < 4; i++) {
            const target = targets[i];
            const mid = { x: (center.x + target.x) / 2, y: (center.y + target.y) / 2 };
            const angle = Math.atan2(target.y - center.y, target.x - center.x) * 180 / Math.PI;

            const linkKey = linkKeys[i];
            const linkOverride = customOverride(linkKey);
            const linkCustomIdx = linkOverride.mode === 'color' ? linkOverride.idx : null;
            const linkIsEmpty = linkOverride.mode === 'empty' || linkCustomIdx === null;

            const invisibleLink = linkIsEmpty && !showWireframes;

            const meta = wallColorMeta(linkCustomIdx);
            const patId = ensurePattern(meta);
            const fill = invisibleLink ? 'rgba(0,0,0,0.001)' : (linkIsEmpty ? 'none' : (patId ? `url(#${patId})` : meta.hex));
            console.log(`l#-r-c: keyId: ${linkKey}, customIdx: ${linkCustomIdx}, isEmpty: ${linkIsEmpty}, invisible: ${invisibleLink}, fill: ${fill}, meta:`, meta);
            // Outline only when filled; light wireframe when empty and wireframes shown.
            const stroke = invisibleLink ? 'none' : (linkIsEmpty ? (showWireframes ? '#cbd5e1' : 'none') : (showOutline ? '#111827' : 'none'));
            const strokeW = invisibleLink ? 0 : (linkIsEmpty ? (showWireframes ? 1.2 : 0) : (showOutline ? 0.8 : 0));
            const filter = invisibleLink || linkIsEmpty ? '' : `filter="url(#${bigShadow})"`;
            const shine = linkIsEmpty ? '' : shineNodeRelative(linkDims.rx, linkDims.ry, meta.hex);

            bigNodes.push(`<g data-wall-cell="1" data-wall-key="${linkKey}" style="cursor:pointer; pointer-events:all;" transform="translate(${mid.x},${mid.y}) rotate(${angle})">
              <ellipse cx="0" cy="0" rx="${linkDims.rx}" ry="${linkDims.ry}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeW}" ${filter} pointer-events="all" />
              ${shine}
            </g>`);
          }
        }
      }

      // Gap 11" balloons between centers (horizontal/vertical midpoints) across the X pattern
      // Keep gaps at midpoints; skip the top row and the far-left column.
      const maxCH = Math.max(0, cols - 1);
      // Diamond 5" fillers at original grid intersections (skip border)
      for (let r = 1; r < rows - 1; r++) {
        for (let c = 1; c < cols - 1; c++) {
          const pos = positions.get(`${r}-${c}`);
          if (!pos) continue;
          const fillerKey = `f-x-${r}-${c}`;
          const fillerOverride = customOverride(fillerKey);
          const fillerIdx = fillerOverride.mode === 'color' ? fillerOverride.idx : null;
          const fillerEmpty = fillerOverride.mode === 'empty' || fillerIdx === null;
          const fillerInvisible = fillerEmpty && !showWireframes;
          const fillerMeta = wallColorMeta(fillerIdx);
          const fillerPat = ensurePattern(fillerMeta);
          const fillerFill = fillerInvisible ? 'rgba(0,0,0,0.001)' : (fillerEmpty ? (showWireframes ? 'none' : 'rgba(0,0,0,0.001)') : (fillerPat ? `url(#${fillerPat})` : fillerMeta.hex));
          const fillerStroke = fillerInvisible ? 'none' : (fillerEmpty ? (showWireframes ? '#cbd5e1' : 'none') : 'none');
          const fillerStrokeW = fillerInvisible ? 0 : (fillerEmpty ? (showWireframes ? 1.2 : 0) : 0);
          const fillerFilter = fillerInvisible || fillerEmpty ? '' : `filter="url(#${smallShadow})"`;
          const fillerShine = fillerEmpty ? '' : shineNodeRelative(fiveInchDims.rx, fiveInchDims.ry, fillerMeta.hex);
          smallNodes.push(`<g data-wall-cell="1" data-wall-key="${fillerKey}" style="cursor:pointer; pointer-events:all;" transform="translate(${pos.x},${pos.y})">
            <circle cx="0" cy="0" r="${fiveInchDims.rx}" fill="${fillerFill}" stroke="${fillerStroke}" stroke-width="${fillerStrokeW}" ${fillerFilter} pointer-events="all" />
            ${fillerShine}
          </g>`);
        }
      }
      for (let r = 1; r < rows - 1; r++) {
        for (let c = 0; c < maxCH; c++) {
          const p1 = positions.get(`${r}-${c}`);
          const p2 = positions.get(`${r}-${c+1}`);
          const mid = { x: (p1.x + p2.x) / 2, y: p1.y };
          const key = `g-h-${r}-${c}`;
          const override = customOverride(key);
          const gapIdx = override.mode === 'color'
            ? override.idx
            : (override.mode === 'empty' ? null : (showGaps ? autoGapColorIdx() : null));
          const isEmpty = gapIdx === null;
          const meta = wallColorMeta(gapIdx);
          const patId = ensurePattern(meta);
          const invisible = isEmpty && !showGaps;
          const fill = invisible ? 'rgba(0,0,0,0.001)' : (isEmpty ? 'none' : (patId ? `url(#${patId})` : meta.hex));
          const stroke = invisible ? 'none' : (isEmpty ? '#cbd5e1' : (showOutline ? '#111827' : 'none'));
          const strokeW = invisible ? 0 : (isEmpty ? 1.4 : (showOutline ? 0.6 : 0));
          const filter = invisible || isEmpty ? '' : `filter="url(#${bigShadow})"`;
          const rGap = bigR * 0.82;
          const shineGap = isEmpty ? '' : shineNodeRelative(rGap, rGap, meta.hex);
          bigNodes.push(`<g data-wall-gap="1" data-wall-key="${key}" style="cursor:pointer; pointer-events:all; cursor:crosshair;" transform="translate(${mid.x},${mid.y})">
            <circle cx="0" cy="0" r="${rGap}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeW}" ${filter} pointer-events="all" />
            ${shineGap}
          </g>`);
        }
      }
      for (let r = 0; r < rows - 1; r++) {
        for (let c = 1; c < maxCH; c++) { // start at column 1 to keep far-left clear
          const p1 = positions.get(`${r}-${c}`);
          const p2 = positions.get(`${r+1}-${c}`);
          const mid = { x: p1.x, y: (p1.y + p2.y) / 2 };
          const key = `g-v-${r}-${c}`;
          const override = customOverride(key);
          const gapIdx = override.mode === 'color'
            ? override.idx
            : (override.mode === 'empty' ? null : (showGaps ? autoGapColorIdx() : null));
          const isEmpty = gapIdx === null;
          const meta = wallColorMeta(gapIdx);
          const patId = ensurePattern(meta);
          const invisible = isEmpty && !showGaps;
          const fill = invisible ? 'rgba(0,0,0,0.001)' : (isEmpty ? 'none' : (patId ? `url(#${patId})` : meta.hex));
          const stroke = invisible ? 'none' : (isEmpty ? '#cbd5e1' : (showOutline ? '#111827' : 'none'));
          const strokeW = invisible ? 0 : (isEmpty ? 1.4 : (showOutline ? 0.6 : 0));
          const filter = invisible || isEmpty ? '' : `filter="url(#${bigShadow})"`;
          const rGap = bigR * 0.82;
          const shineGap = isEmpty ? '' : shineNodeRelative(rGap, rGap, meta.hex);
          bigNodes.push(`<g data-wall-gap="1" data-wall-key="${key}" style="cursor:pointer; pointer-events:all; cursor:crosshair;" transform="translate(${mid.x},${mid.y})">
            <circle cx="0" cy="0" r="${rGap}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeW}" ${filter} pointer-events="all" />
            ${shineGap}
          </g>`);
        }
      }
    }

    const svgString = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="${vb}" width="${width}" height="${height}" aria-label="Balloon wall">
      <defs>${defs.join('')}</defs>
      <g>${bigNodes.join('')}</g>
      <g>${smallNodes.join('')}</g>
      <g>${labels.join('')}</g>
    </svg>`;

    return { svgString, width, height };
  }


  function wallUsedColors() {
    ensureFlatColors();
    if (!wallState) wallState = loadWallState();
    ensureWallGridSize(wallState.rows, wallState.cols);
    const map = new Map();
    const addIdx = (idx) => {
      if (!Number.isInteger(idx) || idx < 0 || !FLAT_COLORS[idx]) return;
      const meta = FLAT_COLORS[idx];
      const key = idx;
      const entry = map.get(key) || { idx, hex: meta.hex, image: meta.image, name: meta.name, count: 0 };
      entry.count += 1;
      map.set(key, entry);
    };
    wallState.colors.forEach(row => row.forEach(addIdx));
    Object.values(wallState.customColors || {}).forEach(addIdx);
    return Array.from(map.values()).sort((a, b) => b.count - a.count);
  }

  function renderWallUsedPalette() {
    if (!wallUsedPaletteEl) return;
    const used = wallUsedColors();
    wallUsedPaletteEl.innerHTML = '';
    if (!used.length) {
      wallUsedPaletteEl.innerHTML = '<div class="text-xs text-gray-500">No colors yet.</div>';
      populateWallReplaceSelects();
      updateWallReplacePreview();
      return;
    }
    const row = document.createElement('div');
    row.className = 'swatch-row';
    used.forEach(item => {
      const sw = document.createElement('button');
      sw.type = 'button';
      sw.className = 'swatch';
      if (item.image) {
        sw.style.backgroundImage = `url("${item.image}")`;
        sw.style.backgroundSize = `${100 * 2.5}%`;
      } else {
        sw.style.backgroundColor = item.hex;
      }
      sw.title = `${item.name || item.hex} (${item.count})`;
      sw.addEventListener('click', () => {
        if (!Number.isInteger(item.idx)) return;
        setActiveColor(normalizeColorIdx(item.idx));
        renderWallPalette();
        renderWallUsedPalette();
      });
      row.appendChild(sw);
    });
    wallUsedPaletteEl.appendChild(row);
    populateWallReplaceSelects();
    updateWallReplacePreview();
  }

  function populateWallReplaceSelects() {
    ensureFlatColors();
    // "From" = only colors currently used on the wall
    if (wallReplaceFromSel) {
      wallReplaceFromSel.innerHTML = '';
      const used = wallUsedColors();
      used.forEach(u => {
        const opt = document.createElement('option');
        opt.value = String(u.idx);
        opt.textContent = `${u.name || u.hex} (${u.count})`;
        wallReplaceFromSel.appendChild(opt);
      });
      if (used.length) {
        const val = wallReplaceFromIdx ?? used[0].idx;
        wallReplaceFromSel.value = String(val);
        wallReplaceFromIdx = val;
      }
    }
    // "To" = colors from the wall palette
    if (wallReplaceToSel) {
      wallReplaceToSel.innerHTML = '';
      (window.PALETTE || []).forEach(group => {
        (group.colors || []).forEach(c => {
          const idx = FLAT_COLORS.findIndex(fc => fc.name === c.name && fc.hex === c.hex && fc.family === group.family);
          if (idx < 0) return;
          const opt = document.createElement('option');
          opt.value = String(idx);
          opt.textContent = c.name || c.hex;
          wallReplaceToSel.appendChild(opt);
        });
      });
      if (wallReplaceToSel.options.length) {
        const val = wallReplaceToIdx ?? parseInt(wallReplaceToSel.options[0].value, 10);
        wallReplaceToSel.value = String(val);
        wallReplaceToIdx = val;
      }
    }
  }

  const wallSetChip = (chip, meta) => {
    if (!chip) return;
    if (meta?.image) {
      chip.style.backgroundImage = `url("${meta.image}")`;
      chip.style.backgroundColor = meta.hex || WALL_FALLBACK_COLOR;
      chip.style.backgroundSize = `${100 * 2.5}%`;
      chip.style.backgroundPosition = `${(meta.imageFocus?.x ?? 0.5) * 100}% ${(meta.imageFocus?.y ?? 0.5) * 100}%`;
    } else {
      chip.style.backgroundImage = 'none';
      chip.style.backgroundColor = meta?.hex || WALL_FALLBACK_COLOR;
    }
  };

  const wallCountMatches = (idx) => {
    if (!Number.isInteger(idx) || idx < 0) return 0;
    ensureWallGridSize(wallState.rows, wallState.cols);
    let count = 0;
    wallState.colors.forEach(row => row.forEach(v => { if (v === idx) count++; }));
    Object.values(wallState.customColors || {}).forEach(v => { if (Number.isInteger(v) && v === idx) count++; });
    return count;
  };

  const updateWallReplacePreview = () => {
    let fromIdx = Number.isInteger(wallReplaceFromIdx) ? wallReplaceFromIdx : parseInt(wallReplaceFromSel?.value || '-1', 10);
    let toIdx = Number.isInteger(wallReplaceToIdx) ? wallReplaceToIdx : parseInt(wallReplaceToSel?.value || '-1', 10);
    if ((!Number.isInteger(fromIdx) || fromIdx < 0) && wallReplaceFromSel?.options?.length) {
      fromIdx = parseInt(wallReplaceFromSel.options[0].value, 10);
      wallReplaceFromSel.value = String(fromIdx);
      wallReplaceFromIdx = fromIdx;
    }
    if ((!Number.isInteger(toIdx) || toIdx < 0) && wallReplaceToSel?.options?.length) {
      toIdx = parseInt(wallReplaceToSel.options[0].value, 10);
      wallReplaceToSel.value = String(toIdx);
      wallReplaceToIdx = toIdx;
    }
    wallSetChip(wallReplaceFromChip, wallColorMeta(fromIdx));
    wallSetChip(wallReplaceToChip, wallColorMeta(toIdx));
    const cnt = wallCountMatches(fromIdx);
    if (wallReplaceCount) wallReplaceCount.textContent = cnt ? `${cnt} match${cnt === 1 ? '' : 'es'}` : '0 matches';
    return cnt;
  };

  const openWallReplacePicker = (mode = 'from') => {
    const picker = window.openColorPicker;
    if (!picker) return;
    populateWallReplaceSelects();
    if (mode === 'from') {
      const used = wallUsedColors();
      const items = used.map(u => ({
        label: u.name || u.hex,
        metaText: `${u.count} in wall`,
        idx: u.idx
      }));
      if (!items.length) {
        if (wallReplaceMsg) wallReplaceMsg.textContent = 'No colors on the wall yet.';
        return;
      }
      picker({
        title: 'Replace: From color',
        subtitle: 'Pick a color currently used in the wall',
        items,
        onSelect: (item) => {
          if (!wallReplaceFromSel) return;
          wallReplaceFromSel.value = String(item.idx);
          wallReplaceFromIdx = item.idx;
          updateWallReplacePreview();
        }
      });
    } else {
      ensureFlatColors();
      const items = [];
      (window.PALETTE || []).forEach(group => {
        (group.colors || []).forEach(c => {
          const idx = FLAT_COLORS.findIndex(fc => fc.name === c.name && fc.hex === c.hex && fc.family === group.family);
          if (idx >= 0) {
            items.push({
              label: c.name || c.hex,
              metaText: group.family || '',
              idx
            });
          }
        });
      });
      if (!items.length) {
        if (wallReplaceMsg) wallReplaceMsg.textContent = 'No palette colors available.';
        return;
      }
      picker({
        title: 'Replace: To color',
        subtitle: 'Choose any color from the wall palette',
        items,
        onSelect: (item) => {
          if (!wallReplaceToSel) return;
          wallReplaceToSel.value = String(item.idx);
          wallReplaceToIdx = item.idx;
          updateWallReplacePreview();
        }
      });
    }
  };

  // Pick a visible default (first reasonably saturated entry).
  function defaultActiveColorIdx() {
    if (!Array.isArray(FLAT_COLORS) || !FLAT_COLORS.length) return 0;
    const isTooLight = (hex = '') => {
      const h = hex.replace('#', '');
      if (h.length !== 6) return false;
      const r = parseInt(h.slice(0, 2), 16);
      const g = parseInt(h.slice(2, 4), 16);
      const b = parseInt(h.slice(4, 6), 16);
      return (r + g + b) > 640; // avoid near-white/pastel defaults
    };
    const firstVisible = FLAT_COLORS.find(c => c?.hex && !isTooLight(c.hex));
    if (firstVisible) {
      const idx = Number.isInteger(firstVisible._idx) ? firstVisible._idx : FLAT_COLORS.indexOf(firstVisible);
      if (idx >= 0) return idx;
    }
    return 0;
  }

  const normalizeColorIdx = (idx) => {
    const fallback = defaultActiveColorIdx();
    if (!Number.isInteger(idx)) return fallback;
    if (idx < 0) return fallback;
    if (Array.isArray(FLAT_COLORS) && FLAT_COLORS.length > 0 && idx >= FLAT_COLORS.length) {
      return FLAT_COLORS.length - 1;
    }
    return idx;
  };

  function setActiveColor(idx) {
    selectedColorIdx = normalizeColorIdx(idx);
    wallState.activeColorIdx = selectedColorIdx;
    updateWallActiveChip(selectedColorIdx);
    if (window.organic?.setColor) {
      window.organic.setColor(selectedColorIdx);
    } else if (window.organic?.updateCurrentColorChip) {
      window.organic.updateCurrentColorChip(selectedColorIdx);
    }
    saveWallState();
  }

  // Sync the wall's active color with the global/organic selection when available.
  function syncActiveColorFromOrganic() {
    const organicIdx = window.organic?.getColor?.();
    if (!Number.isInteger(organicIdx)) return null;
    const normalized = normalizeColorIdx(organicIdx);
    if (normalized !== selectedColorIdx) {
      selectedColorIdx = normalized;
      if (wallState) wallState.activeColorIdx = normalized;
      saveWallState();
      renderWallPalette();
      updateWallActiveChip(normalized);
    }
    return normalized;
  }

  // Read the current UI-selected color. Prefer the global/organic selection so the active color chip always drives wall clicks.
  // Current active color: prefer organic tab, then wall selection, then stored default.
  function getActiveWallColorIdx() {
    const organicIdx = syncActiveColorFromOrganic();
    if (Number.isInteger(organicIdx)) return organicIdx;
    if (Number.isInteger(selectedColorIdx)) return normalizeColorIdx(selectedColorIdx);
    if (Number.isInteger(wallState?.activeColorIdx)) return normalizeColorIdx(wallState.activeColorIdx);
    return defaultActiveColorIdx();
  }

  // Normalize the stored color for a wall key to either a valid index or null (empty).
  function getStoredColorForKey(key) {
    if (!wallState?.customColors) return null;
    const raw = wallState.customColors[key];
    const parsed = Number.isInteger(raw) ? raw : Number.parseInt(raw, 10);
    if (!Number.isInteger(parsed)) return null;
    if (parsed < 0) return null;
    const val = normalizeColorIdx(parsed);
    if (val < 0) return null;
    wallState.customColors[key] = val; // write back normalized numeric value
    return val;
  }

  function updateWallActiveChip(idx) {
    if (!wallActiveChip || !wallActiveLabel) return;
    ensureFlatColors();
    const meta = wallColorMeta(idx);
    if (meta.image) {
      wallActiveChip.style.backgroundImage = `url("${meta.image}")`;
      const zoom = Math.max(1, meta.imageZoom ?? 2.5);
      wallActiveChip.style.backgroundSize = `${100 * zoom}%`;
      wallActiveChip.style.backgroundPosition = `${(meta.imageFocus?.x ?? 0.5) * 100}% ${(meta.imageFocus?.y ?? 0.5) * 100}%`;
      wallActiveChip.style.backgroundColor = '#fff';
    } else {
      wallActiveChip.style.backgroundImage = 'none';
      wallActiveChip.style.backgroundSize = '';
      wallActiveChip.style.backgroundPosition = '';
      wallActiveChip.style.backgroundColor = meta.hex || WALL_FALLBACK_COLOR;
    }
    wallActiveLabel.textContent = meta.name || meta.hex || '';
  }

  function setWallToolMode(mode) {
    wallToolMode = mode === 'erase' ? 'erase' : 'paint';
    if (wallToolPaintBtn && wallToolEraseBtn) {
      const isErase = wallToolMode === 'erase';
      wallToolPaintBtn.setAttribute('aria-pressed', String(!isErase));
      wallToolEraseBtn.setAttribute('aria-pressed', String(isErase));
      wallToolPaintBtn.classList.toggle('tab-active', !isErase);
      wallToolEraseBtn.classList.toggle('tab-active', isErase);
      wallToolPaintBtn.classList.toggle('tab-idle', isErase);
      wallToolEraseBtn.classList.toggle('tab-idle', !isErase);
    }
  }

  // Paint a specific group of nodes with the active color.
  function paintWallGroup(group) {
    ensureWallGridSize(wallState.rows, wallState.cols);
    const idx = getActiveWallColorIdx();
    if (!Number.isInteger(idx)) return;
    const rows = wallState.rows;
    const cols = wallState.cols;
    const isGrid = wallState.pattern === 'grid';
    const custom = { ...wallState.customColors };
    const set = (key) => { custom[key] = idx; };

    if (group === 'links') {
      if (isGrid) {
        for (let r = 0; r < rows; r++) {
          for (let c = 0; c < cols - 1; c++) set(`h-${r}-${c}`);
        }
        for (let r = 0; r < rows - 1; r++) {
          for (let c = 0; c < cols; c++) set(`v-${r}-${c}`);
        }
      } else {
        for (let r = 0; r < rows - 1; r++) {
          for (let c = 0; c < cols - 1; c++) {
            ['l1', 'l2', 'l3', 'l4'].forEach(l => set(`${l}-${r}-${c}`));
          }
        }
      }
    } else if (group === 'small') {
      if (isGrid) {
        for (let r = 0; r < rows; r++) {
          for (let c = 0; c < cols; c++) set(`p-${r}-${c}`);
        }
        wallState.colors = wallState.colors.map(row => row.map(() => idx));
      } else {
        for (let r = 0; r < rows - 1; r++) {
          for (let c = 0; c < cols - 1; c++) {
            set(`c-${r}-${c}`);
            set(`f-x-${r+1}-${c+1}`); // diamond 5" fillers between link crosses
          }
        }
      }
    } else if (group === 'gaps') {
      if (isGrid) {
        for (let r = 0; r < rows - 1; r++) {
          for (let c = 0; c < cols - 1; c++) set(`g-${r}-${c}`);
        }
      } else {
        const maxCH = Math.max(0, cols - 1);
        for (let r = 1; r < rows - 1; r++) {
          for (let c = 0; c < maxCH; c++) set(`g-h-${r}-${c}`);
        }
        for (let r = 0; r < rows - 1; r++) {
          for (let c = 1; c < maxCH; c++) set(`g-v-${r}-${c}`);
        }
      }
    } else if (group === 'filler') {
      if (!isGrid) {
        for (let r = 1; r < rows - 1; r++) {
          for (let c = 1; c < cols - 1; c++) set(`f-x-${r}-${c}`);
        }
      }
    } else {
      return;
    }

    wallState.customColors = custom;
    saveActivePatternState();
    saveWallState();
    renderWall();
  }

  async function renderWall() {
    if (!wallDisplay) return;
    ensureWallGridSize(wallState.rows, wallState.cols);
    if (wallGridLabel) wallGridLabel.textContent = `${wallState.cols} × ${wallState.rows}`;
    try {
      console.info('[Wall] render start');
      const { svgString } = await buildWallSvgPayload(false);
      
      wallDisplay.innerHTML = svgString;

      // Force a reflow to ensure the browser repaints the new SVG.
      void wallDisplay.offsetWidth;
      renderWallUsedPalette();
      updateWallReplacePreview();
      console.info('[Wall] render done');
    } catch (err) {
      console.error('[Wall] render failed', err?.stack || err);
      wallDisplay.innerHTML = `<div class="p-4 text-sm text-red-600">Could not render wall.</div>`;
    }
  }

  function renderWallPalette() {
    if (!wallPaletteEl) return;
    wallPaletteEl.innerHTML = '';
    populateWallReplaceSelects();
    (window.PALETTE || []).forEach(group => {
      const title = document.createElement('div');
      title.className = 'family-title';
      title.textContent = group.family;
      wallPaletteEl.appendChild(title);

      const row = document.createElement('div');
      row.className = 'swatch-row';
      (group.colors || []).forEach(c => {
        const normHex = (c.hex || '').toLowerCase();
        let idx = FLAT_COLORS.findIndex(fc => fc.name === c.name && fc.hex === c.hex && fc.family === group.family);
        if (idx < 0 && window.shared?.HEX_TO_FIRST_IDX?.has(normHex)) {
          idx = window.shared.HEX_TO_FIRST_IDX.get(normHex);
        }
        idx = normalizeColorIdx(idx);
        const sw = document.createElement('button');
        sw.type = 'button';
        sw.className = 'swatch';
        if (c.image) {
          const meta = FLAT_COLORS[idx] || {};
          sw.style.backgroundImage = `url("${c.image}")`;
          sw.style.backgroundSize = `${100 * 2.5}%`;
          sw.style.backgroundPosition = `${(meta.imageFocus?.x ?? 0.5) * 100}% ${(meta.imageFocus?.y ?? 0.5) * 100}%`;
        } else {
          sw.style.backgroundColor = c.hex;
        }
        if (idx === selectedColorIdx) sw.classList.add('active');
        sw.title = c.name;
        sw.addEventListener('click', () => {
          setActiveColor(idx);
          window.organic?.updateCurrentColorChip?.(selectedColorIdx);
          // Also update the global chip explicitly
          if (window.organic?.updateCurrentColorChip) {
            window.organic.updateCurrentColorChip(selectedColorIdx);
          }
          renderWallPalette();
        });
        row.appendChild(sw);
      });
      wallPaletteEl.appendChild(row);
    });
    renderWallUsedPalette();
    updateWallActiveChip(getActiveWallColorIdx());
    updateWallReplacePreview();
  }

  function syncWallInputs() {
    if (!wallState) wallState = wallDefaultState();
    ensureWallGridSize(wallState.rows, wallState.cols);
    if (wallRowsInput) wallRowsInput.value = wallState.rows;
    if (wallColsInput) wallColsInput.value = wallState.cols;
    if (wallSpacingLabel) wallSpacingLabel.textContent = `${wallState.spacing} px (fixed)`;
    if (wallSizeLabel) wallSizeLabel.textContent = `${wallState.bigSize} px (fixed)`;
    if (wallGridLabel) wallGridLabel.textContent = `${wallState.cols} × ${wallState.rows}`;
    if (wallPatternSelect) wallPatternSelect.value = wallState.pattern || 'grid';
    if (wallShowWireCb) wallShowWireCb.checked = wallState.showWireframes !== false;
    if (wallOutlineCb) wallOutlineCb.checked = !!wallState.outline;
  }

  function initWallDesigner() {
    if (!ensureShared()) return;
    ensureFlatColors();
    if (!wallDisplay) return;
    wallState = loadWallState();
    ensurePatternStore();
    loadPatternState(patternKey());
    if (Number.isInteger(wallState.activeColorIdx)) selectedColorIdx = normalizeColorIdx(wallState.activeColorIdx);
    else if (window.organic?.getColor) selectedColorIdx = normalizeColorIdx(window.organic.getColor());
    else selectedColorIdx = defaultActiveColorIdx();
    setActiveColor(selectedColorIdx);
    setWallToolMode('paint');
    loadPatternState(patternKey());
    ensureWallGridSize(wallState.rows, wallState.cols);
    syncWallInputs();
    renderWallPalette();
    renderWall();
    saveActivePatternState();
    saveWallState();

    wallRowsInput?.addEventListener('change', () => {
        const rows = clamp(parseInt(wallRowsInput?.value || '0', 10) || wallState.rows, 2, 20);
        ensureWallGridSize(rows, wallState.cols);
        saveWallState();
        syncWallInputs();
        renderWall();
    });
    wallColsInput?.addEventListener('change',() => {
        const cols = clamp(parseInt(wallColsInput?.value || '0', 10) || wallState.cols, 2, 20);
        ensureWallGridSize(wallState.rows, cols);
        saveWallState();
        syncWallInputs();
        renderWall();
    });
    wallPatternSelect?.addEventListener('change', () => {
      saveActivePatternState();
      wallState.pattern = wallPatternSelect.value === 'x' ? 'x' : 'grid';
      loadPatternState(patternKey());
      ensureWallGridSize(wallState.rows, wallState.cols);
      saveWallState();
      renderWall();
      syncWallInputs();
      renderWallUsedPalette();
      updateWallReplacePreview();
    });
    wallShowWireCb?.addEventListener('change', () => {
      wallState.showWireframes = !!wallShowWireCb.checked;
      saveActivePatternState();
      saveWallState();
      renderWall();
    });
    wallOutlineCb?.addEventListener('change', () => {
      wallState.outline = !!wallOutlineCb.checked;
      saveActivePatternState();
      saveWallState();
      renderWall();
    });
    wallPaintLinksBtn?.addEventListener('click', () => paintWallGroup('links'));
    wallPaintSmallBtn?.addEventListener('click', () => paintWallGroup('small'));
    wallPaintGapsBtn?.addEventListener('click', () => paintWallGroup('gaps'));
    wallReplaceFromSel?.addEventListener('change', updateWallReplacePreview);
    wallReplaceToSel?.addEventListener('change', updateWallReplacePreview);
    wallReplaceFromChip?.addEventListener('click', () => openWallReplacePicker('from'));
    wallReplaceToChip?.addEventListener('click', () => openWallReplacePicker('to'));
    wallToolPaintBtn?.addEventListener('click', () => setWallToolMode('paint'));
    wallToolEraseBtn?.addEventListener('click', () => setWallToolMode('erase'));

    const findWallNode = (el) => {
      let cur = el;
      while (cur && cur !== wallDisplay) {
        if (cur.dataset?.wallKey) return cur;
        cur = cur.parentNode;
      }
      return null;
    };

    const setHoverCursor = (e) => {
      const hit = findWallNode(e.target);
      wallDisplay.style.cursor = hit ? 'crosshair' : 'auto';
    };
    wallDisplay?.addEventListener('pointermove', setHoverCursor);
    wallDisplay?.addEventListener('pointerleave', () => { wallDisplay.style.cursor = 'auto'; });

    wallDisplay.addEventListener('click', (e) => {
      const hit = findWallNode(e.target);
      if (!hit) return;

      const key = hit.dataset.wallKey;
      if (!key) return;

      const activeColor = getActiveWallColorIdx();
      if (!Number.isInteger(activeColor)) return;
      const rawStored = wallState.customColors?.[key];
      const parsedStored = Number.isInteger(rawStored) ? rawStored : Number.parseInt(rawStored, 10);
      const storedColor = Number.isInteger(parsedStored) && parsedStored >= 0 ? normalizeColorIdx(parsedStored) : null;
      const hasStoredColor = Number.isInteger(storedColor) && storedColor >= 0;

      if (e.altKey) {
        if (Number.isInteger(storedColor)) {
          setActiveColor(storedColor);
          renderWallPalette();
          renderWallUsedPalette();
        }
        return;
      }

      // Paint/erase based on tool mode; modifiers still erase.
      const isEraseClick = wallToolMode === 'erase' || e.shiftKey || e.metaKey || e.ctrlKey;
      wallState.customColors[key] = isEraseClick ? -1 : activeColor;

      saveActivePatternState();
      saveWallState();
      renderWall();
    });

    wallClearBtn?.addEventListener('click', () => {
      ensureWallGridSize(wallState.rows, wallState.cols);
      wallState.colors = wallState.colors.map(row => row.map(() => -1));
      wallState.customColors = {};
      // Preserve outline/wireframe toggles; just clear colors.
      wallState.showWireframes = wallState.showWireframes !== false;
      wallState.outline = wallState.outline === true;
      if (wallShowWireCb) wallShowWireCb.checked = wallState.showWireframes;
      if (wallOutlineCb) wallOutlineCb.checked = wallState.outline;
      saveActivePatternState();
      saveWallState();
      renderWall();
    });

    wallFillAllBtn?.addEventListener('click', () => {
      if (window.organic?.getColor) setActiveColor(window.organic.getColor());
      const idx = Number.isInteger(selectedColorIdx) ? selectedColorIdx : 0;
      ensureWallGridSize(wallState.rows, wallState.cols);
      wallState.colors = wallState.colors.map(row => row.map(() => idx));
      const custom = {};
      const rows = wallState.rows;
      const cols = wallState.cols;
      for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols - 1; c++) custom[`h-${r}-${c}`] = idx;
        for (let c = 0; c < cols; c++) custom[`p-${r}-${c}`] = idx;
      }
      for (let r = 0; r < rows - 1; r++) {
        for (let c = 0; c < cols; c++) custom[`v-${r}-${c}`] = idx;
      }
      for (let r = 0; r < rows - 1; r++) {
        for (let c = 0; c < cols - 1; c++) {
            custom[`c-${r}-${c}`] = idx;
            ['l1', 'l2', 'l3', 'l4'].forEach(l => custom[`${l}-${r}-${c}`] = idx);
            custom[`g-${r}-${c}`] = idx; // gap center in grid
        }
      }
      // For X pattern gaps between nodes (skip top row and far-left column)
      if (wallState.pattern === 'x') {
        const maxCH = Math.max(0, cols - 1);
        for (let r = 1; r < rows - 1; r++) {
          for (let c = 0; c < maxCH; c++) custom[`g-h-${r}-${c}`] = idx;
        }
        for (let r = 0; r < rows - 1; r++) {
          for (let c = 1; c < maxCH; c++) custom[`g-v-${r}-${c}`] = idx;
        }
        for (let r = 1; r < rows - 1; r++) {
          for (let c = 1; c < cols - 1; c++) custom[`f-x-${r}-${c}`] = idx;
        }
      }
      wallState.customColors = custom;
      saveActivePatternState();
      saveWallState();
      renderWall();
    });

    wallRemoveUnusedBtn?.addEventListener('click', () => {
      ensureWallGridSize(wallState.rows, wallState.cols);
      const beforeCustom = Object.keys(wallState.customColors || {}).length;

      const filtered = {};
      Object.entries(wallState.customColors || {}).forEach(([k, v]) => {
        if (Number.isInteger(v) && v >= 0) filtered[k] = v;
      });
      wallState.customColors = filtered;
      
      const afterCustom = Object.keys(filtered).length;
      saveWallState();
      renderWall();
      if (wallReplaceMsg) {
        const changed = (beforeCustom !== afterCustom);
        wallReplaceMsg.textContent = changed ? 'Removed unused.' : 'Nothing to remove.';
      }
      saveActivePatternState();
    });

    wallReplaceBtn?.addEventListener('click', () => {
      if (!wallReplaceFromSel || !wallReplaceToSel) return;
      const fromIdx = normalizeColorIdx(Number.isInteger(wallReplaceFromIdx) ? wallReplaceFromIdx : parseInt(wallReplaceFromSel.value, 10));
      const toIdx = normalizeColorIdx(Number.isInteger(wallReplaceToIdx) ? wallReplaceToIdx : parseInt(wallReplaceToSel.value, 10));
      if (!Number.isInteger(fromIdx) || !Number.isInteger(toIdx) || fromIdx === toIdx) {
        if (wallReplaceMsg) wallReplaceMsg.textContent = 'Choose two different colors.';
        return;
      }
      const matches = updateWallReplacePreview();
      if (!matches) {
        if (wallReplaceMsg) wallReplaceMsg.textContent = 'No matches to replace.';
        return;
      }
      if (matches > 120) {
        const ok = window.confirm(`Replace ${matches} balloons? This cannot be undone except via undo/reload.`);
        if (!ok) return;
      }
      ensureWallGridSize(wallState.rows, wallState.cols);
      let replaced = 0;
      wallState.colors = wallState.colors.map(row => row.map(v => {
        const val = Number.isInteger(v) ? v : parseInt(v, 10);
        if (val === fromIdx) { replaced++; return toIdx; }
        return v;
      }));
      Object.keys(wallState.customColors || {}).forEach(k => {
        const raw = wallState.customColors[k];
        const val = Number.isInteger(raw) ? raw : parseInt(raw, 10);
        if (val === fromIdx) { wallState.customColors[k] = toIdx; replaced++; }
      });
      // Keep pattern store in sync for this pattern
      saveActivePatternState();
      saveWallState();
      renderWall();
      renderWallUsedPalette();
      updateWallReplacePreview();
      if (wallReplaceMsg) wallReplaceMsg.textContent = replaced ? `Replaced ${replaced} item${replaced === 1 ? '' : 's'}.` : 'Nothing to replace.';
    });
  }

  window.WallDesigner = {
    init: initWallDesigner,
    buildWallSvgPayload: buildWallSvgPayload
  };

  document.addEventListener('DOMContentLoaded', () => {
    if (document.getElementById('tab-wall') && !window.__wallInit) {
      window.__wallInit = true;
      initWallDesigner();
    }
  });

})();
