(function (global) {
  'use strict';

  var CANONICAL = null;
  var itemStates = {};
  var onChangeCallback = null;

  function load(data) {
    CANONICAL = data;
    resetAllStates();
  }

  function resetAllStates() {
    itemStates = {};
    if (!CANONICAL || !CANONICAL.sections) return;
    CANONICAL.sections.forEach(function (section) {
      Object.keys(section.items || {}).forEach(function (key) {
        var item = section.items[key];
        itemStates[item.id] = {
          mode: 'match',
          insteadText: '',
          replacementFile: null,
          replacementFilename: '',
          replacementObjectUrl: ''
        };
      });
    });
  }

  function forEachItem(fn) {
    if (!CANONICAL) return;
    CANONICAL.sections.forEach(function (section) {
      Object.keys(section.items || {}).forEach(function (key) {
        fn(section, section.items[key]);
      });
    });
  }

  function getParentState(item) {
    if (!item.requires) return { active: true, mode: 'match' };
    var parent = itemStates[item.requires];
    if (!parent) return { active: true, mode: 'match' };
    if (item.requires_mode === 'match_or_instead') {
      return { active: parent.mode === 'match' || parent.mode === 'instead', mode: parent.mode };
    }
    return { active: parent.mode === 'match', mode: parent.mode };
  }

  function applyDependencySideEffects(changedId) {
    forEachItem(function (section, item) {
      if (item.requires !== changedId) return;
      var parent = itemStates[changedId];
      if (!parent) return;
      if (parent.mode === 'gone') {
        itemStates[item.id].mode = 'gone';
        itemStates[item.id].insteadText = '';
      } else if (parent.mode === 'instead' && item.requires_mode === 'match_or_instead') {
        if (itemStates[item.id].mode === 'match') {
          itemStates[item.id].mode = 'gone';
        }
      }
    });
  }

  function setItemMode(id, mode) {
    if (!itemStates[id]) return;
    itemStates[id].mode = mode;
    if (mode === 'match') {
      itemStates[id].insteadText = '';
      clearReplacementFile(id);
    }
    applyDependencySideEffects(id);
    if (onChangeCallback) onChangeCallback();
  }

  function setInsteadText(id, text) {
    if (!itemStates[id]) return;
    itemStates[id].insteadText = text;
    if (onChangeCallback) onChangeCallback();
  }

  function clearReplacementFile(id) {
    var st = itemStates[id];
    if (!st) return;
    if (st.replacementObjectUrl) {
      try { URL.revokeObjectURL(st.replacementObjectUrl); } catch (e) { /* ignore */ }
    }
    st.replacementFile = null;
    st.replacementFilename = '';
    st.replacementObjectUrl = '';
  }

  function setReplacementFile(id, file) {
    if (!itemStates[id] || !file) return;
    clearReplacementFile(id);
    itemStates[id].replacementFile = file;
    itemStates[id].replacementFilename = file.name;
    itemStates[id].replacementObjectUrl = URL.createObjectURL(file);
    if (onChangeCallback) onChangeCallback();
  }

  function exportLineForItem(item) {
    return item.ai_prompt_line || item.canonical_line || item.description || '';
  }

  function isItemActiveForExport(item) {
    var parent = getParentState(item);
    if (!parent.active && item.requires) return false;
    return true;
  }

  function visibleSection(section, customWardrobeMode) {
    if (!customWardrobeMode) return true;
    return section.visible_in_custom_wardrobe !== false;
  }

  function validate(customWardrobeMode) {
    var errors = [];
    if (customWardrobeMode) return errors;
    forEachItem(function (section, item) {
      if (!visibleSection(section, false)) return;
      if (!isItemActiveForExport(item)) return;
      var st = itemStates[item.id];
      if (!st || st.mode !== 'instead') return;
      var text = (st.insteadText || '').trim();
      if (item.instead_required && !text) {
        if (item.gone_means === 'bald' && item.id === 'hair_style') return;
        errors.push(item.description + ': describe the replacement or choose Gone.');
      }
    });
    return errors;
  }

  function buildMatchLines(customWardrobeMode) {
    var lines = [];
    if (customWardrobeMode) return lines;
    forEachItem(function (section, item) {
      if (!visibleSection(section, customWardrobeMode)) return;
      if (!isItemActiveForExport(item)) return;
      var st = itemStates[item.id];
      if (st && st.mode === 'match') {
        lines.push('INCLUDE: ' + exportLineForItem(item));
      }
    });
    return lines;
  }

  function buildDeviationLines(customWardrobeMode) {
    var lines = [];
    if (customWardrobeMode) return lines;
    forEachItem(function (section, item) {
      if (!visibleSection(section, customWardrobeMode)) return;
      if (!isItemActiveForExport(item)) return;
      var st = itemStates[item.id];
      if (!st || st.mode === 'match') return;
      if (st.mode === 'gone') {
        lines.push('REMOVE: ' + (item.remove_line || item.description));
      } else if (st.mode === 'instead') {
        lines.push('REMOVE: ' + (item.remove_line || item.canonical_line || item.description));
        var text = (st.insteadText || '').trim();
        if (!text && item.gone_means === 'bald') text = 'bald head, no hair';
        if (text) {
          lines.push('REPLACE WITH: ' + text);
          if (st.replacementFilename) {
            lines.push('Attach replacement reference: replacement_refs/' + sanitizeFilename(st.replacementFilename));
          }
        }
        if (item.nsfw_warn) {
          lines.push('(Note: edgy replacement - some AI tools may reject)');
        }
      }
    });
    return lines;
  }

  function buildChecklistLines(customWardrobeMode) {
    var lines = [];
    if (customWardrobeMode) {
      lines.push('[ ] Recognizable as same person as attached refs');
      lines.push('[ ] Face identity preserved (freckles, beauty marks, core features)');
      lines.push('[ ] Custom wardrobe applied as described');
      lines.push('[ ] Not a near-duplicate of any reference photo');
      return lines;
    }
    forEachItem(function (section, item) {
      if (!visibleSection(section, false)) return;
      if (!isItemActiveForExport(item)) return;
      var st = itemStates[item.id];
      if (st && st.mode === 'match') {
        lines.push('[ ] ' + (item.description || item.id) + ' matches references');
      } else if (st && st.mode === 'instead' && (st.insteadText || '').trim()) {
        lines.push('[ ] ' + item.description + ' replaced as described');
      }
    });
    lines.push('[ ] Not a near-duplicate of any reference photo');
    lines.push('[ ] Skin looks natural (pores visible), not beauty-filter smooth');
    return lines;
  }

  function getReplacementFilesForZip() {
    var files = [];
    forEachItem(function (section, item) {
      var st = itemStates[item.id];
      if (st && st.replacementFile) {
        files.push({
          folder: 'replacement_refs',
          filename: sanitizeFilename(item.id + '-' + st.replacementFilename),
          blob: st.replacementFile,
          label: item.description
        });
      }
    });
    return files;
  }

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

  function renderInventory(rootEl, options) {
    if (!rootEl || !CANONICAL) return;
    onChangeCallback = options && options.onChange ? options.onChange : null;
    var customMode = options && options.customWardrobeMode;
    rootEl.innerHTML = '';

    CANONICAL.sections.forEach(function (section) {
      if (!visibleSection(section, customMode)) return;

      var fs = document.createElement('fieldset');
      fs.className = 'canonical-section';
      fs.dataset.section = section.id;

      var legend = document.createElement('legend');
      legend.textContent = section.label;
      fs.appendChild(legend);

      Object.keys(section.items || {}).forEach(function (key) {
        var item = section.items[key];
        fs.appendChild(buildItemRow(item));
      });

      rootEl.appendChild(fs);
    });
  }

  function buildItemRow(item) {
    var st = itemStates[item.id] || { mode: 'match', insteadText: '' };
    var parent = getParentState(item);
    var row = document.createElement('div');
    row.className = 'canonical-item' + (item.signature ? ' signature' : '');
    row.dataset.itemId = item.id;

    var head = document.createElement('p');
    head.className = 'canonical-item-desc';
    head.textContent = item.description + (item.signature ? ' (signature)' : '');
    row.appendChild(head);

    if (item.requires && !parent.active) {
      var dep = document.createElement('p');
      dep.className = 'hint canonical-dep';
      dep.textContent = 'Follows ' + item.requires + ' (parent not matching).';
      row.appendChild(dep);
    }

    var modes = document.createElement('div');
    modes.className = 'canonical-modes';

    ['match', 'gone', 'instead'].forEach(function (mode) {
      if (mode === 'gone' && item.instead_required && item.gone_means !== 'bald' && item.id !== 'hair_style') {
        return;
      }
      var label = document.createElement('label');
      label.className = 'radio canonical-mode';
      var input = document.createElement('input');
      input.type = 'radio';
      input.name = 'mode-' + item.id;
      input.value = mode;
      input.checked = st.mode === mode;
      input.disabled = item.requires && !parent.active && mode === 'match';
      input.addEventListener('change', function () {
        setItemMode(item.id, mode);
      });
      var modeLabel = mode === 'match' ? 'Match references' : mode === 'gone' ? 'Gone' : 'Instead';
      label.appendChild(input);
      label.appendChild(document.createTextNode(' ' + modeLabel));
      modes.appendChild(label);
    });
    row.appendChild(modes);

    var insteadWrap = document.createElement('div');
    insteadWrap.className = 'canonical-instead' + (st.mode === 'instead' ? '' : ' hidden');

    var insteadInput = document.createElement('input');
    insteadInput.type = 'text';
    insteadInput.className = 'canonical-instead-text';
    insteadInput.placeholder = item.gone_means === 'bald' ? 'e.g. tight bun, long loose hair (blank = bald if Gone selected on hairstyle)' : 'Describe replacement...';
    insteadInput.value = st.insteadText || '';
    insteadInput.addEventListener('input', function () {
      setInsteadText(item.id, insteadInput.value);
    });
    insteadWrap.appendChild(insteadInput);

    var fileLabel = document.createElement('label');
    fileLabel.className = 'hint canonical-ref-upload';
    fileLabel.textContent = 'Optional replacement reference image:';
    var fileInput = document.createElement('input');
    fileInput.type = 'file';
    fileInput.accept = 'image/png,image/jpeg,image/webp,image/gif';
    fileInput.addEventListener('change', function () {
      if (fileInput.files && fileInput.files[0]) setReplacementFile(item.id, fileInput.files[0]);
    });
    insteadWrap.appendChild(fileLabel);
    insteadWrap.appendChild(fileInput);

    row.appendChild(insteadWrap);

    row.querySelectorAll('input[name="mode-' + item.id + '"]').forEach(function (radio) {
      radio.addEventListener('change', function () {
        insteadWrap.classList.toggle('hidden', itemStates[item.id].mode !== 'instead');
      });
    });

    return row;
  }

  function getCanonicalShotPayload(customWardrobeMode, customWardrobeText) {
    return {
      mode: customWardrobeMode ? 'custom_wardrobe' : 'signature_look',
      custom_wardrobe_text: customWardrobeMode ? (customWardrobeText || null) : null,
      item_states: JSON.parse(JSON.stringify(itemStates)),
      validation_errors: validate(customWardrobeMode)
    };
  }

  function buildCanonicalExportBlocks(customWardrobeMode, customWardrobeText, alsoAdd, additionalDetails, props) {
    var parts = [];
    parts.push('=== VISUAL REFERENCE AUTHORITY ===');
    parts.push(CANONICAL.visual_reference_authority || '');
    parts.push('');
    if (CANONICAL.ref_drift_note) {
      parts.push(CANONICAL.ref_drift_note);
      parts.push('');
    }
    parts.push('=== CORE IDENTITY (always - same person) ===');
    (CANONICAL.core_identity || []).forEach(function (line) {
      parts.push(line);
    });
    parts.push('');

    if (customWardrobeMode) {
      parts.push('=== CUSTOM WARDROBE (bulk outfit override) ===');
      parts.push(customWardrobeText || '(unspecified custom outfit)');
      parts.push('');
    } else {
      var allMatch = buildMatchLines(false);
      if (allMatch.length) {
        parts.push('=== CANONICAL SIGNATURE - MATCH REFERENCES ===');
        allMatch.forEach(function (line) { parts.push(line); });
        parts.push('');
      }
      var devs = buildDeviationLines(false);
      if (devs.length) {
        parts.push('=== DEVIATIONS FROM CANONICAL ===');
        devs.forEach(function (line) { parts.push(line); });
        parts.push('');
      }
      if (CANONICAL.canonical_master_prompt && allMatch.length > 20) {
        parts.push('=== CANONICAL MASTER SUMMARY (all-match shorthand) ===');
        parts.push(CANONICAL.canonical_master_prompt);
        parts.push('');
      }
    }

    if (alsoAdd) {
      parts.push('=== ADDITIONALLY INCLUDE ===');
      parts.push(alsoAdd);
      parts.push('');
    }
    if (additionalDetails) {
      parts.push('=== ADDITIONAL APPEARANCE DETAILS ===');
      parts.push(additionalDetails);
      parts.push('Apply together with per-row deviations above. Same person as Yumi unless explicitly stated otherwise.');
      parts.push('');
    }
    if (props && props.length) {
      parts.push('=== PROP ADDITIONS ===');
      props.forEach(function (p, i) {
        parts.push('PROP ' + (i + 1) + ': ' + p.description);
        if (p.filename) parts.push('Attach prop reference: props/' + p.filename);
      });
      parts.push('');
    }
    if (CANONICAL.priority_tiers && CANONICAL.priority_tiers.length) {
      parts.push('=== ATTACHMENT PRIORITY (if AI limits file count) ===');
      CANONICAL.priority_tiers.forEach(function (t) { parts.push(t); });
      parts.push('');
    }
    return parts.join('\n');
  }

  global.YumiCanonical = {
    load: load,
    renderInventory: renderInventory,
    buildChecklistLines: buildChecklistLines,
    buildCanonicalExportBlocks: buildCanonicalExportBlocks,
    getCanonicalShotPayload: getCanonicalShotPayload,
    getReplacementFilesForZip: getReplacementFilesForZip,
    getData: function () { return CANONICAL; }
  };
})(window);
