/**
 * @license
 * Copyright 2022-2025 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { camelize, GeneratedClass, ImplementationError, isObject } from "#general";
import { Access, ElementTag, FieldValue, Metatype } from "#model";
import { PhantomReferenceError, SchemaImplementationError, Val } from "#protocol";
import { Instrumentation } from "../Instrumentation.js";
import { Internal } from "../Internal.js";
import { ManagedReference } from "../ManagedReference.js";
import { NameResolver } from "../NameResolver.js";
import { PrimitiveManager } from "./PrimitiveManager.js";
const AUTHORIZE_READ = Symbol("authorize-read");
function StructManager(owner, schema) {
  const prototypeDescriptors = {
    // TODO - interferes with Chai deep equal.  Best fix would probably be a custom deep equal assertion but
    // leaving out for now
    // [Symbol.toStringTag]: {
    //     value: name,
    // },
    // TODO - makes Mocha diffs pretty useless.  Best fix is probably customized diff but leaving out for now
    // toString: {
    //     value() {
    //         return serialize(this);
    //     }
    // },
    // AUTHORIZE_READ is effectively a protected method, see StructManager.assertDirectReadAuthorized below
    [AUTHORIZE_READ]: {
      value(attributeId) {
        const access = propertyAccessControls[attributeId];
        if (access === void 0) {
          throw new ImplementationError(`Direct read of unknown property ${attributeId}`);
        }
        access.authorizeRead(this[Internal.session], this[Internal.reference].location);
      }
    }
  };
  const instanceDescriptors = {};
  const propertyAccessControls = {};
  let hasFabricIndex = false;
  const isCluster = schema.tag === ElementTag.Cluster;
  for (const member of owner.membersOf(schema)) {
    const name2 = camelize(member.name);
    const { access, descriptor } = configureProperty(owner, member);
    instanceDescriptors[name2] = descriptor;
    if (member.id !== void 0) {
      prototypeDescriptors[member.id] = { ...descriptor, enumerable: false };
      propertyAccessControls[member.id] = access;
    }
    if (member.name === "FabricIndex") {
      hasFabricIndex = true;
    }
  }
  let name = schema.name;
  if (schema.tag === ElementTag.Cluster && !name.endsWith("$State")) {
    name = `${name}$State`;
  }
  const Wrapper = GeneratedClass({
    name,
    initialize(ref, session) {
      Object.defineProperties(this, {
        [Internal.reference]: {
          value: ref
        },
        [Internal.session]: {
          value: session
        }
      });
      if (!isObject(ref.value)) {
        throw new SchemaImplementationError(
          ref.location,
          `Cannot manage ${typeof ref.value} because it is not a struct`
        );
      }
      if (hasFabricIndex) {
        const owningFabric = ref.value.fabricIndex;
        ref.location = { ...ref.location, owningFabric };
      }
      if (!isCluster || !session.protocol) {
        Object.defineProperties(this, instanceDescriptors);
      }
    },
    instanceDescriptors: prototypeDescriptors
  });
  Instrumentation.instrumentStruct(Wrapper);
  return (reference, session) => {
    reference.owner = new Wrapper(reference, session);
    return reference.owner;
  };
}
((StructManager2) => {
  function assertDirectReadAuthorized(struct, attributeId) {
    if (!struct?.[AUTHORIZE_READ]) {
      throw new ImplementationError("Cannot authorize read of unmanaged value");
    }
    return struct[AUTHORIZE_READ](attributeId);
  }
  StructManager2.assertDirectReadAuthorized = assertDirectReadAuthorized;
})(StructManager || (StructManager = {}));
function configureProperty(supervisor, schema) {
  const name = camelize(schema.name);
  const id = schema.id;
  const { access, manage, validate } = supervisor.get(schema);
  const fabricScopedList = schema.effectiveAccess.fabric === Access.Fabric.Scoped && schema.effectiveMetatype === Metatype.array;
  let defaultReader;
  if (typeof FieldValue.referenced(schema.default) === "string") {
    defaultReader = NameResolver(supervisor, schema.parent, camelize(FieldValue.referenced(schema.default)));
  }
  const descriptor = {
    enumerable: true,
    set(value) {
      access.authorizeWrite(this[Internal.session], this[Internal.reference].location);
      const pk = this[Internal.reference].primaryKey;
      let key = pk === "id" ? id ?? name : name;
      let storedKey;
      if (key in this[Internal.reference].value) {
        storedKey = key;
      } else if (pk === "id") {
        if (name in this[Internal.reference].value) {
          storedKey = name;
        }
      } else if (id !== void 0) {
        if (id in this[Internal.reference].value) {
          storedKey = id;
        }
      }
      const oldValue = storedKey === void 0 ? void 0 : this[Internal.reference].value[storedKey];
      const self = this;
      this[Internal.reference].change(() => {
        const struct = this[Internal.reference].value;
        let target;
        if (Val.properties in struct) {
          const properties = struct[Val.properties](
            this[Internal.reference].rootOwner,
            this[Internal.session]
          );
          if (name in properties) {
            key = storedKey = name;
            target = properties;
          } else {
            target = struct;
          }
        } else {
          target = struct;
        }
        if (value && value[Internal.reference]) {
          value = value[Internal.reference].value;
        }
        if (fabricScopedList && Array.isArray(value) && Array.isArray(oldValue)) {
          const proxy = self[name];
          for (let i = 0; i < value.length; i++) {
            proxy[i] = value[i];
          }
          proxy.length = value.length;
        } else {
          target[key] = value;
          if (storedKey !== void 0 && storedKey !== key) {
            delete target[storedKey];
          }
        }
        if (!this[Internal.session].acceptInvalid && validate) {
          try {
            validate(value, this[Internal.session], {
              path: this[Internal.reference].location.path,
              siblings: struct
            });
          } catch (e) {
            target[key] = oldValue;
            throw e;
          }
        }
      });
    }
  };
  if (manage === PrimitiveManager) {
    descriptor.get = function() {
      if (access.mayRead(this[Internal.session], this[Internal.reference].location)) {
        const struct = this[Internal.reference].value;
        if (struct === void 0) {
          throw new PhantomReferenceError(this[Internal.reference].location);
        }
        if (struct[Val.properties]) {
          const properties = struct[Val.properties](
            this[Internal.reference].rootOwner,
            this[Internal.session]
          );
          if (name in properties) {
            return properties[name];
          }
        }
        const key = this[Internal.reference].primaryKey === "id" ? id ?? name : name;
        if (key in struct) {
          return struct[key];
        }
        const key2 = this[Internal.reference].primaryKey === "id" ? name : id;
        if (key2 !== void 0 && key2 in struct) {
          return struct[key2];
        }
        return void 0;
      }
    };
  } else {
    let cloneContainer;
    switch (schema.effectiveMetatype) {
      case Metatype.array:
        cloneContainer = (container) => [...container];
        break;
      case Metatype.bitmap:
        cloneContainer = (container) => {
          if (typeof container === "number" || typeof container === "bigint") {
            return {};
          }
          return { ...container };
        };
        break;
      default:
        cloneContainer = (container) => ({ ...container });
    }
    descriptor.get = function() {
      let value;
      const pk = this[Internal.reference].primaryKey;
      const struct = this[Internal.reference].value;
      const key = pk === "id" ? id ?? name : name;
      if (struct[Val.properties]) {
        const properties = struct[Val.properties](
          this[Internal.reference].rootOwner,
          this[Internal.session]
        );
        if (name in properties) {
          value = properties[name];
        } else {
          value = struct[name];
        }
      } else {
        if (key in struct) {
          value = struct[key];
        } else {
          const key2 = pk === "id" ? id : name;
          if (key2 !== void 0 && key2 in struct) {
            value = struct[key2];
          }
        }
      }
      if (!access.mayRead(this[Internal.session], this[Internal.reference].location)) {
        return void 0;
      }
      if (value === void 0) {
        return defaultReader?.(this);
      }
      if (value === null) {
        return value;
      }
      const managed = this[Internal.reference].subrefs?.[name];
      if (managed) {
        return managed.owner;
      }
      const assertWriteOk = (value2) => {
        access.authorizeWrite(this[Internal.session], this[Internal.reference].location);
        if (validate) {
          validate(value2, this[Internal.session], {
            path: this[Internal.reference].location.path,
            siblings: this[Internal.reference].value
          });
        }
      };
      const ref = ManagedReference(
        this[Internal.reference],
        pk,
        name,
        id,
        assertWriteOk,
        cloneContainer,
        this[Internal.session]
      );
      ref.owner = manage(ref, this[Internal.session]);
      return ref.owner;
    };
  }
  return { descriptor, access };
}
export {
  StructManager
};
//# sourceMappingURL=StructManager.js.map
