(function () {
  'use strict';

  var DAS = null;
  var REGISTRY = null;
  var selectedRefs = {};
  var customRefs = [];
  var customRefCounter = 0;
  var MAX_CUSTOM_REFS = 12;
  var MAX_CUSTOM_FILE_MB = 8;
  var NOTES_STORAGE_KEY = 'yumi-builder-notes-for-ai-v1';
  var STEP_STORAGE_KEY = 'yumi-builder-wizard-step-v1';
  var currentStep = 1;

  var els = {
    posePreset: document.getElementById('posePreset'),
    pose: document.getElementById('pose'),
    expressionPreset: document.getElementById('expressionPreset'),
    expression: document.getElementById('expression'),
    framing: document.getElementById('framing'),
    location: document.getElementById('location'),
    scene: document.getElementById('scene'),
    lighting: document.getElementById('lighting'),
    cameraPreset: document.getElementById('cameraPreset'),
    cameraPov: document.getElementById('cameraPov'),
    cameraColor: document.getElementById('cameraColor'),
    cameraGrain: document.getElementById('cameraGrain'),
    cameraCustom: document.getElementById('cameraCustom'),
    notesForAi: document.getElementById('notesForAi'),
    coreIdentityList: document.getElementById('coreIdentityList'),
    canonicalInventoryRoot: document.getElementById('canonicalInventoryRoot'),
    canonicalValidation: document.getElementById('canonicalValidation'),
    customWardrobePanel: document.getElementById('customWardrobePanel'),
    customWardrobe: document.getElementById('customWardrobe'),
    alsoAdd: document.getElementById('alsoAdd'),
    additionalAppearance: document.getElementById('additionalAppearance'),
    propDescription: document.getElementById('propDescription'),
    propImage: document.getElementById('propImage'),
    refGallery: document.getElementById('refGallery'),
    loadError: document.getElementById('loadError'),
    refLinkCount: document.getElementById('refLinkCount'),
    refSelectAll: document.getElementById('refSelectAll'),
    refSelectCritical: document.getElementById('refSelectCritical'),
    downloadCompleteZip: document.getElementById('downloadCompleteZip'),
    downloadContactSheet: document.getElementById('downloadContactSheet'),
    includeRefsInZip: document.getElementById('includeRefsInZip'),
    includeContactInZip: document.getElementById('includeContactInZip'),
    customRefInput: document.getElementById('customRefInput'),
    customRefGallery: document.getElementById('customRefGallery'),
    customRefEmpty: document.getElementById('customRefEmpty'),
    copyHint: document.getElementById('copyHint'),
    copyWarning: document.getElementById('copyWarning'),
    copyForAi: document.getElementById('copyForAi'),
    creativeForkNote: document.getElementById('creativeForkNote'),
    outUnified: document.getElementById('outUnified'),
    outJson: document.getElementById('outJson'),
    outPrompt: document.getElementById('outPrompt'),
    outNegative: document.getElementById('outNegative'),
    outChecklist: document.getElementById('outChecklist'),
    copyToast: document.getElementById('copyToast'),
    savePackageTxt: document.getElementById('savePackageTxt'),
    mobileSavePanel: document.getElementById('mobileSavePanel'),
    mobileSaveLink: document.getElementById('mobileSaveLink'),
    mobileSaveDismiss: document.getElementById('mobileSaveDismiss')
  };

  var mobileSaveUrl = null;
  var mobileSaveRevokeTimer = null;

  function fetchJson(path) {
    return fetch(path).then(function (r) {
      if (!r.ok) throw new Error('Failed to load ' + path);
      return r.json();
    });
  }

  function getStarterRefs() {
    if (!REGISTRY || !REGISTRY.images) return [];
    return REGISTRY.images
      .filter(function (img) { return img.recommended_for_generation; })
      .sort(function (a, b) {
        return (a.attach_order || a.priority || 0) - (b.attach_order || b.priority || 0);
      });
  }

  function refHostedUrl(img) {
    if (!REGISTRY || !REGISTRY.hosted_base_path || !img.local_url) return '';
    var rel = img.local_url.replace(/^\.\.\//, '');
    return REGISTRY.hosted_base_path + rel;
  }

  function refThumbCandidates(img) {
    var candidates = [];
    var hosted = refHostedUrl(img);
    if (hosted) candidates.push(hosted);
    if (img.url) candidates.push(img.url);
    if (img.local_url) candidates.push(img.local_url);
    return candidates.filter(function (url, index, list) {
      return url && list.indexOf(url) === index;
    });
  }

  function refDisplayUrl(img) {
    return img.url || refHostedUrl(img) || img.local_url || '';
  }

  function refCopyUrl(img) {
    var protocol = window.location.protocol;
    var hosted = refHostedUrl(img);
    if ((protocol === 'http:' || protocol === 'https:') && hosted) {
      if (hosted.charAt(0) === '/') {
        return window.location.origin + hosted;
      }
      return hosted;
    }
    if (img.url) return img.url;
    return img.local_url || '';
  }

  function sanitizeFilename(name) {
    return (name || 'image.png').replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'image.png';
  }

  function normalizeOfficialRef(img) {
    return {
      source: 'official',
      id: img.id,
      attach_order: img.attach_order,
      attach_label: img.attach_label || img.title,
      attach_priority_tier: img.attach_priority_tier || 'recommended',
      title: img.title,
      url: img.url,
      local_url: img.local_url,
      bundleFilename: String(img.attach_order || 0).padStart(2, '0') + '-' + sanitizeFilename(img.filename || (img.attach_label || 'ref') + '.png')
    };
  }

  function getSelectedOfficialRefs() {
    return getStarterRefs().filter(function (img) {
      return selectedRefs[img.id] !== false;
    });
  }

  function getSelectedCustomRefs() {
    return customRefs.filter(function (ref) {
      return selectedRefs[ref.id] !== false;
    });
  }

  function getExportRefs() {
    var list = [];

    getSelectedOfficialRefs().forEach(function (img) {
      list.push(normalizeOfficialRef(img));
    });

    getSelectedCustomRefs().forEach(function (ref) {
      list.push({
        source: 'custom',
        id: ref.id,
        attach_order: list.length + 1,
        attach_label: ref.label,
        attach_priority_tier: 'user',
        title: ref.label,
        bundleFilename: ref.bundleFilename,
        fileName: ref.fileName,
        blob: ref.blob,
        objectUrl: ref.objectUrl
      });
    });

    return list;
  }

  function hasCustomRefsInExport() {
    return getSelectedCustomRefs().length > 0;
  }

  function getReplacementImageEntries() {
    var entries = [];
    if (!window.YumiCanonical) return entries;
    YumiCanonical.getReplacementFilesForZip().forEach(function (entry) {
      entries.push({
        source: 'replacement',
        attach_label: 'Replacement ref: ' + entry.label,
        attach_priority_tier: 'replacement',
        bundleFilename: entry.filename,
        folder: 'replacement_refs',
        blob: entry.blob
      });
    });
    return entries;
  }

  function getPropImageEntries() {
    return getPropsForExport().filter(function (p) { return p.blob; }).map(function (p) {
      return {
        source: 'prop',
        attach_label: 'Prop: ' + p.description,
        attach_priority_tier: 'prop',
        bundleFilename: p.filename,
        folder: 'props',
        blob: p.blob
      };
    });
  }

  function getAllPackageImageEntries() {
    var list = [];
    getExportRefs().forEach(function (ref) {
      var entry = Object.assign({}, ref, { folder: 'references' });
      list.push(entry);
    });
    getReplacementImageEntries().forEach(function (entry) {
      list.push(entry);
    });
    getPropImageEntries().forEach(function (entry) {
      list.push(entry);
    });
    return list;
  }

  function packageImageCount() {
    return getAllPackageImageEntries().length;
  }

  function setAllRefSelection(selected) {
    getStarterRefs().forEach(function (img) {
      selectedRefs[img.id] = selected;
    });
  }

  function setCriticalRefSelection() {
    getStarterRefs().forEach(function (img) {
      selectedRefs[img.id] = img.attach_priority_tier === 'critical';
    });
  }

  function updateRefLinkCount() {
    var officialTotal = getStarterRefs().length;
    var officialSelected = getSelectedOfficialRefs().length;
    var customSelected = getSelectedCustomRefs().length;
    var exportCount = getExportRefs().length;

    if (els.refLinkCount) {
      if (!officialTotal && !customRefs.length) {
        els.refLinkCount.textContent = 'No reference links available.';
      } else if (customSelected) {
        els.refLinkCount.textContent = exportCount + ' total for Step 3 package (' + officialSelected + ' official + ' + customSelected + ' custom)';
      } else {
        els.refLinkCount.textContent = officialSelected + ' of ' + officialTotal + ' official refs selected for Step 3 package';
      }
    }
    if (els.copyHint) {
      var totalImages = packageImageCount();
      if (!totalImages) {
        els.copyHint.textContent = 'Step 3: download images first when you can. Text copy still works if you attach images manually in your AI tool.';
      } else {
        els.copyHint.textContent = 'Step 3 workflow: download images first (Step 1 button), attach them in your AI tool, then use Step 3 Copy text. Package includes ' + totalImages + ' image file' + (totalImages === 1 ? '' : 's') + ' when you download the zip or contact sheet.';
      }
    }
    if (els.copyWarning) {
      if (!exportCount) {
        els.copyWarning.textContent = 'No reference images selected. Download the complete package with a contact sheet, or attach files manually in your AI tool.';
        els.copyWarning.classList.remove('hidden');
      } else if (hasCustomRefsInExport()) {
        els.copyWarning.textContent = 'Custom extras included: your images are not validated against DAS Yumi rules. Results may drift from canonical Yumi.';
        els.copyWarning.classList.remove('hidden');
      } else {
        els.copyWarning.classList.add('hidden');
      }
    }
    if (els.customRefEmpty) {
      els.customRefEmpty.classList.toggle('hidden', customRefs.length > 0);
    }
  }

  function syncRefCardCheckboxes() {
    document.querySelectorAll('.ref-include-cb').forEach(function (cb) {
      var id = cb.getAttribute('data-ref-id');
      cb.checked = selectedRefs[id] !== false;
      var card = cb.closest('.ref-card');
      if (card) card.classList.toggle('ref-unchecked', !cb.checked);
    });
    updateRefLinkCount();
    updateExports();
  }

  function showLoadError(message) {
    if (!els.loadError) return;
    els.loadError.innerHTML = '<strong>Could not load Yumi data</strong>' +
      'Check that <code>reference_registry.json</code>, <code>das/DAS-ACT-0001-Yumi.json</code>, and <code>das/canonical-inventory.json</code> are uploaded.<br><br>' +
      message;
    els.loadError.classList.remove('hidden');
  }

  function hideLoadError() {
    if (els.loadError) els.loadError.classList.add('hidden');
  }

  function wireOutboundLinks() {
    document.querySelectorAll('a[href]').forEach(function (link) {
      if (link.target) return;
      link.target = '_blank';
      link.rel = 'noopener noreferrer';
    });
  }

  function showStep(stepNum) {
    currentStep = stepNum;
    document.querySelectorAll('.wizard-step').forEach(function (section) {
      var n = parseInt(section.getAttribute('data-step'), 10);
      var active = n === stepNum;
      section.hidden = !active;
      section.classList.toggle('active', active);
    });
    document.querySelectorAll('.wizard-tab').forEach(function (tab) {
      var n = parseInt(tab.getAttribute('data-step'), 10);
      tab.classList.toggle('active', n === stepNum);
    });
    try {
      localStorage.setItem(STEP_STORAGE_KEY, String(stepNum));
    } catch (e) { /* ignore */ }
    window.scrollTo({ top: 0, behavior: 'smooth' });
  }

  function setPanelHidden(el, hidden) {
    if (!el) return;
    el.classList.toggle('hidden', hidden);
    if (hidden) el.setAttribute('hidden', '');
    else el.removeAttribute('hidden');
  }

  function isCustomWardrobeMode() {
    var checked = document.querySelector('input[name="wardrobeMode"]:checked');
    return checked && checked.value === 'custom';
  }

  function renderCoreIdentity() {
    if (!els.coreIdentityList || !window.YumiCanonical) return;
    var data = YumiCanonical.getData();
    els.coreIdentityList.innerHTML = '';
    (data && data.core_identity ? data.core_identity : []).forEach(function (line) {
      var li = document.createElement('li');
      li.textContent = line;
      els.coreIdentityList.appendChild(li);
    });
  }

  function renderCanonicalInventory() {
    if (!window.YumiCanonical || !els.canonicalInventoryRoot) return;
    YumiCanonical.renderInventory(els.canonicalInventoryRoot, {
      customWardrobeMode: isCustomWardrobeMode(),
      onChange: updateExports
    });
  }

  function toggleWardrobePanels() {
    var custom = isCustomWardrobeMode();
    setPanelHidden(els.customWardrobePanel, !custom);
    renderCanonicalInventory();
  }

  function getFreeformValues() {
    return {
      alsoAdd: (els.alsoAdd && els.alsoAdd.value || '').trim(),
      additionalAppearance: (els.additionalAppearance && els.additionalAppearance.value || '').trim(),
      propDescription: (els.propDescription && els.propDescription.value || '').trim(),
      propFile: els.propImage && els.propImage.files && els.propImage.files[0] ? els.propImage.files[0] : null
    };
  }

  function getPropsForExport() {
    var ff = getFreeformValues();
    var props = [];
    if (ff.propDescription || ff.propFile) {
      props.push({
        description: ff.propDescription || 'Prop as shown in attached reference image',
        filename: ff.propFile ? sanitizeFilename('prop-' + ff.propFile.name) : null,
        blob: ff.propFile || null
      });
    }
    return props;
  }

  function findCameraPreset(id) {
    if (!DAS.camera_look || !DAS.camera_look.presets) return null;
    for (var i = 0; i < DAS.camera_look.presets.length; i++) {
      if (DAS.camera_look.presets[i].id === id) return DAS.camera_look.presets[i];
    }
    return null;
  }

  function findCameraOption(list, id) {
    if (!list) return null;
    for (var i = 0; i < list.length; i++) {
      if (list[i].id === id) return list[i];
    }
    return null;
  }

  function normalizeCameraPov(pov) {
    if (pov === 'friend_taking_photo') return 'third_person';
    return pov;
  }

  function getCameraLookFromUI() {
    return {
      preset: els.cameraPreset.value,
      pov: normalizeCameraPov(els.cameraPov.value),
      color: els.cameraColor.value,
      grain: els.cameraGrain.value,
      custom_notes: (els.cameraCustom.value || '').trim()
    };
  }

  function applyCameraPresetDefaults(presetId) {
    var preset = findCameraPreset(presetId);
    if (!preset || !preset.defaults) return;
    els.cameraPov.value = normalizeCameraPov(preset.defaults.pov || els.cameraPov.value);
    els.cameraColor.value = preset.defaults.color || els.cameraColor.value;
    els.cameraGrain.value = preset.defaults.grain || els.cameraGrain.value;
  }

  function usesRealismNegatives(presetId) {
    if (!DAS.camera_look || !DAS.camera_look.realism_presets) return true;
    return DAS.camera_look.realism_presets.indexOf(presetId) !== -1;
  }

  function buildCameraLookPrompt(cameraLook) {
    var cfg = DAS.camera_look;
    if (!cfg) return '';

    var preset = findCameraPreset(cameraLook.preset);
    var pov = findCameraOption(cfg.pov_options, cameraLook.pov);
    var color = findCameraOption(cfg.color_options, cameraLook.color);
    var grain = findCameraOption(cfg.grain_options, cameraLook.grain);
    var parts = [];

    if (cameraLook.preset === 'custom') {
      parts.push(cameraLook.custom_notes || 'Photorealistic lifelike photograph of the same woman.');
    } else if (preset && preset.prompt) {
      parts.push(preset.prompt);
    }

    if (pov && pov.prompt) parts.push(pov.prompt);
    if (color && color.prompt) parts.push(color.prompt);
    if (grain && grain.prompt) parts.push(grain.prompt);
    if (cameraLook.preset !== 'custom' && cameraLook.custom_notes) {
      parts.push(cameraLook.custom_notes);
    }

    return parts.join(' ');
  }

  function buildRefGallery() {
    if (!els.refGallery) return;
    els.refGallery.innerHTML = '';
    var refs = getStarterRefs();

    if (!refs.length) {
      var empty = document.createElement('p');
      empty.className = 'hint ref-gallery-empty';
      empty.textContent = 'No reference images found in registry.';
      els.refGallery.appendChild(empty);
      return;
    }

    refs.forEach(function (img) {
      if (selectedRefs[img.id] === undefined) selectedRefs[img.id] = true;

      var card = document.createElement('article');
      card.className = 'ref-card tier-' + (img.attach_priority_tier || 'recommended');
      if (!selectedRefs[img.id]) card.classList.add('ref-unchecked');

      var badge = document.createElement('span');
      badge.className = 'ref-order';
      badge.textContent = String(img.attach_order || '');

      var tier = document.createElement('span');
      tier.className = 'ref-tier';
      tier.textContent = img.attach_priority_tier === 'critical' ? 'Critical' : 'Recommended';

      var thumb = document.createElement('img');
      thumb.className = 'ref-thumb';
      var thumbCandidates = refThumbCandidates(img);
      var thumbAttempt = 0;
      thumb.src = thumbCandidates[0] || '';
      thumb.alt = img.attach_label || img.title;
      thumb.loading = 'lazy';
      thumb.addEventListener('error', function () {
        thumbAttempt += 1;
        if (thumbAttempt < thumbCandidates.length) {
          thumb.src = thumbCandidates[thumbAttempt];
        } else {
          thumb.classList.add('ref-thumb-failed');
          thumb.removeAttribute('src');
          thumb.alt = 'Image failed to load - use Open & save';
        }
      });

      var title = document.createElement('h4');
      title.textContent = (img.attach_label || img.title);

      var usage = document.createElement('p');
      usage.className = 'ref-usage';
      usage.textContent = img.usage || '';

      var includeLabel = document.createElement('label');
      includeLabel.className = 'ref-include';
      var includeCb = document.createElement('input');
      includeCb.type = 'checkbox';
      includeCb.className = 'ref-include-cb';
      includeCb.setAttribute('data-ref-id', img.id);
      includeCb.checked = selectedRefs[img.id] !== false;
      includeCb.addEventListener('change', function () {
        selectedRefs[img.id] = includeCb.checked;
        card.classList.toggle('ref-unchecked', !includeCb.checked);
        updateRefLinkCount();
        updateExports();
      });
      includeLabel.appendChild(includeCb);
      includeLabel.appendChild(document.createTextNode(' Include in bundle and copy'));

      var actions = document.createElement('div');
      actions.className = 'ref-actions';

      var openBtn = document.createElement('a');
      openBtn.className = 'btn secondary small-btn';
      openBtn.href = refDisplayUrl(img);
      openBtn.target = '_blank';
      openBtn.rel = 'noopener noreferrer';
      openBtn.textContent = 'Open & save';

      actions.appendChild(openBtn);
      card.appendChild(badge);
      card.appendChild(tier);
      card.appendChild(thumb);
      card.appendChild(title);
      card.appendChild(usage);
      card.appendChild(includeLabel);
      card.appendChild(actions);
      els.refGallery.appendChild(card);
    });

    updateRefLinkCount();
    wireOutboundLinks();
  }

  function buildCustomRefGallery() {
    if (!els.customRefGallery) return;
    els.customRefGallery.innerHTML = '';

    customRefs.forEach(function (ref) {
      if (selectedRefs[ref.id] === undefined) selectedRefs[ref.id] = true;

      var card = document.createElement('article');
      card.className = 'ref-card tier-user';
      if (!selectedRefs[ref.id]) card.classList.add('ref-unchecked');

      var badge = document.createElement('span');
      badge.className = 'ref-order';
      badge.textContent = 'U';

      var tier = document.createElement('span');
      tier.className = 'ref-tier';
      tier.textContent = 'Your file';

      var thumb = document.createElement('img');
      thumb.className = 'ref-thumb';
      thumb.src = ref.objectUrl;
      thumb.alt = ref.label;
      thumb.loading = 'lazy';

      var title = document.createElement('h4');
      title.textContent = ref.label;

      var usage = document.createElement('p');
      usage.className = 'ref-usage';
      usage.textContent = 'Bundle file: references/' + ref.bundleFilename;

      var includeLabel = document.createElement('label');
      includeLabel.className = 'ref-include';
      var includeCb = document.createElement('input');
      includeCb.type = 'checkbox';
      includeCb.className = 'ref-include-cb';
      includeCb.setAttribute('data-ref-id', ref.id);
      includeCb.checked = selectedRefs[ref.id] !== false;
      includeCb.addEventListener('change', function () {
        selectedRefs[ref.id] = includeCb.checked;
        card.classList.toggle('ref-unchecked', !includeCb.checked);
        updateRefLinkCount();
        updateExports();
      });
      includeLabel.appendChild(includeCb);
      includeLabel.appendChild(document.createTextNode(' Include in bundle and copy'));

      var removeBtn = document.createElement('button');
      removeBtn.type = 'button';
      removeBtn.className = 'ref-remove-btn';
      removeBtn.textContent = 'Remove';
      removeBtn.addEventListener('click', function () {
        removeCustomRef(ref.id);
      });

      card.appendChild(badge);
      card.appendChild(tier);
      card.appendChild(thumb);
      card.appendChild(title);
      card.appendChild(usage);
      card.appendChild(includeLabel);
      card.appendChild(removeBtn);
      els.customRefGallery.appendChild(card);
    });

    updateRefLinkCount();
  }

  function removeCustomRef(id) {
    customRefs = customRefs.filter(function (ref) {
      if (ref.id === id) {
        try { URL.revokeObjectURL(ref.objectUrl); } catch (e) { /* ignore */ }
        delete selectedRefs[id];
        return false;
      }
      return true;
    });
    buildCustomRefGallery();
    updateExports();
  }

  function addCustomRefFiles(fileList) {
    if (!fileList || !fileList.length) return;
    var remaining = MAX_CUSTOM_REFS - customRefs.length;
    if (remaining <= 0) {
      alert('Maximum ' + MAX_CUSTOM_REFS + ' custom reference images. Remove one to add more.');
      return;
    }

    Array.prototype.slice.call(fileList, 0, remaining).forEach(function (file) {
      if (!file.type || file.type.indexOf('image/') !== 0) return;
      if (file.size > MAX_CUSTOM_FILE_MB * 1024 * 1024) {
        alert(file.name + ' is too large. Max ' + MAX_CUSTOM_FILE_MB + 'MB per image.');
        return;
      }
      customRefCounter += 1;
      var id = 'custom-' + customRefCounter;
      var safeName = sanitizeFilename(file.name);
      var bundleFilename = 'user-' + String(customRefs.length + 1).padStart(2, '0') + '-' + safeName;
      var objectUrl = URL.createObjectURL(file);
      customRefs.push({
        id: id,
        fileName: file.name,
        label: file.name,
        blob: file,
        objectUrl: objectUrl,
        bundleFilename: bundleFilename
      });
      selectedRefs[id] = true;
    });

    if (els.customRefInput) els.customRefInput.value = '';
    buildCustomRefGallery();
    updateExports();
  }

  function val(presetEl, fieldEl, fallback) {
    var custom = (fieldEl.value || '').trim();
    if (custom) return custom;
    if (presetEl.value) return presetEl.value;
    return fallback;
  }

  function getNotesForAi() {
    return (els.notesForAi.value || '').trim();
  }

  function buildAppearanceBlocks(ff, props) {
    if (!window.YumiCanonical) return '';
    var custom = isCustomWardrobeMode();
    var customText = (els.customWardrobe && els.customWardrobe.value || '').trim();
    if (!ff) ff = getFreeformValues();
    if (!props) props = getPropsForExport();
    return YumiCanonical.buildCanonicalExportBlocks(
      custom,
      customText,
      ff.alsoAdd,
      ff.additionalAppearance,
      props
    );
  }

  function buildShot() {
    var customWardrobe = isCustomWardrobeMode();
    var customText = customWardrobe ? ((els.customWardrobe.value || '').trim() || null) : null;
    var canonicalPayload = window.YumiCanonical
      ? YumiCanonical.getCanonicalShotPayload(customWardrobe, customText)
      : null;
    var ff = getFreeformValues();
    var props = getPropsForExport();
    var cameraLook = getCameraLookFromUI();
    var appearanceBlocks = buildAppearanceBlocks(ff, props);

    return {
      schema: 'DAS.YumiShot',
      version: '1.1',
      generated_by: 'Yumi Builder v1.0',
      generated_at: new Date().toISOString(),
      actor_id: DAS.actor_id,
      name: DAS.name,
      das_version: DAS.version,
      user_notes_for_ai: getNotesForAi() || null,
      shot: {
        pose: val(els.posePreset, els.pose, DAS.defaults.pose),
        expression: val(els.expressionPreset, els.expression, DAS.defaults.expression),
        framing: els.framing.value,
        location: (els.location.value || '').trim() || 'unspecified',
        scene: (els.scene.value || '').trim() || 'unspecified',
        lighting: (els.lighting.value || '').trim() || 'unspecified',
        camera_look: cameraLook,
        appearance_mode: canonicalPayload ? canonicalPayload.mode : (customWardrobe ? 'custom_wardrobe' : 'signature_look'),
        canonical_inventory: canonicalPayload,
        custom_wardrobe_text: customText,
        also_add: ff.alsoAdd || null,
        additional_appearance: ff.additionalAppearance || null,
        props: props.map(function (p) {
          return {
            description: p.description,
            bundle_filename: p.filename ? 'props/' + p.filename : null
          };
        }),
        validation_errors: canonicalPayload ? canonicalPayload.validation_errors : []
      },
      references: getExportRefs().map(function (i) {
        return {
          id: i.id,
          source: i.source,
          attach_order: i.attach_order,
          attach_priority_tier: i.attach_priority_tier,
          url: i.source === 'official' ? refCopyUrl(i) : null,
          bundle_filename: i.bundleFilename,
          title: i.title || i.attach_label,
          attach_label: i.attach_label
        };
      }),
      absolute_truth_url: DAS.authority.absolute_truth_url,
      prompts: {
        appearance_canonical: appearanceBlocks,
        photo_look: buildCameraLookPrompt(cameraLook)
      }
    };
  }

  function buildIdentityLockBlock(shot) {
    if (shot.shot.appearance_mode === 'custom_wardrobe') {
      return [
        '=== YUMI IDENTITY LOCK (CUSTOM WARDROBE) ===',
        '',
        'ONE person: Yumi. Match face, hair, freckles, beauty marks, age, and body from attached refs.',
        'OUTFIT: Use ONLY the custom wardrobe description in this package. Do NOT force canonical signature items unless you described them.',
        'Per-row body/hair/face deviations above still apply.',
        'New photo: match identity, not reference composition.',
        '',
        'Regenerate if her face or hair drifts - not if the outfit differs from reference photos.'
      ].join('\n');
    }
    return DAS.generation.identity_lock_instructions || '';
  }

  function buildPromptText(shot) {
    var g = DAS.generation.templates;
    var text = g.identity_prompt;
    if (shot.shot.appearance_mode === 'custom_wardrobe') {
      text += '\n\nIdentity from attached refs. Wardrobe is user-defined (bulk override).';
    } else {
      text += '\n\nFollow the CANONICAL SIGNATURE and DEVIATIONS sections in this package for outfit and accessories.';
    }
    if (shot.prompts.photo_look) text += '\n\nPhoto look: ' + shot.prompts.photo_look;
    text += '\n\nExpression: ' + shot.shot.expression + '.';
    text += '\nPose: ' + shot.shot.pose + '.';
    text += '\nFraming: ' + shot.shot.framing + '.';
    if (shot.shot.location !== 'unspecified') text += '\nLocation: ' + shot.shot.location + '.';
    if (shot.shot.scene !== 'unspecified') text += '\nScene: ' + shot.shot.scene + '.';
    if (shot.shot.lighting !== 'unspecified') text += '\nLighting: ' + shot.shot.lighting + '.';
    text += '\n\n' + DAS.shot_rules.anti_copy_prompt;
    text += '\n' + DAS.shot_rules.hammer_line;
    text += '\nPhotorealistic brand-new photograph of the same woman.';
    return text;
  }

  function hairStillCanonical(states) {
    if (!states) return true;
    var hairIds = ['hair_style', 'hair_color'];
    return hairIds.every(function (id) {
      return !states[id] || states[id].mode === 'match';
    });
  }

  function buildNegativeText(shot) {
    var customWardrobe = shot.shot.appearance_mode === 'custom_wardrobe';
    var base = customWardrobe
      ? 'different face, lookalike, aged up, aged down, no freckles, missing beauty marks, black hair only, platinum blonde, no dark roots, no twin tails, airbrushed plastic skin, exaggerated proportions, anime, cartoon, stylized, copy of reference, duplicate reference photo'
      : DAS.generation.templates.negative_prompt;
    var extras = [];
    if (usesRealismNegatives(shot.shot.camera_look.preset) && DAS.camera_look.realism_negative_extras) {
      extras.push(DAS.camera_look.realism_negative_extras);
    }
    if (!customWardrobe && window.YumiCanonical) {
      var canonical = YumiCanonical.getData();
      var states = shot.shot.canonical_inventory && shot.shot.canonical_inventory.item_states;
      if (canonical && canonical.hair_lock_negative && hairStillCanonical(states)) {
        extras.push(canonical.hair_lock_negative);
      }
    }
    if (!extras.length) return base;
    return base + ', ' + extras.join(', ');
  }

  function buildChecklistText(shot) {
    var lines = ['YUMI APPROVAL CHECKLIST', ''];
    if (window.YumiCanonical) {
      var custom = shot.shot.appearance_mode === 'custom_wardrobe';
      YumiCanonical.buildChecklistLines(custom).forEach(function (line) {
        lines.push(line);
      });
      if (!custom) {
        lines.push('');
        lines.push('Signature recognition (when all-match):');
        (DAS.approval_gate.must_pass || []).forEach(function (item) {
          lines.push('[ ] ' + item);
        });
      }
      return lines.join('\n');
    }
    if (shot.shot.appearance_mode === 'custom_wardrobe') {
      lines.push('[ ] Recognizable as same person as attached refs');
      lines.push('[ ] Face/hair identity preserved');
      lines.push('[ ] Custom wardrobe applied as described');
      lines.push('[ ] Not a near-duplicate of any reference photo');
      return lines.join('\n');
    }
    DAS.approval_gate.must_pass.forEach(function (item) {
      lines.push('[ ] ' + item);
    });
    return lines.join('\n');
  }

  function buildRefAttachBlock() {
    var selected = getExportRefs();
    var allImages = getAllPackageImageEntries();
    var officialTotal = getStarterRefs().length;
    var officialInExport = selected.filter(function (ref) { return ref.source === 'official'; }).length;
    var replacementCount = getReplacementImageEntries().length;
    var propCount = getPropImageEntries().length;
    var lines = [
      '=================================================================',
      'ATTACH THESE ' + allImages.length + ' IMAGE FILE' + (allImages.length === 1 ? '' : 'S') + ' IN YOUR AI TOOL FIRST',
      '=================================================================',
      '',
      'Before pasting the prompt below, attach each image file in your AI tool.',
      'Download the complete package (zip) from Step 3 first. Attach references/, then replacement_refs/ and props/ if present, or use contact-sheet.png.',
      ''
    ];

    if (officialInExport === officialTotal && officialTotal > 0) {
      lines.push('Ideal for genuine Yumi: all ' + officialTotal + ' official refs selected below. If your AI tool limits attachments, attach Critical 1-4 first.');
    } else if (officialInExport > 0) {
      lines.push('You selected ' + officialInExport + ' official Yumi ref' + (officialInExport === 1 ? '' : 's') + ' (listed below).');
    }

    if (officialInExport > 0) {
      lines.push('Public URLs below are a backup for selected official refs when you cannot attach files.');
    }

    if (lines[lines.length - 1] !== '') lines.push('');

    if (hasCustomRefsInExport()) {
      lines.push('--- USER REFERENCE NOTICE ---');
      lines.push('You added YOUR images IN ADDITION to official Yumi refs. Custom files are not validated against DAS rules.');
      lines.push('Custom images have no public URL. Attach them from your downloaded package (references/ folder).');
      lines.push('');
    }

    if (replacementCount) {
      lines.push('--- REPLACEMENT REFERENCE IMAGES (Step 2 Instead uploads) ---');
      lines.push('Attach these from replacement_refs/ in your downloaded zip.');
      lines.push('');
    }

    if (propCount) {
      lines.push('--- PROP REFERENCE IMAGES ---');
      lines.push('Attach these from props/ in your downloaded zip.');
      lines.push('');
    }

    if (window.location.protocol === 'file:') {
      lines.push('Note: You are running from a local file. Cloud AI tools cannot open local paths - attach PNG files from your package manually.');
      lines.push('');
    }

    if (!allImages.length) {
      lines.push('No reference images selected - attach images manually in your AI tool.');
      lines.push('');
    } else {
      allImages.forEach(function (ref, index) {
        var slotNum = index + 1;
        var folder = ref.folder || 'references';
        if (ref.source === 'official') {
          var tier = ref.attach_priority_tier === 'critical' ? 'CRITICAL' : 'RECOMMENDED';
          lines.push('IMAGE ' + slotNum + ' [' + tier + ']: ' + ref.attach_label);
          lines.push('Bundle file: ' + folder + '/' + ref.bundleFilename);
          lines.push('URL: ' + refCopyUrl(ref));
        } else if (ref.source === 'custom') {
          lines.push('IMAGE ' + slotNum + ' [USER - YOUR FILE]: ' + ref.attach_label);
          lines.push('Bundle file: ' + folder + '/' + ref.bundleFilename);
          lines.push('(No public URL - attach this file from your downloaded zip.)');
        } else if (ref.source === 'replacement') {
          lines.push('IMAGE ' + slotNum + ' [REPLACEMENT REF]: ' + ref.attach_label);
          lines.push('Bundle file: ' + folder + '/' + ref.bundleFilename);
          lines.push('(Attach from your downloaded zip replacement_refs/ folder.)');
        } else if (ref.source === 'prop') {
          lines.push('IMAGE ' + slotNum + ' [PROP REF]: ' + ref.attach_label);
          lines.push('Bundle file: ' + folder + '/' + ref.bundleFilename);
          lines.push('(Attach from your downloaded zip props/ folder.)');
        }
        lines.push('');
      });
    }

    lines.push('Optional: attach prior renders of the same person IN ADDITION to the refs above.');
    lines.push('');
    return lines.join('\n');
  }

  function buildUnifiedExport(shot) {
    var parts = [
      'YUMI - COMPLETE AI PACKAGE (DAS-ACT-0001 · JHVinylShop · v1.0)',
      '"Yumi Builder" by Jason Hathaway and JHVinylShop.com',
      '',
      buildRefAttachBlock(),
      '=================================================================',
      'PASTE EVERYTHING BELOW INTO YOUR AI TOOL AND GENERATE',
      '=================================================================',
      '',
      buildIdentityLockBlock(shot),
      ''
    ];

    if (shot.user_notes_for_ai) {
      parts.push('--- NOTES FOR AI ---');
      parts.push(shot.user_notes_for_ai);
      parts.push('');
    }

    parts.push('--- APPEARANCE (canonical inventory + freeform) ---');
    parts.push(shot.prompts.appearance_canonical || '');
    parts.push('');

    if (shot.prompts.photo_look) {
      parts.push('--- PHOTO LOOK ---');
      parts.push(shot.prompts.photo_look);
      parts.push('');
    }

    parts.push('--- PROMPT ---');
    parts.push(buildPromptText(shot));
    parts.push('');
    parts.push('--- AVOID (negative prompt) ---');
    parts.push(buildNegativeText(shot));
    parts.push('');
    parts.push('--- CHECKLIST (review your result) ---');
    parts.push(buildChecklistText(shot));
    parts.push('');
    parts.push('--- END - Save good outputs; attach them next time with the official 8 ---');

    return parts.join('\n');
  }

  function updateCanonicalValidation(shot) {
    if (!els.canonicalValidation) return;
    var errors = shot.shot.validation_errors || [];
    if (errors.length) {
      els.canonicalValidation.classList.remove('hidden');
      els.canonicalValidation.textContent = 'Fix before export: ' + errors.join(' ');
    } else {
      els.canonicalValidation.classList.add('hidden');
    }
  }

  function updateExports() {
    if (!DAS) return;
    updateRefLinkCount();
    var shot = buildShot();
    els.outUnified.textContent = buildUnifiedExport(shot);
    els.outJson.textContent = JSON.stringify(shot, null, 2);
    els.outPrompt.textContent = buildPromptText(shot);
    els.outNegative.textContent = buildNegativeText(shot);
    els.outChecklist.textContent = buildChecklistText(shot);
    updateCanonicalValidation(shot);
    try {
      localStorage.setItem(NOTES_STORAGE_KEY, els.notesForAi.value || '');
    } catch (e) { /* ignore */ }
  }

  var toastTimer = null;

  function showCopyToast() {
    els.copyToast.classList.remove('hidden');
    els.copyToast.classList.add('visible');
    if (toastTimer) clearTimeout(toastTimer);
    toastTimer = setTimeout(function () {
      els.copyToast.classList.remove('visible');
      setTimeout(function () {
        els.copyToast.classList.add('hidden');
      }, 300);
    }, 5000);
  }

  function copyToClipboard(text) {
    if (navigator.clipboard && window.isSecureContext) {
      return navigator.clipboard.writeText(text);
    }
    return new Promise(function (resolve, reject) {
      var ta = document.createElement('textarea');
      ta.value = text;
      ta.setAttribute('readonly', '');
      ta.style.position = 'fixed';
      ta.style.left = '-9999px';
      document.body.appendChild(ta);
      ta.select();
      ta.setSelectionRange(0, ta.value.length);
      try {
        var ok = document.execCommand('copy');
        document.body.removeChild(ta);
        if (ok) resolve();
        else reject(new Error('execCommand failed'));
      } catch (err) {
        document.body.removeChild(ta);
        reject(err);
      }
    });
  }

  function copyEverythingForAi() {
    var text = els.outUnified.textContent;
    copyToClipboard(text).then(function () {
      showCopyToast();
      if (els.copyForAi) {
        var old = els.copyForAi.textContent;
        els.copyForAi.textContent = 'Copied! Paste in your open AI now';
        setTimeout(function () {
          els.copyForAi.textContent = old;
        }, 3000);
      }
    }).catch(function () {
      els.copyToast.querySelector('strong').textContent = 'Copy failed';
      els.copyToast.querySelector('span').textContent = 'Select the preview text manually and copy (Ctrl+C).';
      els.copyToast.classList.remove('hidden');
      els.copyToast.classList.add('visible');
    });
  }

  function isMobileSaveContext() {
    return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent) ||
      (navigator.maxTouchPoints > 1 && window.innerWidth <= 768);
  }

  function isIOS() {
    return /iPhone|iPad|iPod/i.test(navigator.userAgent) ||
      (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
  }

  function hideMobileSavePanel() {
    if (els.mobileSavePanel) els.mobileSavePanel.classList.add('hidden');
    if (mobileSaveRevokeTimer) {
      clearTimeout(mobileSaveRevokeTimer);
      mobileSaveRevokeTimer = null;
    }
    if (mobileSaveUrl) {
      URL.revokeObjectURL(mobileSaveUrl);
      mobileSaveUrl = null;
    }
    if (els.mobileSaveLink) {
      els.mobileSaveLink.removeAttribute('href');
      els.mobileSaveLink.removeAttribute('download');
    }
  }

  function showMobileSavePanel(url, filename) {
    hideMobileSavePanel();
    mobileSaveUrl = url;
    if (els.mobileSaveLink) {
      els.mobileSaveLink.href = url;
      els.mobileSaveLink.download = filename;
      els.mobileSaveLink.textContent = 'Save ' + filename;
    }
    if (els.mobileSavePanel) els.mobileSavePanel.classList.remove('hidden');
    mobileSaveRevokeTimer = setTimeout(function () {
      hideMobileSavePanel();
    }, 30000);
  }

  function setDownloadBusy(button, busy, busyLabel) {
    if (!button) return;
    if (busy) {
      if (!button.dataset.defaultLabel) button.dataset.defaultLabel = button.textContent;
      button.disabled = true;
      button.textContent = busyLabel;
    } else {
      button.disabled = false;
      if (button.dataset.defaultLabel) button.textContent = button.dataset.defaultLabel;
    }
  }

  function fallbackSaveBlob(blob, filename) {
    var url = URL.createObjectURL(blob);
    if (isMobileSaveContext() || isIOS()) {
      showMobileSavePanel(url, filename);
      return Promise.resolve('panel');
    }
    var a = document.createElement('a');
    a.href = url;
    a.download = filename;
    a.style.display = 'none';
    document.body.appendChild(a);
    a.click();
    setTimeout(function () {
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
    }, 3000);
    return Promise.resolve('download');
  }

  function saveBlobFile(blob, filename, mimeType) {
    mimeType = mimeType || blob.type || 'application/octet-stream';
    if (isMobileSaveContext() && navigator.canShare && typeof File !== 'undefined') {
      try {
        var file = new File([blob], filename, { type: mimeType });
        if (navigator.canShare({ files: [file] })) {
          return navigator.share({ files: [file], title: filename }).then(function () {
            return 'share';
          }).catch(function (err) {
            if (err && err.name === 'AbortError') return 'cancelled';
            return fallbackSaveBlob(blob, filename);
          });
        }
      } catch (e) { /* fall through */ }
    }
    return fallbackSaveBlob(blob, filename);
  }

  function buildCompletePackageReadme(includeRefs, includeSheet) {
    var lines = [
      'YUMI COMPLETE AI PACKAGE',
      '',
      'Built from your Yumi Builder Step 1 selections and Step 2 customization.',
      '',
      'WORKFLOW (images first, then text):',
      ''
    ];
    var step = 1;
    if (includeRefs) {
      lines.push(step + '. Attach all images in references/ to your AI tool.');
      step += 1;
      if (getReplacementImageEntries().length) {
        lines.push(step + '. Attach replacement_refs/ images (Step 2 Instead uploads).');
        step += 1;
      }
      if (getPropImageEntries().length) {
        lines.push(step + '. Attach props/ images if present.');
        step += 1;
      }
    }
    if (includeSheet) {
      lines.push(step + '. Or attach contact-sheet.png (labeled grid of all package images).');
      step += 1;
    }
    lines.push(step + '. Open yumi-ai-package.txt and paste the full text into the same chat.');
    lines.push((step + 1) + '. Generate your image.');
    return lines.join('\n');
  }

  function wantsRefsInZip() {
    return !els.includeRefsInZip || els.includeRefsInZip.checked;
  }

  function wantsContactInZip() {
    return !!(els.includeContactInZip && els.includeContactInZip.checked);
  }

  function downloadCompletePackage() {
    if (typeof JSZip === 'undefined') {
      alert('Zip library did not load. Check your internet connection and refresh.');
      return;
    }

    var includeRefs = wantsRefsInZip();
    var includeSheet = wantsContactInZip();
    if (!includeRefs && !includeSheet) {
      alert('Choose at least one: Include reference images, or Include contact sheet.');
      return;
    }

    var refs = getExportRefs();
    if (!refs.length) {
      alert('Select at least one reference image in Step 1 first.');
      return;
    }

    setDownloadBusy(els.downloadCompleteZip, true, 'Building package...');
    setDownloadBusy(els.downloadContactSheet, true, 'Building package...');

    var zip = new JSZip();
    zip.file('README.txt', buildCompletePackageReadme(includeRefs, includeSheet));
    zip.file('yumi-ai-package.txt', els.outUnified ? els.outUnified.textContent : '');

    var chain = Promise.resolve();

    if (includeRefs) {
      var folder = zip.folder('references');
      chain = chain.then(function () {
        return Promise.all(refs.map(function (ref) {
          return fetchRefBlob(ref).then(function (blob) {
            folder.file(ref.bundleFilename, blob);
          });
        }));
      });
    }

    if (includeSheet) {
      chain = chain.then(function () {
        return buildContactSheetBlob(getAllPackageImageEntries()).then(function (sheetBlob) {
          if (sheetBlob) zip.file('contact-sheet.png', sheetBlob);
        });
      });
    }

    if (window.YumiCanonical) {
      var replacementFiles = YumiCanonical.getReplacementFilesForZip();
      if (replacementFiles.length) {
        var replFolder = zip.folder('replacement_refs');
        chain = chain.then(function () {
          replacementFiles.forEach(function (entry) {
            replFolder.file(entry.filename, entry.blob);
          });
        });
      }
    }

    var propFiles = getPropsForExport().filter(function (p) { return p.blob; });
    if (propFiles.length) {
      var propsFolder = zip.folder('props');
      chain = chain.then(function () {
        propFiles.forEach(function (entry) {
          propsFolder.file(entry.filename, entry.blob);
        });
      });
    }

    chain.then(function () {
      return zip.generateAsync({ type: 'blob' });
    }).then(function (blob) {
      return saveBlobFile(blob, 'yumi-complete-package.zip', 'application/zip');
    }).then(function (result) {
      if (result === 'cancelled') return;
      showCopyToast();
      els.copyToast.querySelector('strong').textContent = 'Package ready!';
      if (result === 'panel') {
        els.copyToast.querySelector('span').textContent = 'Tap Save below to download. Then attach images from the zip and paste yumi-ai-package.txt in your AI.';
      } else {
        els.copyToast.querySelector('span').textContent = 'Attach images from the zip (or contact sheet), then paste yumi-ai-package.txt in your already-open AI.';
      }
    }).catch(function (err) {
      alert('Could not build package: ' + err.message);
      console.error(err);
    }).finally(function () {
      setDownloadBusy(els.downloadCompleteZip, false);
      setDownloadBusy(els.downloadContactSheet, false);
    });
  }

  function downloadContactSheetOnly() {
    var entries = getAllPackageImageEntries();
    if (!entries.length) {
      alert('Select at least one reference image in Step 1 first.');
      return;
    }

    setDownloadBusy(els.downloadContactSheet, true, 'Building contact sheet...');
    setDownloadBusy(els.downloadCompleteZip, true, 'Building contact sheet...');

    buildContactSheetBlob(entries).then(function (blob) {
      return saveBlobFile(blob, 'yumi-contact-sheet.png', 'image/png');
    }).then(function (result) {
      if (result === 'cancelled') return;
      showCopyToast();
      els.copyToast.querySelector('strong').textContent = 'Contact sheet ready!';
      if (result === 'panel') {
        els.copyToast.querySelector('span').textContent = 'Tap Save below to download. Then attach the PNG in your already-open AI tool.';
      } else {
        els.copyToast.querySelector('span').textContent = 'Attach this one PNG in your already-open AI tool, then copy or paste the text package.';
      }
    }).catch(function (err) {
      alert('Could not build contact sheet: ' + err.message);
    }).finally(function () {
      setDownloadBusy(els.downloadContactSheet, false);
      setDownloadBusy(els.downloadCompleteZip, false);
    });
  }

  function downloadBundle() {
    var blob = new Blob([els.outUnified.textContent], { type: 'text/plain;charset=utf-8' });
    saveBlobFile(blob, 'yumi-ai-package.txt', 'text/plain').then(function (result) {
      if (result === 'cancelled') return;
      showCopyToast();
      els.copyToast.querySelector('strong').textContent = 'Downloaded!';
      if (result === 'panel') {
        els.copyToast.querySelector('span').textContent = 'Tap Save below to download yumi-ai-package.txt.';
      } else {
        els.copyToast.querySelector('span').textContent = 'yumi-ai-package.txt saved to your downloads folder.';
      }
    }).catch(function (err) {
      alert('Could not save file: ' + (err.message || err));
    });
  }

  function fetchRefBlob(ref) {
    if (ref.blob) return Promise.resolve(ref.blob);
    var url = refCopyUrl(ref);
    if (!url) return Promise.reject(new Error('No URL for ' + ref.attach_label));
    return fetch(url).then(function (r) {
      if (!r.ok) throw new Error('Failed to fetch ' + ref.attach_label);
      return r.blob();
    });
  }

  function loadImageFromBlob(blob) {
    return new Promise(function (resolve, reject) {
      var img = new Image();
      var url = URL.createObjectURL(blob);
      img.onload = function () {
        URL.revokeObjectURL(url);
        resolve(img);
      };
      img.onerror = function () {
        URL.revokeObjectURL(url);
        reject(new Error('Failed to load image'));
      };
      img.src = url;
    });
  }

  function buildContactSheetBlob(refs) {
    if (!refs.length) return Promise.reject(new Error('No images selected'));

    var cols = refs.length <= 4 ? 2 : 4;
    var rows = Math.ceil(refs.length / cols);
    var cellW = 512;
    var cellH = 640;
    var labelH = 36;
    var canvas = document.createElement('canvas');
    canvas.width = cols * cellW;
    canvas.height = rows * (cellH + labelH);
    var ctx = canvas.getContext('2d');
    ctx.fillStyle = '#ffffff';
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    return Promise.all(refs.map(function (ref) {
      return fetchRefBlob(ref).then(loadImageFromBlob).then(function (img) {
        return { ref: ref, img: img };
      });
    })).then(function (loaded) {
      loaded.forEach(function (item, index) {
        var col = index % cols;
        var row = Math.floor(index / cols);
        var x = col * cellW;
        var y = row * (cellH + labelH);
        var img = item.img;
        var scale = Math.min(cellW / img.width, cellH / img.height);
        var dw = img.width * scale;
        var dh = img.height * scale;
        var dx = x + (cellW - dw) / 2;
        var dy = y + (cellH - dh) / 2;
        ctx.fillStyle = '#e2e8f0';
        ctx.fillRect(x, y, cellW, cellH);
        ctx.drawImage(img, dx, dy, dw, dh);
        ctx.fillStyle = '#1a2744';
        ctx.fillRect(x, y + cellH, cellW, labelH);
        ctx.fillStyle = '#ffffff';
        ctx.font = 'bold 16px Georgia, serif';
        var tier = 'REC';
        if (item.ref.source === 'custom') tier = 'USER';
        else if (item.ref.source === 'replacement') tier = 'REPL';
        else if (item.ref.source === 'prop') tier = 'PROP';
        else if (item.ref.attach_priority_tier === 'critical') tier = 'CRITICAL';
        var orderLabel = item.ref.attach_order ? String(item.ref.attach_order) + ' ' : '';
        ctx.fillText(orderLabel + tier + ': ' + (item.ref.attach_label || item.ref.title || 'Image'), x + 8, y + cellH + 24);
      });

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

  function wireWizardNav() {
    document.querySelectorAll('[data-goto]').forEach(function (btn) {
      btn.addEventListener('click', function () {
        showStep(parseInt(btn.getAttribute('data-goto'), 10));
      });
    });

    document.querySelectorAll('.wizard-tab').forEach(function (tab) {
      tab.addEventListener('click', function () {
        showStep(parseInt(tab.getAttribute('data-step'), 10));
      });
    });
  }

  function wireRefToolbar() {
    if (els.refSelectAll) {
      els.refSelectAll.addEventListener('click', function () {
        setAllRefSelection(true);
        customRefs.forEach(function (ref) { selectedRefs[ref.id] = true; });
        syncRefCardCheckboxes();
        buildCustomRefGallery();
      });
    }
    if (els.refSelectCritical) {
      els.refSelectCritical.addEventListener('click', function () {
        setCriticalRefSelection();
        syncRefCardCheckboxes();
      });
    }
    if (els.customRefInput) {
      els.customRefInput.addEventListener('change', function () {
        addCustomRefFiles(els.customRefInput.files);
      });
    }
  }

  function wireComposerEvents() {
    ['posePreset', 'pose', 'expressionPreset', 'expression', 'framing', 'location', 'scene', 'lighting', 'notesForAi', 'cameraPov', 'cameraColor', 'cameraGrain', 'cameraCustom'].forEach(function (id) {
      var el = document.getElementById(id);
      if (el) {
        el.addEventListener('input', updateExports);
        el.addEventListener('change', updateExports);
      }
    });

    if (els.cameraPreset) {
      els.cameraPreset.addEventListener('change', function () {
        applyCameraPresetDefaults(els.cameraPreset.value);
        updateExports();
      });
    }

    document.querySelectorAll('input[name="wardrobeMode"]').forEach(function (radio) {
      radio.addEventListener('change', function () {
        toggleWardrobePanels();
        updateExports();
      });
    });

    if (els.customWardrobe) {
      els.customWardrobe.addEventListener('input', updateExports);
    }

    ['alsoAdd', 'additionalAppearance', 'propDescription'].forEach(function (id) {
      var el = document.getElementById(id);
      if (el) {
        el.addEventListener('input', updateExports);
        el.addEventListener('change', updateExports);
      }
    });
    if (els.propImage) {
      els.propImage.addEventListener('change', updateExports);
    }

    if (els.copyForAi) els.copyForAi.addEventListener('click', copyEverythingForAi);
    if (els.savePackageTxt) els.savePackageTxt.addEventListener('click', downloadBundle);
    if (els.downloadCompleteZip) els.downloadCompleteZip.addEventListener('click', downloadCompletePackage);
    if (els.downloadContactSheet) els.downloadContactSheet.addEventListener('click', downloadContactSheetOnly);
    if (els.mobileSaveDismiss) els.mobileSaveDismiss.addEventListener('click', hideMobileSavePanel);
    if (els.includeRefsInZip) {
      els.includeRefsInZip.addEventListener('change', updateExports);
    }
    if (els.includeContactInZip) {
      els.includeContactInZip.addEventListener('change', updateExports);
    }

    if (els.posePreset) {
      els.posePreset.addEventListener('change', function () {
        if (els.posePreset.value) els.pose.value = '';
        updateExports();
      });
    }
    if (els.expressionPreset) {
      els.expressionPreset.addEventListener('change', function () {
        if (els.expressionPreset.value) els.expression.value = '';
        updateExports();
      });
    }
  }

  function initBuilderAfterLoad(das, registry, canonicalData) {
    DAS = das;
    REGISTRY = registry;

    if (canonicalData && window.YumiCanonical) {
      YumiCanonical.load(canonicalData);
      renderCoreIdentity();
      renderCanonicalInventory();
    } else if (els.canonicalInventoryRoot) {
      els.canonicalInventoryRoot.innerHTML = '<p class="hint">Canonical inventory could not load. Re-upload <code>das/canonical-inventory.json</code> and refresh.</p>';
    }

    if (DAS.generation && DAS.generation.creative_fork_note && els.creativeForkNote) {
      els.creativeForkNote.textContent = DAS.generation.creative_fork_note;
    }
    if (DAS.defaults && DAS.defaults.camera_look && els.cameraPreset) {
      els.cameraPreset.value = DAS.defaults.camera_look.preset;
      if (els.cameraPov) els.cameraPov.value = normalizeCameraPov(DAS.defaults.camera_look.pov);
      if (els.cameraColor) els.cameraColor.value = DAS.defaults.camera_look.color;
      if (els.cameraGrain) els.cameraGrain.value = DAS.defaults.camera_look.grain;
    }
    try {
      var savedNotes = localStorage.getItem(NOTES_STORAGE_KEY);
      if (savedNotes && els.notesForAi) els.notesForAi.value = savedNotes;
      var savedStep = parseInt(localStorage.getItem(STEP_STORAGE_KEY), 10);
      if (savedStep >= 1 && savedStep <= 3) currentStep = savedStep;
    } catch (e) { /* ignore */ }
    toggleWardrobePanels();
    buildRefGallery();
    buildCustomRefGallery();
    wireRefToolbar();
    wireComposerEvents();
    hideLoadError();
    try {
      updateExports();
    } catch (exportErr) {
      console.error(exportErr);
      showLoadError(exportErr.message || String(exportErr));
    }
    showStep(currentStep);
  }

  wireWizardNav();
  wireOutboundLinks();
  showStep(1);

  Promise.all([
    fetchJson('das/DAS-ACT-0001-Yumi.json'),
    fetchJson('../reference_registry.json'),
    fetchJson('das/canonical-inventory.json').catch(function (err) {
      console.warn('Canonical inventory load failed (Builder will still run):', err);
      return null;
    })
  ]).then(function (results) {
    initBuilderAfterLoad(results[0], results[1], results[2]);
  }).catch(function (err) {
    showLoadError(err.message);
    if (els.outUnified) {
      els.outUnified.textContent = 'Error loading DAS data: ' + err.message;
    }
    console.error(err);
  });
})();
