/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { isObject, serialize } from "@matter/general";
import { Access, DataModelPath, ValueModel } from "@matter/model";
import {
  AccessControl,
  ExpiredReferenceError,
  hasLocalActor,
  hasRemoteActor,
  ReadError,
  SchemaImplementationError,
  WriteError
} from "@matter/protocol";
import { StatusCode } from "@matter/types";
import { Instrumentation } from "../Instrumentation.js";
import { Internal } from "../Internal.js";
import { ManagedReference } from "../ManagedReference.js";
import { PrimitiveManager } from "./PrimitiveManager.js";
function ListManager(owner, schema) {
  const config = createConfig(owner, schema);
  return (list, session) => {
    if (!Array.isArray(list.value)) {
      throw new SchemaImplementationError(
        list.location,
        `Cannot manage ${typeof list.value} because it is not an array`
      );
    }
    return createProxy(config, list, session);
  };
}
function createConfig(owner, schema) {
  const entry = schema instanceof ValueModel ? schema.listEntry : void 0;
  if (entry === void 0) {
    throw new SchemaImplementationError(DataModelPath(schema.path), "List schema has no entry definition");
  }
  const entryManager = owner.get(entry);
  const access = AccessControl(schema);
  return {
    schema,
    fabricScoped: schema.effectiveAccess.fabric === Access.Fabric.Scoped,
    fabricSensitive: schema.effectiveAccess.fabric === Access.Fabric.Sensitive,
    manageEntries: entryManager.manage !== PrimitiveManager,
    manageEntry: entryManager.manage,
    validateEntry: entryManager.validate,
    authorizeRead: access.authorizeRead,
    authorizeWrite: access.authorizeWrite
  };
}
function createProxy(config, reference, session) {
  const { manageEntries, manageEntry, validateEntry, authorizeRead, authorizeWrite } = config;
  const readVal = () => reference.value ?? [];
  const writeVal = () => {
    if (reference.expired) {
      throw new ExpiredReferenceError(reference.location);
    }
    return reference.value;
  };
  let getListLength = () => readVal().length;
  let setListLength = (length) => {
    if (length > 65535) {
      throw new WriteError(reference.location, `Index ${length} is greater than allowed maximum of 65535`);
    }
    reference.change(() => writeVal().length = length);
  };
  let hasEntry = (index) => readVal()[index] !== void 0;
  let readEntry;
  let getIteratorFn = () => readVal()[Symbol.iterator];
  let ownKeys = () => Reflect.ownKeys(readVal());
  let getOwnPropertyDescriptor = (_target, key) => Reflect.getOwnPropertyDescriptor(readVal(), key);
  const sublocation = {
    ...reference.location,
    path: reference.location.path.at(-1)
  };
  if (manageEntries) {
    readEntry = (index) => {
      authorizeRead(session, reference.location);
      if (index < 0 || index >= readVal().length) {
        throw new ReadError(reference.location, `Index ${index} is out of bounds`);
      }
      if (index > 65535) {
        throw new ReadError(reference.location, `Index ${index} is greater than allowed maximum of 65535`);
      }
      const value = readVal()[index];
      if (value === void 0 || value === null) {
        return value;
      }
      let subref = reference.subrefs?.[index];
      if (subref === void 0) {
        subref = ManagedReference(
          reference,
          "name",
          index,
          // For managed reference's purposes this is our "name"
          void 0,
          () => true,
          (val) => Array.isArray(val) ? [...val] : isObject(val) ? { ...val } : val,
          session
        );
        manageEntry(subref, session);
      }
      return subref.owner;
    };
  } else {
    readEntry = (index, location) => {
      authorizeRead(session, location);
      if (index < 0 || index > readVal().length) {
        throw new ReadError(location, `Index ${index} is out of bounds`);
      }
      return readVal()[index];
    };
  }
  let writeEntry = (index, value, location) => {
    authorizeWrite(session, location);
    if (index < 0 || index > readVal().length + 1) {
      throw new WriteError(location, `Index ${index} is out of bounds`);
    }
    if (index > 65535) {
      throw new ReadError(location, `Index ${index} is greater than allowed maximum of 65535`);
    }
    if (value && value[Internal.reference]) {
      value = value[Internal.reference].value;
    }
    reference.change(() => writeVal()[index] = value);
  };
  if (manageEntries && config.fabricScoped) {
    let mapScopedToActual2 = function(index, reading) {
      if (index < 0) {
        throw new (reading ? ReadError : WriteError)(reference.location, `Negative index ${index} unsupported`);
      }
      let nextPos = 0;
      for (let i = 0; i < readVal().length; i++) {
        const entry = readVal()[i];
        if (!isObject(entry)) {
          continue;
        }
        if (hasLocalActor(session) || !entry.fabricIndex || entry.fabricIndex === session.fabric) {
          if (nextPos === index) {
            return i;
          }
          nextPos++;
        }
      }
      if (reading) {
        throw new ReadError(reference.location, `Index ${index} extends beyond available entries`);
      }
      if (nextPos === index) {
        return readVal().length;
      }
      throw new WriteError(reference.location, `Index ${index} would leave gaps in fabric-filtered list`);
    };
    var mapScopedToActual = mapScopedToActual2;
    if (hasRemoteActor(session) && (session.fabricFiltered || config.fabricSensitive)) {
      const nextReadEntry = readEntry;
      hasEntry = (index) => {
        try {
          return nextReadEntry(mapScopedToActual2(index, true), reference.location) !== void 0;
        } catch (e) {
          return false;
        }
      };
      readEntry = (index, location) => {
        return nextReadEntry(mapScopedToActual2(index, true), location);
      };
      const nextWriteEntry = writeEntry;
      writeEntry = (index, value, location) => {
        if (value === void 0) {
          const valueIndex = mapScopedToActual2(index, false);
          writeVal().splice(valueIndex, 1);
        } else {
          if (!isObject(value)) {
            throw new WriteError(location, `Fabric scoped list value is not an object`, StatusCode.Failure);
          }
          value.fabricIndex ??= session.fabric;
          nextWriteEntry(mapScopedToActual2(index, false), value, location);
        }
      };
      getListLength = () => {
        let length = 0;
        for (let i = 0; i < readVal().length; i++) {
          const entry = readVal()[i];
          if (isObject(entry) && (!entry.fabricIndex || entry.fabricIndex === session.fabric)) {
            length++;
          }
        }
        return length;
      };
      setListLength = (length) => {
        const formerLength = getListLength();
        reference.change(() => {
          for (let i = formerLength - 1; i >= length; i--) {
            const entry = writeVal()[mapScopedToActual2(i, true)];
            if (isObject(entry) && (!entry.fabricIndex || entry.fabricIndex === session.fabric)) {
              writeVal().splice(mapScopedToActual2(i, false), 1);
            } else if (entry !== void 0) {
              throw new WriteError(
                reference.location,
                `Fabric scoped list value is not an object`,
                StatusCode.Failure
              );
            }
          }
        });
      };
      getIteratorFn = () => () => {
        const iterator = readVal()[Symbol.iterator]();
        return {
          ...iterator,
          next() {
            while (true) {
              const next = iterator.next();
              if (!next.done && isObject(next.value) && next.value.fabricIndex !== session.fabric) {
                continue;
              }
              return next;
            }
          },
          [Symbol.iterator]() {
            return this;
          }
        };
      };
      ownKeys = () => {
        const length = getListLength();
        const keys = Reflect.ownKeys(readVal()).filter((k) => {
          if (typeof k !== "string") {
            return true;
          }
          if (!k.match(/^\d+$/)) {
            return true;
          }
          if (Number.parseInt(k) < length) {
            return true;
          }
          return false;
        });
        return keys;
      };
      getOwnPropertyDescriptor = (_target, key) => {
        if (typeof key === "string" && key.match(/^\d+$/)) {
          key = Number.parseInt(key);
        }
        if (typeof key !== "number") {
          return Reflect.getOwnPropertyDescriptor(readVal(), key);
        }
        return Reflect.getOwnPropertyDescriptor(readVal(), mapScopedToActual2(key, true));
      };
    }
  }
  const target = [];
  const handlers = {
    get(_target, property, receiver) {
      if (typeof property === "string" && property.match(/^\d+/)) {
        sublocation.path.id = property;
        return readEntry(Number.parseInt(property), sublocation);
      }
      switch (property) {
        case "length":
          return getListLength();
        case Symbol.iterator:
          return getIteratorFn();
        case Internal.reference:
          return reference;
        case "toString":
          return function() {
            return serialize(this);
          };
        case Symbol.toStringTag:
          return void 0;
      }
      return Reflect.get(readVal(), property, receiver);
    },
    // On write we enter a transaction
    set(_target, property, newValue, receiver) {
      if (typeof property === "string" && property.match(/^\d+/)) {
        sublocation.path.id = property;
        validateEntry?.(newValue, session, sublocation);
        writeEntry(Number.parseInt(property), newValue, sublocation);
        return true;
      } else if (property === "length") {
        setListLength(newValue);
        return true;
      }
      return Reflect.set(writeVal(), property, newValue, receiver);
    },
    has(_target, property) {
      if (typeof property === "string" && property.match(/^\d+/)) {
        return hasEntry(Number.parseInt(property));
      }
      return Reflect.has(readVal(), property);
    },
    deleteProperty: (_target, property) => {
      if (typeof property === "string" && property.match(/^\d+/)) {
        sublocation.path.id = property;
        writeEntry(Number.parseInt(property), void 0, sublocation);
        return true;
      }
      return Reflect.deleteProperty(writeVal(), property);
    },
    ownKeys,
    getOwnPropertyDescriptor
  };
  const factory = Instrumentation.instrumentList((handlers2, target2) => new Proxy(target2, handlers2));
  reference.owner = factory(handlers, target);
  return reference.owner;
}
export {
  ListManager
};
//# sourceMappingURL=ListManager.js.map
