/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { UnexpectedDataError } from "../MatterError.js";
import { Bytes } from "../util/Bytes.js";
import { DataReader } from "../util/DataReader.js";
import { toHex } from "../util/Number.js";
import { isObject } from "../util/Type.js";
class DerError extends UnexpectedDataError {
}
var DerType = /* @__PURE__ */ ((DerType2) => {
  DerType2[DerType2["Boolean"] = 1] = "Boolean";
  DerType2[DerType2["Integer"] = 2] = "Integer";
  DerType2[DerType2["BitString"] = 3] = "BitString";
  DerType2[DerType2["OctetString"] = 4] = "OctetString";
  DerType2[DerType2["Null"] = 5] = "Null";
  DerType2[DerType2["ObjectIdentifier"] = 6] = "ObjectIdentifier";
  DerType2[DerType2["UTF8String"] = 12] = "UTF8String";
  DerType2[DerType2["Sequence"] = 16] = "Sequence";
  DerType2[DerType2["Set"] = 17] = "Set";
  DerType2[DerType2["PrintableString"] = 19] = "PrintableString";
  DerType2[DerType2["T16String"] = 20] = "T16String";
  DerType2[DerType2["IA5String"] = 22] = "IA5String";
  DerType2[DerType2["UtcDate"] = 23] = "UtcDate";
  DerType2[DerType2["GeneralizedTime"] = 24] = "GeneralizedTime";
  return DerType2;
})(DerType || {});
const CONSTRUCTED = 32;
const DerTag = {
  Sequence: 16 /* Sequence */ | CONSTRUCTED,
  Set: 17 /* Set */ | CONSTRUCTED
};
var DerClass = /* @__PURE__ */ ((DerClass2) => {
  DerClass2[DerClass2["Universal"] = 0] = "Universal";
  DerClass2[DerClass2["Application"] = 64] = "Application";
  DerClass2[DerClass2["ContextSpecific"] = 128] = "ContextSpecific";
  DerClass2[DerClass2["Private"] = 192] = "Private";
  return DerClass2;
})(DerClass || {});
const ObjectId = (objectId) => ({
  _tag: 6 /* ObjectIdentifier */,
  _bytes: typeof objectId === "bigint" ? Bytes.fromBigInt(objectId) : Bytes.fromHex(objectId)
});
const DerObject = (objectId, content = {}) => ({
  _objectId: ObjectId(objectId),
  ...content
});
const DerBitString = (data, padding = 0) => ({
  _tag: 3 /* BitString */,
  _bytes: data,
  _padding: padding
});
const ContextTagged = (tagId, value) => ({
  _tag: tagId | 128 /* ContextSpecific */ | CONSTRUCTED,
  _bytes: value === void 0 ? new Uint8Array(0) : DerCodec.encode(value)
});
const ContextTaggedBytes = (tagId, value) => ({
  _tag: tagId | 128 /* ContextSpecific */,
  _bytes: value
});
const DatatypeOverride = (type, value) => ({
  _type: type,
  _raw: value
});
const RawBytes = (bytes) => ({
  _bytes: bytes
});
const DerRawUint = (number) => {
  const numberData = Bytes.of(number);
  if (numberData[0] & 128) {
    number = Bytes.concat(new Uint8Array([0]), numberData);
  } else {
    let firstByte = 0;
    while (firstByte < numberData.length - 1) {
      if (numberData[firstByte] || numberData[firstByte + 1] & 128) {
        break;
      }
      firstByte++;
    }
    if (firstByte) {
      number = numberData.slice(firstByte);
    }
  }
  return {
    _tag: 2 /* Integer */,
    _bytes: number
  };
};
class DerCodec {
  static encode(value) {
    if (Array.isArray(value)) {
      return this.#encodeArray(value);
    } else if (Bytes.isBytes(value)) {
      return this.#encodeOctetString(value);
    } else if (value instanceof Date) {
      return this.#encodeDate(value);
    } else if (typeof value === "string") {
      return this.#encodeString(value);
    } else if (typeof value === "number" || typeof value === "bigint") {
      return this.#encodeInteger(value);
    } else if (typeof value === "boolean") {
      return this.#encodeBoolean(value);
    } else if (value === void 0) {
      return new Uint8Array(0);
    } else if (isObject(value)) {
      if (value._tag !== void 0) {
        const { _tag: tagId, _padding: bitsPadding, _bytes: bytes } = value;
        if (typeof tagId !== "number") {
          throw new DerError("Tag ID is non-numeric");
        }
        if (bitsPadding !== void 0 && typeof bitsPadding !== "number") {
          throw new DerError("Bits padding is not a numeric byte value");
        }
        if (bytes === void 0 || !Bytes.isBytes(bytes)) {
          throw new DerError("DER bytes is not a byte array");
        }
        return this.#encodeAsn1(
          tagId,
          bitsPadding === void 0 ? bytes : Bytes.concat(Uint8Array.of(bitsPadding), Bytes.of(bytes))
        );
      } else if (value._type !== void 0 && value._raw !== void 0) {
        if (value._type === 2 /* Integer */ && Bytes.isBytes(value._raw)) {
          return this.#encodeInteger(value._raw);
        } else if (value._type === 3 /* BitString */ && typeof value._raw === "number") {
          return this.#encodeBitString(value._raw);
        } else if (value._type === 19 /* PrintableString */ && typeof value._raw === "string") {
          return this.#encodePrintableString(value._raw);
        } else if (value._type === 22 /* IA5String */ && typeof value._raw === "string") {
          return this.#encodeIA5String(value._raw);
        } else {
          throw new DerError(`Unsupported override type ${value._type}`);
        }
      } else if ("_bytes" in value && value._bytes !== void 0 && Bytes.isBytes(value._bytes) && Object.keys(value).length === 1) {
        return Bytes.of(value._bytes);
      } else if (value._type === void 0 && value._bytes === void 0) {
        return this.#encodeSequence(value);
      } else {
        throw new DerError(`Unsupported object type ${typeof value}`);
      }
    } else {
      throw new DerError(`Unsupported type ${typeof value}`);
    }
  }
  static decode(data) {
    return this.#decodeRec(new DataReader(data));
  }
  /**
   * Extract a large integer value to a byte array with a specific number of bytes.
   */
  static decodeBigUint(value, byteLength) {
    if (value === void 0) {
      throw new DerError("Missing number in DER object");
    }
    if (value._tag !== 2 /* Integer */) {
      throw new DerError(`Expected integer but DER tag is ${DerType[value._tag]}`);
    }
    if (!Bytes.isBytes(value._bytes)) {
      throw new DerError("Incorrect DER object type");
    }
    const bytes = Bytes.of(value._bytes);
    if (bytes.length === byteLength) {
      return bytes;
    }
    if (bytes.length === byteLength + 1 && !bytes[0]) {
      return bytes.slice(1);
    }
    if (bytes.length < byteLength) {
      return Bytes.concat(new Uint8Array(byteLength - bytes.length), bytes);
    }
    throw new DerError("Encoded integer contains too many bytes");
  }
  static #encodeDate(date) {
    if (date.getFullYear() > 2049) {
      return this.#encodeAsn1(
        24 /* GeneralizedTime */,
        Bytes.fromString(
          date.toISOString().replace(/[-:.T]/g, "").slice(0, 14) + "Z"
        )
      );
    } else {
      return this.#encodeAsn1(
        23 /* UtcDate */,
        Bytes.fromString(
          date.toISOString().replace(/[-:.T]/g, "").slice(2, 14) + "Z"
        )
      );
    }
  }
  static #encodeBoolean(bool) {
    return this.#encodeAsn1(1 /* Boolean */, Uint8Array.of(bool ? 255 : 0));
  }
  static #encodeArray(array) {
    return this.#encodeAsn1(DerTag.Set, Bytes.concat(...array.map((element) => this.encode(element))));
  }
  static #encodeOctetString(value) {
    return this.#encodeAsn1(4 /* OctetString */, value);
  }
  static #encodeSequence(object) {
    const attributes = new Array();
    for (const key in object) {
      attributes.push(this.encode(object[key]));
    }
    return this.#encodeAsn1(DerTag.Sequence, Bytes.concat(...attributes));
  }
  static #encodeString(value) {
    return this.#encodeAsn1(12 /* UTF8String */, Bytes.fromString(value));
  }
  static #encodePrintableString(value) {
    if (!/^[a-z0-9 '()+,\-./:=?]*$/i.test(value)) {
      throw new DerError(`String ${value} is not a printable string.`);
    }
    return this.#encodeAsn1(19 /* PrintableString */, Bytes.fromString(value));
  }
  static #encodeIA5String(value) {
    if (!/^[\x00-\x7F]*$/.test(value)) {
      throw new DerError(`String ${value} is not an IA5 string.`);
    }
    return this.#encodeAsn1(22 /* IA5String */, Bytes.fromString(value));
  }
  static #encodeInteger(value) {
    const valueBytes = Bytes.isBytes(value) ? value : Bytes.fromHex(toHex(value));
    const byteArray = Bytes.concat(new Uint8Array(1), valueBytes);
    const dataView = Bytes.dataViewOf(byteArray);
    let start = 0;
    while (true) {
      if (dataView.getUint8(start) !== 0) break;
      if (dataView.getUint8(start + 1) >= 128) break;
      start++;
      if (start === byteArray.byteLength - 1) break;
    }
    return this.#encodeAsn1(2 /* Integer */, start === 0 ? byteArray : Bytes.of(byteArray).slice(start));
  }
  static #encodeBitString(value) {
    const reversedBits = value.toString(2).padStart(8, "0");
    const unusedBits = reversedBits.indexOf("1");
    const bitByteArray = Uint8Array.of(parseInt(reversedBits.split("").reverse().join(""), 2));
    return this.encode(DerBitString(bitByteArray, unusedBits === -1 ? 8 : unusedBits));
  }
  static #encodeLengthBytes(value) {
    const byteArray = new Uint8Array(5);
    const dataView = Bytes.dataViewOf(byteArray);
    dataView.setUint32(1, value);
    let start = 0;
    while (true) {
      if (dataView.getUint8(start) !== 0) break;
      start++;
      if (start === 4) break;
    }
    const lengthLength = byteArray.length - start;
    if (lengthLength > 1 || dataView.getUint8(start) >= 128) {
      start--;
      dataView.setUint8(start, 128 + lengthLength);
    }
    return byteArray.slice(start);
  }
  static #encodeAsn1(tag, data) {
    return Bytes.concat(Uint8Array.of(tag), this.#encodeLengthBytes(data.byteLength), data);
  }
  static #decodeRec(reader) {
    const { tag, bytes } = this.#decodeAsn1(reader);
    if (tag === 3 /* BitString */) {
      const data = Bytes.of(bytes);
      return { _tag: tag, _bytes: data.slice(1), _padding: data[0] };
    }
    if ((tag & CONSTRUCTED) === 0) return { _tag: tag, _bytes: bytes };
    const elementsReader = new DataReader(bytes);
    const elements = [];
    while (elementsReader.remainingBytesCount > 0) {
      elements.push(this.#decodeRec(elementsReader));
    }
    return { _tag: tag, _bytes: bytes, _elements: elements };
  }
  static #decodeAsn1(reader) {
    const tag = reader.readUInt8();
    let length = reader.readUInt8();
    if ((length & 128) !== 0) {
      let lengthLength = length & 127;
      length = 0;
      while (lengthLength > 0) {
        length = (length << 8) + reader.readUInt8();
        lengthLength--;
      }
    }
    const bytes = reader.readByteArray(length);
    return { tag, bytes };
  }
}
export {
  ContextTagged,
  ContextTaggedBytes,
  DatatypeOverride,
  DerBitString,
  DerCodec,
  DerError,
  DerObject,
  DerRawUint,
  DerTag,
  DerType,
  ObjectId,
  RawBytes
};
//# sourceMappingURL=DerCodec.js.map
