(function attachPyroReusableAssets(root) {
  'use strict';

  const FillPaint = root.PyroFillPaint || (typeof require === 'function' ? require('../fills/paint.js') : null);
  const StrokeStyle = root.PyroStrokeStyle || (typeof require === 'function' ? require('../strokes/style.js') : null);
  const Effects = root.PyroEffects || (typeof require === 'function' ? require('../effects/stack.js') : null);
  const RESOURCE_KEYS = Object.freeze(['symbols', 'objectStyles', 'fillPresets', 'strokePresets']);
  const deepClone = (value) => value == null ? value : JSON.parse(JSON.stringify(value));
  const plainObject = (value) => Boolean(value && typeof value === 'object' && !Array.isArray(value));

  function emptyLibrary() {
    return { symbols: [], objectStyles: [], fillPresets: [], strokePresets: [] };
  }

  function ensureLibrary(document) {
    if (!plainObject(document.resources)) document.resources = emptyLibrary();
    RESOURCE_KEYS.forEach((key) => { if (!Array.isArray(document.resources[key])) document.resources[key] = []; });
    return document.resources;
  }

  function libraryCounts(document) {
    const library = ensureLibrary(document);
    return Object.fromEntries(RESOURCE_KEYS.map((key) => [key, library[key].length]));
  }

  const appearanceKeys = Object.freeze([
    'fill', 'fillPaint', 'stroke', 'strokePaint', 'strokeWidth', 'strokeType', 'strokeStack', 'textStroke', 'textStrokeWidth', 'effects', 'svgFilterSettings'
  ]);

  function captureObjectStyle(object) {
    const style = {};
    appearanceKeys.forEach((key) => {
      if (Object.prototype.hasOwnProperty.call(object || {}, key)) style[key] = deepClone(object[key]);
    });
    return style;
  }

  function applyObjectStyle(object, style, { createId } = {}) {
    if (!object || !plainObject(style)) return false;
    appearanceKeys.forEach((key) => delete object[key]);
    appearanceKeys.forEach((key) => {
      if (Object.prototype.hasOwnProperty.call(style, key)) object[key] = deepClone(style[key]);
    });
    if (object.fillPaint && FillPaint) object.fillPaint = FillPaint.normalizePaint(object.fillPaint, { fallbackColor: object.fill || '#4f6df5', href: object.fillPaint.href });
    if (object.strokePaint && FillPaint) object.strokePaint = FillPaint.normalizePaint(object.strokePaint, { fallbackColor: object.textStroke || object.stroke || '#172033', href: object.strokePaint.href });
    if (Array.isArray(object.strokeStack) && StrokeStyle) {
      object.strokeStack = StrokeStyle.normalizeStack(object.strokeStack, { fallback: StrokeStyle.legacyStyle(object), createId });
      object.strokeStack.forEach((stroke) => { if (typeof createId === 'function') stroke.id = createId('stroke'); });
      StrokeStyle.syncLegacy(object);
    }
    if (Array.isArray(object.effects) && Effects) {
      object.effects = Effects.normalizeStack(object.effects, { createId });
      object.effects.forEach((effect) => { if (typeof createId === 'function') effect.id = createId('effect'); });
    }
    return true;
  }

  function objectTrees(objects) {
    const output = [];
    (objects || []).forEach(function visit(object) {
      if (!object || typeof object !== 'object') return;
      output.push(object); (object.children || []).forEach(visit);
    });
    return output;
  }

  function referencedSymbolIds(objects) {
    return new Set(objectTrees(objects).flatMap((object) => [object.symbolId, object.symbolMasterId]).filter(Boolean));
  }

  function referencedLibrary(objects, document) {
    const library = ensureLibrary(document); const symbolIds = referencedSymbolIds(objects);
    return {
      symbols: library.symbols.filter((entry) => symbolIds.has(entry.id)).map(deepClone),
      objectStyles: [], fillPresets: [], strokePresets: []
    };
  }

  function rewriteSymbolReferences(objects, idMap) {
    objectTrees(objects).forEach((object) => {
      if (object.symbolId && idMap.has(object.symbolId)) object.symbolId = idMap.get(object.symbolId);
      if (object.symbolMasterId && idMap.has(object.symbolMasterId)) object.symbolMasterId = idMap.get(object.symbolMasterId);
    });
    return objects;
  }

  function comparableResource(resource) {
    const copy = deepClone(resource); delete copy.id; return JSON.stringify(copy);
  }

  function mergeLibrary(targetDocument, incoming, { createId } = {}) {
    const target = ensureLibrary(targetDocument); const source = plainObject(incoming) ? incoming : emptyLibrary();
    const idMaps = Object.fromEntries(RESOURCE_KEYS.map((key) => [key, new Map()]));
    const added = Object.fromEntries(RESOURCE_KEYS.map((key) => [key, 0]));
    RESOURCE_KEYS.forEach((key) => {
      (Array.isArray(source[key]) ? source[key] : []).forEach((raw) => {
        if (!plainObject(raw)) return;
        const entry = deepClone(raw); const oldId = String(entry.id || '');
        const same = oldId && target[key].find((candidate) => candidate.id === oldId);
        if (same && comparableResource(same) === comparableResource(entry)) { idMaps[key].set(oldId, same.id); return; }
        if (!oldId || same || target[key].some((candidate) => candidate.id === entry.id)) {
          entry.id = typeof createId === 'function' ? createId(key.slice(0, -1)) : `${key.slice(0, -1)}-${Math.random().toString(36).slice(2, 10)}`;
        }
        target[key].push(entry); if (oldId) idMaps[key].set(oldId, entry.id); added[key] += 1;
      });
    });
    return { library: target, idMaps, added, totalAdded: Object.values(added).reduce((sum, value) => sum + value, 0) };
  }

  const api = Object.freeze({
    RESOURCE_KEYS, emptyLibrary, ensureLibrary, libraryCounts,
    captureObjectStyle, applyObjectStyle, referencedSymbolIds, referencedLibrary, rewriteSymbolReferences, mergeLibrary
  });
  root.PyroReusableAssets = api;
  if (typeof module === 'object' && module?.exports) module.exports = api;
})(typeof globalThis !== 'undefined' ? globalThis : window);
