(() => {
  'use strict';

  // Shared helpers & palette flattening
  document.addEventListener('DOMContentLoaded', () => {
    const PX_PER_INCH = 4;
    const SIZE_PRESETS = [24, 18, 11, 9, 5];
    const TEXTURE_ZOOM_DEFAULT = 1.8;
    const TEXTURE_FOCUS_DEFAULT = { x: 0.5, y: 0.5 };
    const SWATCH_TEXTURE_ZOOM = 2.5;
    const PNG_EXPORT_SCALE = 3;

    const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
    const clamp01 = v => clamp(v, 0, 1);
    const normalizeHex = h => (h || '').toLowerCase();
    function hexToRgb(hex) {
      const h = normalizeHex(hex).replace('#','');
      if (h.length === 3) {
        const r = parseInt(h[0] + h[0], 16);
        const g = parseInt(h[1] + h[1], 16);
        const b = parseInt(h[2] + h[2], 16);
        return { r, g, b };
      }
      if (h.length === 6) {
        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 };
      }
      return { r: 0, g: 0, b: 0 };
    }
    function luminance(hex) {
      const { r, g, b } = hexToRgb(hex || '#000');
      const norm = [r,g,b].map(v => {
        const c = v / 255;
        return c <= 0.03928 ? c/12.92 : Math.pow((c+0.055)/1.055, 2.4);
      });
      return 0.2126*norm[0] + 0.7152*norm[1] + 0.0722*norm[2];
    }
    function shineStyle(colorHex) {
      const hex = normalizeHex(colorHex);
      const isRetroWhite = hex === '#e8e3d9';
      const isPureWhite = hex === '#ffffff';
      const lum = luminance(hex);
      if (isPureWhite || isRetroWhite) {
        return { fill: 'rgba(220,220,220,0.22)', stroke: null };
      }
      if (lum > 0.7) {
        const t = clamp01((lum - 0.7) / 0.3);
        const fillAlpha = 0.08 + (0.04 - 0.08) * t;
        return { fill: `rgba(0,0,0,${fillAlpha})`, stroke: null };
      }
      const base = 0.20;
      const softened = lum > 0.4 ? base * 0.7 : base;
      const finalAlpha = isRetroWhite ? softened * 0.6 : softened;
      return { fill: `rgba(255,255,255,${finalAlpha})`, stroke: null };
    }

    const FLAT_COLORS = [];
    const NAME_BY_HEX = new Map();
    const HEX_TO_FIRST_IDX = new Map();
    const allowedSet = new Set();

    (function buildFlat() {
      if (!Array.isArray(window.PALETTE)) return;
      window.PALETTE.forEach(group => {
        (group.colors || []).forEach(c => {
          if (!c?.hex) return;
          const item = { ...c, family: group.family };
          item.imageZoom = Number.isFinite(c.imageZoom) ? Math.max(1, c.imageZoom) : TEXTURE_ZOOM_DEFAULT;
          item.imageFocus = {
            x: clamp01(c.imageFocusX ?? c.imageFocus?.x ?? TEXTURE_FOCUS_DEFAULT.x),
            y: clamp01(c.imageFocusY ?? c.imageFocus?.y ?? TEXTURE_FOCUS_DEFAULT.y)
          };
          item._idx = FLAT_COLORS.length;
          FLAT_COLORS.push(item);

          const key = (c.hex || '').toLowerCase();
          if (!NAME_BY_HEX.has(key)) NAME_BY_HEX.set(key, c.name);
          if (!HEX_TO_FIRST_IDX.has(key)) HEX_TO_FIRST_IDX.set(key, item._idx);
          allowedSet.add(key);
        });
      });
    })();

    const IMG_CACHE = new Map();
    function getImage(path, onLoad) {
      if (!path) return null;
      let img = IMG_CACHE.get(path);
      if (!img) {
        img = new Image();
        // Avoid CORS issues on file:// by only setting crossOrigin for http/https
        const href = (() => { try { return new URL(path, window.location.href); } catch { return null; } })();
        const isFile = href?.protocol === 'file:' || window.location.protocol === 'file:';
        if (!isFile) img.crossOrigin = 'anonymous';
        img.decoding = 'async';
        img.loading = 'eager';
        img.src = path;
        if (onLoad) img.onload = onLoad;
        IMG_CACHE.set(path, img);
      }
      return img;
    }

    const DATA_URL_CACHE = new Map();
    const XLINK_NS = 'http://www.w3.org/1999/xlink';
    const blobToDataUrl = blob => new Promise((resolve, reject) => {
      const r = new FileReader();
      r.onloadend = () => resolve(r.result);
      r.onerror = reject;
      r.readAsDataURL(blob);
    });
    function imageToDataUrl(img) {
      if (!img || !img.complete || img.naturalWidth === 0) return null;
      // On file:// origins, drawing may be blocked; return null to fall back to original href.
      if (window.location.protocol === 'file:') return null;
      try {
        const c = document.createElement('canvas');
        c.width = img.naturalWidth;
        c.height = img.naturalHeight;
        c.getContext('2d').drawImage(img, 0, 0);
        return c.toDataURL('image/png');
      } catch (err) {
        console.warn('[Export] imageToDataUrl failed:', err);
        return null;
      }
    }
    async function imageUrlToDataUrl(src) {
      if (!src || src.startsWith('data:')) return src;
      if (DATA_URL_CACHE.has(src)) return DATA_URL_CACHE.get(src);
      const abs = (() => { try { return new URL(src, window.location.href).href; } catch { return src; } })();
      const urlObj = (() => { try { return new URL(abs); } catch { return null; } })();
      const isFile = urlObj?.protocol === 'file:';

      const cachedImg = IMG_CACHE.get(src);
      const cachedUrl = imageToDataUrl(cachedImg);
      if (cachedUrl) { DATA_URL_CACHE.set(src, cachedUrl); return cachedUrl; }

      let dataUrl = null;
      try {
        if (isFile) {
          // On file:// we cannot safely read pixels; return the original path.
          dataUrl = src;
        } else {
          const resp = await fetch(abs);
          if (!resp.ok) throw new Error(`Status ${resp.status}`);
          dataUrl = await blobToDataUrl(await resp.blob());
        }
      } catch (err) {
        if (!isFile) console.warn('[Export] Fetch failed for', abs, err);
        dataUrl = await new Promise(resolve => {
          const img = new Image();
          if (!isFile) img.crossOrigin = 'anonymous';
          img.onload = () => {
            try {
              const c = document.createElement('canvas');
              c.width = img.naturalWidth || 1;
              c.height = img.naturalHeight || 1;
              c.getContext('2d').drawImage(img, 0, 0);
              resolve(c.toDataURL('image/png'));
            } catch (e) {
              console.error('[Export] Canvas fallback failed for', abs, e);
              resolve(null);
            }
          };
          img.onerror = () => resolve(null);
          img.src = abs;
        });
      }
      if (!dataUrl) dataUrl = abs;
      DATA_URL_CACHE.set(src, dataUrl);
      return dataUrl;
    }

    function download(href, suggestedFilename) {
      const a = document.createElement('a');
      a.href = href;
      a.download = suggestedFilename || 'download';
      a.rel = 'noopener';
      document.body.appendChild(a);
      a.click();
      a.remove();
    }

    window.shared = {
      PX_PER_INCH,
      SIZE_PRESETS,
      TEXTURE_ZOOM_DEFAULT,
      TEXTURE_FOCUS_DEFAULT,
      SWATCH_TEXTURE_ZOOM,
      PNG_EXPORT_SCALE,
      clamp,
      clamp01,
      normalizeHex,
      hexToRgb,
      shineStyle,
      luminance,
      FLAT_COLORS,
      NAME_BY_HEX,
      HEX_TO_FIRST_IDX,
      allowedSet,
      getImage,
      DATA_URL_CACHE,
      XLINK_NS,
      blobToDataUrl,
      imageToDataUrl,
      imageUrlToDataUrl,
      download,
    };
  });
})();
