/**
 * @license
 * Copyright 2022-2025 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { Logger, UnexpectedDataError } from "#general";
import { ValidationDatatypeMismatchError } from "../common/ValidationError.js";
import { TlvCodec, TlvType } from "./TlvCodec.js";
import { TlvArrayReader, TlvSchema } from "./TlvSchema.js";
class AnySchema extends TlvSchema {
  getEncodedByteLength(tlvStream) {
    let byteLength = 0;
    tlvStream.forEach(({ tag, typeLength, value }) => {
      switch (typeLength.type) {
        case TlvType.Null:
        case TlvType.Boolean:
        case TlvType.Array:
        case TlvType.Structure:
        case TlvType.List:
        case TlvType.EndOfContainer:
          byteLength += TlvCodec.getTagByteLength(tag);
          break;
        case TlvType.UnsignedInt:
        case TlvType.SignedInt:
        case TlvType.Float:
        case TlvType.Utf8String:
        case TlvType.ByteString:
          byteLength += TlvCodec.getTagByteLength(tag);
          byteLength += TlvCodec.getPrimitiveByteLength(typeLength, value);
          break;
      }
    });
    return byteLength;
  }
  encodeTlvInternal(writer, tlvStream, tagAssigned) {
    tlvStream.forEach(({ tag, typeLength, value }) => {
      if (tagAssigned !== void 0) {
        tag = tagAssigned;
        tagAssigned = void 0;
      }
      switch (typeLength.type) {
        case TlvType.Null:
        case TlvType.Boolean:
        case TlvType.Array:
        case TlvType.Structure:
        case TlvType.List:
        case TlvType.EndOfContainer:
          writer.writeTag(typeLength, tagAssigned ?? tag);
          break;
        case TlvType.UnsignedInt:
        case TlvType.SignedInt:
        case TlvType.Float:
        case TlvType.Utf8String:
        case TlvType.ByteString:
          writer.writeTag(typeLength, tagAssigned ?? tag);
          writer.writePrimitive(typeLength, value);
          break;
      }
    });
  }
  decodeTlvInternalValue(reader, typeLength) {
    return this.decodeTlvValueRec(reader, typeLength, new Array());
  }
  decodeTlvValueRec(reader, typeLength, tlvStream, tag) {
    switch (typeLength.type) {
      case TlvType.Null:
        tlvStream.push({ tag, typeLength, value: null });
        break;
      case TlvType.Boolean:
      case TlvType.UnsignedInt:
      case TlvType.SignedInt:
      case TlvType.Float:
      case TlvType.Utf8String:
      case TlvType.ByteString:
        tlvStream.push({ tag, typeLength, value: reader.readPrimitive(typeLength) });
        break;
      case TlvType.Array:
      case TlvType.Structure:
      case TlvType.List:
        tlvStream.push({ tag, typeLength });
        while (true) {
          const { tag: elementTag, typeLength: typeLengthElement } = reader.readTagType();
          this.decodeTlvValueRec(reader, typeLengthElement, tlvStream, elementTag);
          if (typeLengthElement.type === TlvType.EndOfContainer) break;
        }
        break;
      case TlvType.EndOfContainer:
        tlvStream.push({ tag, typeLength });
        break;
    }
    return tlvStream;
  }
  validate(tlvStream) {
    if (!Array.isArray(tlvStream))
      throw new ValidationDatatypeMismatchError(`Expected TlvStream, got ${typeof tlvStream}.`);
    tlvStream.forEach(({ typeLength }) => {
      if (!typeLength || typeof typeLength !== "object")
        throw new ValidationDatatypeMismatchError(
          `Expected typeLength properties in TlvStream, got ${typeof typeLength}.`
        );
      if (typeof typeLength.type !== "number")
        throw new ValidationDatatypeMismatchError(
          `Expected typeLength.type as number in TlvStream, got ${typeof typeLength.type}.`
        );
    });
  }
  decodeAnyTlvStream(encoded) {
    if (encoded.length === 0) {
      return void 0;
    }
    const reader = new TlvArrayReader(encoded);
    const result = this.decodeGenericElement(reader);
    const nextElement = reader.readTagType();
    if (nextElement !== void 0) {
      throw new UnexpectedDataError(`Unexpected data left after parsing all data: ${Logger.toJSON(nextElement)}`);
    }
    return result;
  }
  decodeGenericElement(reader, preReadElement, allowTag = false) {
    const element = preReadElement ?? reader.readTagType();
    const {
      tag,
      typeLength: { type }
    } = element;
    switch (type) {
      case TlvType.Null:
      case TlvType.Boolean:
      case TlvType.UnsignedInt:
      case TlvType.SignedInt:
      case TlvType.Float:
      case TlvType.Utf8String:
      case TlvType.ByteString:
        if (tag !== void 0 && !allowTag) {
          throw new UnexpectedDataError(`Tag detected for a native type: ${Logger.toJSON(element)}`);
        }
        return reader.readPrimitive(element.typeLength);
      case TlvType.Array:
      case TlvType.List:
        return this.decodeGenericArrayOrList(reader, type === TlvType.List);
      case TlvType.Structure:
        return this.decodeGenericStructure(reader);
      default:
        throw new UnexpectedDataError(`Unknown type: ${type}`);
    }
  }
  decodeGenericArrayOrList(reader, allowTag = false) {
    const result = new Array();
    while (true) {
      const element = reader.readTagType();
      const {
        tag,
        typeLength: { type }
      } = element;
      if (type === TlvType.EndOfContainer) break;
      if (tag !== void 0 && !allowTag) {
        throw new UnexpectedDataError(`Tag detected : ${Logger.toJSON(element)}`);
      }
      result.push(this.decodeGenericElement(reader, element, allowTag));
    }
    return result;
  }
  decodeGenericStructure(reader) {
    const result = {};
    while (true) {
      const element = reader.readTagType();
      const {
        tag,
        typeLength: { type }
      } = element;
      if (type === TlvType.EndOfContainer) break;
      if (tag === void 0 || tag.id === void 0) {
        throw new UnexpectedDataError(`Tag missing for a structure: ${Logger.toJSON(element)}`);
      }
      result[tag.id] = this.decodeGenericElement(reader, element, true);
    }
    return result;
  }
}
const TlvAny = new AnySchema();
export {
  AnySchema,
  TlvAny
};
//# sourceMappingURL=TlvAny.js.map
