/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { InternalError, isDeepEqual } from "#general";
import { Conformance } from "../../aspects/index.js";
import { FeatureBitmap } from "./FeatureBitmap.js";
function IllegalFeatureCombinations(cluster) {
  const illegal = [];
  const choices = {};
  function add(flags) {
    if (!illegal.some((e) => isDeepEqual(e, flags))) {
      illegal.push(flags);
    }
  }
  for (const f of cluster.features) {
    addFeatureNode(f, f.conformance.ast, add, choices);
  }
  let requiresFeatures = false;
  for (const choice of Object.values(choices)) {
    if (choice.exclusive) {
      for (const f1 of choice.features) {
        for (const f2 of choice.features) {
          if (f1 !== f2) {
            add({ [f1]: true, [f2]: true });
          }
        }
      }
    }
    const flags = FeatureBitmap();
    for (const f of choice.features) {
      flags[f] = false;
    }
    add(flags);
    requiresFeatures = true;
  }
  return { illegal, requiresFeatures };
}
function addFeatureNode(feature, node, add, choices) {
  switch (node.type) {
    case Conformance.Special.Desc:
    case Conformance.Special.Empty:
    case Conformance.Flag.Optional:
    case Conformance.Flag.Provisional:
      break;
    case Conformance.Flag.Mandatory:
      add({ [feature.name]: false });
      break;
    case Conformance.Flag.Deprecated:
    case Conformance.Flag.Disallowed:
      add({ [feature.name]: true });
      break;
    case Conformance.Special.Otherwise:
      const rules = node.param;
      const entryExclusions = Array();
      let optionalIfIndex;
      for (let i = 0; i < rules.length; i++) {
        if (rules[i].type === Conformance.Special.OptionalIf) {
          if (optionalIfIndex !== void 0) {
            unsupported();
          }
          optionalIfIndex = i;
        }
        const thisEntryExclusions = [];
        entryExclusions.push(thisEntryExclusions);
        addFeatureNode(feature, rules[i], (flags) => thisEntryExclusions.push(flags), choices);
      }
      if (optionalIfIndex !== void 0) {
        const negations = {};
        for (let i = 0; i < rules.length; i++) {
          if (i === optionalIfIndex) {
            continue;
          }
          for (const entry of entryExclusions[i]) {
            for (const name in entry) {
              if (negations[name] !== void 0) {
                unsupported();
              }
              negations[name] = !entry[name];
            }
          }
        }
        entryExclusions[optionalIfIndex] = entryExclusions[optionalIfIndex].map((exclusions) => ({
          ...negations,
          ...exclusions
        }));
      }
      for (const ee of entryExclusions) {
        ee.forEach(add);
      }
      break;
    case Conformance.Special.Choice:
      if (node.param.num > 1) {
        unsupported();
      }
      const choice = choices[node.param.name];
      if (choice) {
        choice.features.push(feature.name);
      } else {
        choices[node.param.name] = choices[node.param.name] = {
          exclusive: !node.param.orMore,
          features: [feature.name]
        };
      }
      break;
    case Conformance.Special.Name:
      add({ [node.param]: true, [feature.name]: false });
      break;
    case Conformance.Special.OptionalIf:
      switch (node.param.type) {
        case Conformance.AND:
        case Conformance.Special.Name:
          addDependencyRequirement(feature.name, node.param);
          break;
        case Conformance.OR: {
          const flags = FeatureBitmap({ [feature.name]: true });
          addExclusivityRequirement(flags, node.param);
          add(flags);
          break;
        }
        case Conformance.Operator.NOT: {
          add({ [feature.name]: true, [extractName(node.param.param)]: true });
          break;
        }
        default:
          unsupported();
      }
      break;
    case Conformance.Operator.AND: {
      const lhsFeatures = extractDisjunctFeatures(node.param.lhs);
      const rhsFeature = extractFeatureFlag(node.param.rhs);
      for (const lhsFeature in lhsFeatures) {
        add({
          feature: false,
          [lhsFeature]: lhsFeatures[lhsFeature],
          ...rhsFeature
        });
      }
      break;
    }
    case Conformance.Operator.OR: {
      const features = extractDisjunctFeatures(node);
      add(Object.fromEntries(Object.entries(features).map((k, v) => [k, !v])));
      break;
    }
    default:
      unsupported();
  }
  function unsupported() {
    throw new InternalError(`New rule required to support ${feature.path} conformance "${feature.conformance}"`);
  }
  function extractName(node2) {
    if (node2.type === Conformance.Special.Name) {
      return node2.param;
    }
    unsupported();
  }
  function extractFeatureFlag(node2) {
    switch (node2.type) {
      case Conformance.Special.Name:
        return { [node2.param]: true };
      case Conformance.Operator.NOT:
        return { [extractName(node2.param)]: false };
      default:
        unsupported();
    }
  }
  function addExclusivityRequirement(flags, node2) {
    switch (node2.type) {
      case Conformance.OR:
        addExclusivityRequirement(flags, node2.param.lhs);
        addExclusivityRequirement(flags, node2.param.rhs);
        break;
      default:
        Object.assign(flags, extractFeatureFlag(node2));
        break;
    }
  }
  function addDependencyRequirement(feature2, node2) {
    switch (node2.type) {
      case Conformance.Special.Name:
        add({ [feature2]: true, [node2.param]: false });
        break;
      case Conformance.AND:
        addDependencyRequirement(feature2, node2.param.lhs);
        addDependencyRequirement(feature2, node2.param.rhs);
        break;
      case Conformance.OR:
        if (node2.param.lhs.type === Conformance.Special.Name && node2.param.rhs.type === Conformance.Special.Name) {
          add({ [feature2]: true, [node2.param.lhs.param]: false, [node2.param.rhs.param]: false });
        }
        break;
      case Conformance.Operator.NOT:
        if (node2.param.type === Conformance.Special.Name) {
          add({ [feature2]: true, [node2.param.param]: true });
        }
        break;
      default:
        unsupported();
    }
  }
  function extractDisjunctFeatures(node2) {
    const result = {};
    function extract(node3, invert = false) {
      switch (node3.type) {
        case Conformance.Special.Name:
          result[node3.param] = !invert;
          break;
        case Conformance.Operator.OR:
          extract(node3.param.lhs, invert);
          extract(node3.param.rhs, invert);
          break;
        case Conformance.Operator.NOT:
          extract(node3.param, !invert);
          break;
        default:
          unsupported();
      }
    }
    extract(node2);
    return result;
  }
}
export {
  IllegalFeatureCombinations
};
//# sourceMappingURL=IllegalFeatureCombinations.js.map
