/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { InternalError } from "@matter/general";
import { Constraint } from "../aspects/index.js";
import { ElementTag, Metatype } from "../common/index.js";
import { Model, ValueModel } from "../models/index.js";
import { ModelVariantTraversal } from "./ModelVariantTraversal.js";
function MergedModel(revision, variants, priorities = MergedModel.DefaultPriorities) {
  const priority = new PriorityHandler(priorities || MergedModel.DefaultPriorities);
  const visitor = new MergeTraversal(revision, priority, (variants2, recurse) => {
    const merged = merge(variants2);
    if (variants2.tag === ElementTag.Cluster) {
      reparentToCanonicalParent(revision, priority, variants2);
    }
    const manual = priority.get(variants2.tag, "type")[0];
    if (merged.type && variants2.map[manual]?.type === merged.type && !variants2.map[manual].children?.length) {
      return merged;
    }
    if (merged.type) {
      for (const variant of Object.values(variants2.map)) {
        if (variant.type === void 0) {
          variant.type = merged.type;
        }
      }
    }
    merged.children = recurse();
    return merged;
  });
  return visitor.traverse(variants);
  function merge(variants2) {
    const variantValues = Object.fromEntries(
      Object.entries(variants2.map).map(([variantName, variant]) => [variantName, variant.valueOf()])
    );
    const keys = new Set(Object.values(variantValues).flatMap((v) => Object.keys(v)));
    keys.delete("children");
    const properties = Object.fromEntries([...keys].map((k) => [k, visitor.pluck(variants2.tag, k, variantValues)]));
    if (properties.type) {
      const type = visitor.chooseType(variants2);
      if (type?.type !== void 0 && type?.type !== null) {
        properties.type = type.type;
      }
    }
    const constraint = new Constraint(properties.constraint);
    if (constraint.desc) {
      for (const key of priority.get("*", "constraint")) {
        const definition = variants2.map[key]?.constraint;
        const constraint2 = new Constraint(definition);
        if (!constraint2?.isEmpty && !constraint2?.desc) {
          properties.constraint = definition;
          break;
        }
      }
    }
    return Model.create(properties);
  }
}
class MergeTraversal extends ModelVariantTraversal {
  constructor(revision, priority, visitor) {
    super(revision, priority.get("*", "type"));
    this.priority = priority;
    this.visitor = visitor;
  }
  visit(variants, recurse) {
    return this.visitor(variants, recurse);
  }
  /**
   * Use priority rules to select a single value from available variants.
   */
  pluck(tag, fieldName, variantValues) {
    const variantPriorities = this.priority.get(tag, fieldName);
    for (const variantName of variantPriorities) {
      const variantValue = variantValues[variantName]?.[fieldName];
      if (variantValue !== void 0) {
        return variantValue;
      }
    }
  }
  /**
   * Type selection is more complicated than other fields.
   */
  chooseType(variants) {
    const variantPriorities = this.priority.get(variants.tag, "type");
    let type;
    let metatype;
    for (const sourceName of variantPriorities) {
      const variant = variants.map[sourceName];
      if (!variant) {
        continue;
      }
      if (sourceName === variantPriorities[0]) {
        return variant;
      }
      if (!type) {
        type = variant;
        if (type instanceof ValueModel) {
          metatype = type.effectiveMetatype;
        }
        continue;
      }
      if (!(type instanceof ValueModel) || !(variant instanceof ValueModel)) {
        continue;
      }
      let overridePriority;
      const variantMetatype = variant.effectiveMetatype;
      if (!metatype) {
        if (variantMetatype) {
          overridePriority = true;
        }
      } else if (metatype === Metatype.integer) {
        const variantMetatype2 = variant.effectiveMetatype;
        if (variantMetatype2 === Metatype.enum || variantMetatype2 === Metatype.bitmap) {
          overridePriority = true;
        }
      }
      if (overridePriority) {
        type = variant;
        metatype = variantMetatype;
      }
    }
    return type;
  }
}
class PriorityHandler {
  constructor(priorities) {
    this.priorities = priorities;
  }
  /**
   * Get the priority for a specific tag and field.
   */
  get(typeName, fieldName) {
    const priority = this.priorities[typeName]?.[fieldName] || this.priorities["*"]?.[fieldName] || this.priorities[typeName]?.["*"] || this.priorities["*"]?.["*"];
    if (!priority) {
      throw new InternalError("No default (*, *) priority available");
    }
    return priority;
  }
}
function reparentToCanonicalParent(revision, priority, variants) {
  const deparented = Array();
  const traversal = new MergeTraversal(revision, priority, (variants2, recurse) => {
    const type = traversal.chooseType(variants2);
    if (!(type instanceof ValueModel)) {
      recurse();
      return;
    }
    if (type.base?.isSeed && Metatype.hasChildren(type.effectiveMetatype)) {
      for (const variantName in variants2.map) {
        const variant = variants2.map[variantName];
        if (variant === type || variant.children.length) {
          continue;
        }
        const base = variant.base;
        if (!(base instanceof ValueModel) || base.parent?.tag !== ElementTag.Cluster || !base.children.length) {
          continue;
        }
        if (base.parent?.references(base).length > 1) {
          continue;
        }
        deparented.push(base);
        variant.children = base.children;
        variant.type = base.type;
      }
    }
    recurse();
  });
  traversal.traverse(variants.map);
  deparented.forEach((m) => m.parent = void 0);
  return variants;
}
((MergedModel2) => {
  MergedModel2.DefaultPriorities = {
    "*": {
      "*": ["local", "chip", "spec"],
      // Prefer spec for elements that are insufficiently defined in chip
      conformance: ["local", "spec", "chip"],
      constraint: ["local", "spec", "chip"],
      quality: ["local", "spec", "chip"],
      access: ["local", "spec", "chip"],
      // Prefer spec for element names
      name: ["local", "spec", "chip"],
      // Prefer spec for datatype names (must match element names)
      type: ["local", "spec", "chip"],
      // Prefer spec for detailed documentation
      details: ["local", "spec", "chip"],
      // Prefer spec for default values
      default: ["local", "spec", "chip"]
    }
  };
})(MergedModel || (MergedModel = {}));
export {
  MergedModel
};
//# sourceMappingURL=MergedModel.js.map
