"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var ModelVariantTraversal_exports = {};
__export(ModelVariantTraversal_exports, {
  ModelVariantTraversal: () => ModelVariantTraversal
});
module.exports = __toCommonJS(ModelVariantTraversal_exports);
var import_general = require("@matter/general");
var import_common = require("../common/index.js");
var import_models = require("../models/index.js");
var import_ModelTraversal = require("./ModelTraversal.js");
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const logger = import_general.Logger.get("ModelVariantTraversal");
class ModelVariantTraversal {
  /**
   * Create a new visitor.  Must list the valid names of sources.  The order of this list implies the priority used
   * for choosing a name when multiple model variants have different names.
   */
  constructor(revision, sourceNames) {
    this.revision = revision;
    this.sourceNames = sourceNames;
  }
  revision;
  sourceNames;
  clusterState;
  visiting = false;
  modelTraversal = new import_ModelTraversal.ModelTraversal();
  /**
   * Initiate traversal.  The class is stateful so this call should not be invoked while traversal is ongoing.
   */
  traverse(variants) {
    if (this.visiting) {
      throw new import_general.InternalError("ModelVariantVisitor.visit called with active visit; reentrancy not supported");
    }
    delete this.clusterState;
    this.visiting = true;
    try {
      return this.visitVariants(
        this.createVariantDetail(
          Object.fromEntries(
            Object.entries(variants).map(([sourceName, element]) => {
              if (!(element instanceof import_models.Model)) {
                element = import_models.Model.create(element);
              }
              return [sourceName, element];
            })
          )
        )
      );
    } finally {
      this.visiting = false;
    }
  }
  /**
   * Get the canonical name for a model.  Within cluster scope alternate names may be selected, otherwise the name of
   * the model is returned.
   */
  getCanonicalName(model) {
    if (this.clusterState) {
      const name = this.clusterState.canonicalNames.get(model);
      if (name !== void 0) {
        return name;
      }
    }
    return model.name;
  }
  /**
   * Determine if we are entering a cluster and install cluster state if so.
   */
  enterCluster(variants) {
    if (variants.tag === import_common.ElementTag.Cluster) {
      this.clusterState = {
        canonicalNames: computeCanonicalNames(this.revision, this.sourceNames, variants)
      };
      return true;
    }
    return false;
  }
  /**
   * This is the function that actually recurses during the visit.
   */
  visitVariants(variants) {
    const state = this.modelTraversal.operation(() => {
      return this.visit(variants, () => {
        const enteredCluster = this.enterCluster(variants);
        const mappings = this.mapChildren(variants);
        const result = Array();
        mappings: for (const childVariants of mappings) {
          const detail = this.createVariantDetail(childVariants);
          let inherited = false;
          let overrides = false;
          if (variants.tag === import_common.ElementTag.Cluster) {
            for (const k in variants.map) {
              if (childVariants[k] === void 0) {
                const inheritedModel = variants.map[k].base?.member(detail.name, [detail.tag]);
                if (inheritedModel) {
                  inherited = true;
                }
                continue;
              }
              const child = childVariants[k];
              if (child instanceof import_models.ValueModel && child.overridesShadow) {
                overrides = true;
              }
            }
          }
          if (inherited && !overrides) {
            continue mappings;
          }
          result.push(this.visitVariants(detail));
        }
        if (enteredCluster) {
          delete this.clusterState;
        }
        return result;
      });
    });
    return state;
  }
  /**
   * Group children across variants with the same identity.
   */
  mapChildren(variants) {
    const multimaps = this.mapChildrenToMultimap(variants);
    const result = Array();
    for (const multimap of multimaps) {
      const maps = Array();
      for (const incomingSource in multimap) {
        let addUnmatched2 = function(variant) {
          if (unmatchedIncoming) {
            unmatchedIncoming.push(variant);
          } else {
            unmatchedIncoming = [variant];
          }
        };
        var addUnmatched = addUnmatched2;
        const variants2 = multimap[incomingSource];
        let unmatchedIncoming;
        nextIncomingVariant: for (const incomingVariant of variants2) {
          const incomingConformance = conformanceStr(incomingVariant);
          if (incomingConformance === void 0) {
            addUnmatched2(incomingVariant);
            continue;
          }
          for (const establishedMap of maps) {
            if (establishedMap[incomingSource]) {
              continue;
            }
            for (const establishedSource in establishedMap) {
              const establishedConformance = conformanceStr(establishedMap[establishedSource]);
              if (establishedConformance === void 0 || establishedConformance !== incomingConformance) {
                continue;
              }
              establishedMap[incomingSource] = incomingVariant;
              continue nextIncomingVariant;
            }
          }
          addUnmatched2(incomingVariant);
        }
        if (!unmatchedIncoming) {
          continue;
        }
        nextUnmatched: for (const variant of unmatchedIncoming) {
          for (const established of maps) {
            if (established[incomingSource] === void 0) {
              established[incomingSource] = variant;
              continue nextUnmatched;
            }
          }
          maps.push({ [incomingSource]: variant });
        }
      }
      result.push(...maps);
    }
    return result;
  }
  /**
   * Group children across variants with the same identity allowing for multiple definitions of an identity.  This is
   * the primary function of {@link mapChildren} but allows for multiple definitions of the same element.
   */
  mapChildrenToMultimap(variants) {
    const mappings = {};
    for (const [sourceName, variant] of Object.entries(variants.map)) {
      for (let i = 0; i < variant.children.length; i++) {
        const child = variant.children[i];
        if (!child.appliesTo(this.revision)) {
          continue;
        }
        const mapping = mappings[child.tag] || (mappings[child.tag] = { slots: [], idToSlot: {}, nameToSlot: {} });
        let idKey;
        let nameKey;
        if (child.matchTo !== void 0) {
          const { id, name } = child.matchTo;
          if (id !== void 0) {
            idKey = id.toString();
          }
          if (name !== void 0) {
            nameKey = name.toString();
          }
        } else {
          idKey = child.key;
          nameKey = this.getCanonicalName(child);
        }
        if (child.discriminator !== void 0) {
          nameKey = `${nameKey}\u241C${child.discriminator}`;
        }
        let slot;
        if (idKey !== void 0) {
          slot = mapping.idToSlot[idKey];
        }
        if (slot === void 0 && nameKey !== void 0) {
          slot = mapping.nameToSlot[nameKey];
        }
        if (slot === void 0) {
          slot = mapping.slots.length;
          mapping.slots.push({});
        }
        if (idKey !== void 0 && mapping.idToSlot[idKey] === void 0) {
          mapping.idToSlot[idKey] = slot;
        }
        if (nameKey !== void 0 && mapping.nameToSlot[nameKey] === void 0) {
          mapping.nameToSlot[nameKey] = slot;
        }
        if (mapping.slots[slot][sourceName]) {
          mapping.slots[slot][sourceName].push(child);
        } else {
          mapping.slots[slot][sourceName] = [child];
        }
      }
    }
    return Object.values(mappings).flatMap((v) => v.slots);
  }
  /**
   * Create a VariantDetail from a VariantMap.
   */
  createVariantDetail(map) {
    let tag;
    let id;
    let name;
    this.sourceNames.forEach((sourceName) => {
      const variant = map[sourceName];
      if (variant) {
        if (!tag) {
          tag = variant.tag;
        } else if (tag !== variant.tag) {
          throw new import_general.InternalError(
            `Variant tag mismatch; previous variant identified as ${tag} but ${sourceName} identifies as ${tag}`
          );
        }
        if (!id) {
          id = variant.id;
        }
        if (!name) {
          name = this.getCanonicalName(variant);
        }
      }
    });
    if (!tag) {
      throw new import_general.InternalError("No tag identified in variant set");
    }
    if (!name) {
      throw new import_general.InternalError("No name identified in variant set");
    }
    return { tag, id, name, map };
  }
}
function computeCanonicalNames(revision, sourceNames, variants) {
  const datatypeNameMap = /* @__PURE__ */ new Map();
  let numberOfMappings;
  do {
    numberOfMappings = datatypeNameMap.size;
    inferEquivalentDatatypes(revision, sourceNames, variants, datatypeNameMap);
  } while (numberOfMappings !== datatypeNameMap.size);
  const canonicalNames = chooseCanonicalNames(revision, sourceNames, variants, datatypeNameMap);
  return canonicalNames;
}
function inferEquivalentDatatypes(revision, sourceNames, variants, datatypeNameMap) {
  const nameVariants = /* @__PURE__ */ new Map();
  const traversal = new class extends ModelVariantTraversal {
    visit(variants2, recurse) {
      let mapEntry;
      for (let priority = 0; priority < sourceNames.length; priority++) {
        const sourceName = sourceNames[priority];
        const variant = variants2.map[sourceName];
        const base = variant?.base;
        if (!base || base.parent?.tag !== import_common.ElementTag.Cluster) {
          continue;
        }
        if (!mapEntry) {
          mapEntry = {
            mapTo: base.name,
            priority
          };
        }
        const existingEntry = nameVariants.get(base);
        if (existingEntry) {
          if (existingEntry.priority > mapEntry.priority) {
            nameVariants.set(base, mapEntry);
          } else if (existingEntry.priority === mapEntry.priority && existingEntry.mapTo !== mapEntry.mapTo) {
            logger.warn(
              `Mapping ${sourceName} ${base.tag} ${base.name} to ${existingEntry.mapTo} but it also maps to ${mapEntry.mapTo}`
            );
          }
        } else {
          nameVariants.set(base, mapEntry);
        }
      }
      recurse();
    }
    enterCluster() {
      if (variants.tag === import_common.ElementTag.Cluster) {
        this.clusterState = { canonicalNames: datatypeNameMap };
        return true;
      }
      return false;
    }
  }(revision, sourceNames);
  traversal.traverse(variants.map);
  for (const [model, mapEntry] of nameVariants) {
    if (mapEntry.mapTo && mapEntry.mapTo !== model.name) {
      datatypeNameMap.set(model, mapEntry.mapTo);
    }
  }
}
function chooseCanonicalNames(revision, sourceNames, variants, datatypeNameMap) {
  const canonicalNames = /* @__PURE__ */ new Map();
  const traversal = new class extends ModelVariantTraversal {
    visit(variants2, recurse) {
      let canonicalName;
      for (let i = 0; i < sourceNames.length; i++) {
        const name = variants2.map[sourceNames[i]]?.name;
        if (!i && name !== void 0) {
          break;
        }
        if (!canonicalName) {
          canonicalName = name;
          continue;
        }
        if (name?.length < canonicalName?.length) {
          canonicalName = name;
          continue;
        }
        if (canonicalName?.toLowerCase() === name?.toLowerCase()) {
          if (canonicalName === name) {
            continue;
          }
          if (canonicalName.replace(/[^A-Z]/g, "").length < name.replace(/[^A-Z]/g, "").length) {
            canonicalName = name;
            continue;
          }
        }
      }
      if (!canonicalName) {
        canonicalName = variants2.name;
      }
      for (const sourceName in variants2.map) {
        const variant = variants2.map[sourceName];
        if (variant.name !== canonicalName) {
          canonicalNames.set(variant, canonicalName);
        }
      }
      recurse();
    }
    enterCluster(variants2) {
      if (variants2.tag === import_common.ElementTag.Cluster) {
        this.clusterState = { canonicalNames: datatypeNameMap };
        return true;
      }
      return false;
    }
  }(revision, sourceNames);
  traversal.traverse(variants.map);
  return canonicalNames;
}
function conformanceStr(model) {
  const conformance = model.conformance?.toString();
  if (conformance === "") {
    return void 0;
  }
  return conformance;
}
//# sourceMappingURL=ModelVariantTraversal.js.map
