/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { Bytes, deepCopy, ImplementationError, UnexpectedDataError, Verhoeff } from "@matter/general";
import { VendorId } from "../datatype/VendorId.js";
import { TlvAny } from "../tlv/TlvAny.js";
import { TlvType } from "../tlv/TlvCodec.js";
import { TlvUInt16, TlvUInt32, TlvUInt8 } from "../tlv/TlvNumber.js";
import { TlvObject, TlvOptionalField } from "../tlv/TlvObject.js";
import { TlvByteString, TlvString } from "../tlv/TlvString.js";
import { Base38 } from "./Base38Schema.js";
import {
  BitField,
  BitFieldEnum,
  BitFlag,
  BitmapSchema,
  ByteArrayBitmapSchema
} from "./BitmapSchema.js";
import { Schema } from "./Schema.js";
const MATTER_QR_CODE_SINGLE_PAYLOAD_MAX_LENGTH = 255;
const MATTER_QR_CODE_ALL_PAYLOAD_MAX_LENGTH = 4296;
var CommissioningFlowType = /* @__PURE__ */ ((CommissioningFlowType2) => {
  CommissioningFlowType2[CommissioningFlowType2["Standard"] = 0] = "Standard";
  CommissioningFlowType2[CommissioningFlowType2["UserIntent"] = 1] = "UserIntent";
  CommissioningFlowType2[CommissioningFlowType2["Custom"] = 2] = "Custom";
  return CommissioningFlowType2;
})(CommissioningFlowType || {});
const DiscoveryCapabilitiesBitmap = {
  /**
   * BLE
   * * Set when device supports BLE for discovery when not commissioned.
   * * Not-Set when device does not support BLE for discovery or is currently commissioned into one or more fabrics.
   */
  ble: BitFlag(1),
  /**
   * On the IP network
   * * Set when device is already on the IP network.
   */
  onIpNetwork: BitFlag(2),
  /**
   * Wi-Fi Public Action Frame
   * * Set when device supports Wi-Fi Public Action Frame for discovery when not commissioned.
   * * Not-Set when device does not support Wi-Fi Public Action Frame for discovery or is currently commissioned into
   *   one or more fabrics.
   */
  wifiPublicActionFrame: BitFlag(3)
};
const DiscoveryCapabilitiesSchema = BitmapSchema(DiscoveryCapabilitiesBitmap);
const QrCodeDataSchema = ByteArrayBitmapSchema({
  version: BitField(0, 3),
  vendorId: BitField(3, 16),
  productId: BitField(19, 16),
  flowType: BitFieldEnum(35, 2),
  discoveryCapabilities: BitField(37, 8),
  discriminator: BitField(45, 12),
  passcode: BitField(57, 27)
});
const QrCodeTlvDataDefaultFields = {
  /** Device Serial # */
  serialNumber: TlvOptionalField(0, TlvAny),
  // can be TlvString with up to 32 bytes or Unsigned Int up to 8 bytes
  pbkdfIterations: TlvOptionalField(1, TlvUInt32.bound({ min: 1e3, max: 1e5 })),
  // Or could also be UInt 16?
  pbkdfSalt: TlvOptionalField(2, TlvByteString.bound({ minLength: 16, maxLength: 32 })),
  /**
   * Number of devices that are expected to be onboarded using this payload when using the Enhanced Commissioning
   * Method
   */
  numberOfDevices: TlvOptionalField(3, TlvUInt8.bound({ min: 1 })),
  /**
   * Time, in seconds, during which the device(s) are expected to be commissionable using the Enhanced Commissioning
   * Method
   */
  commissioningTimeout: TlvOptionalField(4, TlvUInt16)
};
const PREFIX = "MT:";
class QrPairingCodeSchema extends Schema {
  encodeInternal(payloadData) {
    if (payloadData.length === 0) throw new ImplementationError("Provided Payload data is empty");
    const result = PREFIX + payloadData.map((payloadData2) => {
      const { tlvData } = payloadData2;
      const data = tlvData !== void 0 && tlvData.byteLength > 0 ? Bytes.of(Bytes.concat(QrCodeDataSchema.encode(payloadData2), tlvData)) : QrCodeDataSchema.encode(payloadData2);
      const result2 = Base38.encode(data);
      if (result2.length > MATTER_QR_CODE_SINGLE_PAYLOAD_MAX_LENGTH) {
        throw new UnexpectedDataError(
          `Encoded pairing code is too long: ${result2.length} characters (max ${MATTER_QR_CODE_SINGLE_PAYLOAD_MAX_LENGTH})`
        );
      }
      return result2;
    }).join("*");
    if (result.length > MATTER_QR_CODE_ALL_PAYLOAD_MAX_LENGTH) {
      throw new UnexpectedDataError(
        `Encoded pairing code is too long: ${result.length} characters (max ${MATTER_QR_CODE_ALL_PAYLOAD_MAX_LENGTH})`
      );
    }
    return result;
  }
  decodeInternal(encoded) {
    if (!encoded.startsWith(PREFIX)) throw new UnexpectedDataError("The pairing code should start with MT:");
    return encoded.slice(PREFIX.length).split("*").map((encodedData) => {
      const data = Bytes.of(Base38.decode(encodedData));
      return {
        ...QrCodeDataSchema.decode(data.slice(0, 11)),
        tlvData: data.length > 11 ? data.slice(11) : void 0
        // TlvData (if any) is after the fixed-length data
      };
    });
  }
  /**
   * Decodes the TLV data from the QR code payload.
   * This method especially also handles that an encoded serialNumber can be UTF-8-String or a Unsigned Integer.
   *
   * @param data Encoded TLV data
   * @param schema The schema to use for decoding the TLV data, by default a schema with the QrCodeTlvDataDefaultFields is used
   */
  decodeTlvData(data, schema = TlvObject(QrCodeTlvDataDefaultFields)) {
    const decoded = schema.decode(data);
    if (decoded.serialNumber !== void 0) {
      if (!Array.isArray(decoded.serialNumber) || decoded.serialNumber.length !== 1 || decoded.serialNumber[0].typeLength === void 0 || decoded.serialNumber[0].value === void 0) {
        throw new UnexpectedDataError("Invalid serial number TLV data");
      }
      switch (decoded.serialNumber[0].typeLength.type) {
        case TlvType.Utf8String:
        case TlvType.UnsignedInt:
          decoded.serialNumber = decoded.serialNumber[0].value;
          break;
        default:
          throw new UnexpectedDataError("Invalid serial number TLV data");
      }
    }
    return decoded;
  }
  /**
   * Encodes the TLV data for the QR code payload.
   * This method especially also handles that an encoded serialNumber can be UTF-8-String or a Unsigned Integer.
   *
   * @param data Data object to encode
   * @param schema The schema to use for encoding the TLV data, by default a schema with the QrCodeTlvDataDefaultFields is used
   */
  encodeTlvData(data, schema = TlvObject(QrCodeTlvDataDefaultFields)) {
    const dataToEncode = deepCopy(data);
    if ("serialNumber" in dataToEncode && dataToEncode.serialNumber !== void 0) {
      switch (typeof dataToEncode.serialNumber) {
        case "string":
          dataToEncode.serialNumber = TlvString.encodeTlv(dataToEncode.serialNumber);
          break;
        case "number":
          dataToEncode.serialNumber = TlvUInt8.encodeTlv(dataToEncode.serialNumber);
          break;
        default:
          throw new ImplementationError("Invalid serial number data");
      }
    }
    return schema.encode(dataToEncode);
  }
}
const QrPairingCodeCodec = new QrPairingCodeSchema();
class ManualPairingCodeSchema extends Schema {
  encodeInternal({ discriminator, passcode, vendorId, productId }) {
    if (discriminator === void 0) throw new UnexpectedDataError("discriminator is required");
    if (discriminator > 4095) throw new UnexpectedDataError("discriminator value must be less than 4096");
    let result = "";
    const hasVendorProductIds = vendorId !== void 0 && productId !== void 0;
    result += discriminator >> 10 | (hasVendorProductIds ? 1 << 2 : 0);
    result += ((discriminator & 768) << 6 | passcode & 16383).toString().padStart(5, "0");
    result += (passcode >> 14).toString().padStart(4, "0");
    if (hasVendorProductIds) {
      result += vendorId.toString().padStart(5, "0");
      result += productId.toString().padStart(5, "0");
    }
    result += new Verhoeff().computeChecksum(result);
    return result;
  }
  decodeInternal(encoded) {
    encoded = encoded.replace(/\D/g, "");
    if (encoded.length !== 11 && encoded.length !== 21) {
      throw new UnexpectedDataError("Invalid pairing code");
    }
    if (new Verhoeff().computeChecksum(encoded.slice(0, -1)) !== parseInt(encoded.slice(-1))) {
      throw new UnexpectedDataError("Invalid checksum");
    }
    const hasVendorProductIds = !!(parseInt(encoded[0]) & 1 << 2);
    const shortDiscriminator = (parseInt(encoded[0]) & 3) << 2 | parseInt(encoded.slice(1, 6)) >> 14 & 3;
    const passcode = parseInt(encoded.slice(1, 6)) & 16383 | parseInt(encoded.slice(6, 10)) << 14;
    let vendorId;
    let productId;
    if (hasVendorProductIds) {
      vendorId = VendorId(parseInt(encoded.slice(10, 15)));
      productId = parseInt(encoded.slice(15, 20));
    }
    return { shortDiscriminator, passcode, vendorId, productId };
  }
}
const ManualPairingCodeCodec = new ManualPairingCodeSchema();
export {
  CommissioningFlowType,
  DiscoveryCapabilitiesBitmap,
  DiscoveryCapabilitiesSchema,
  MATTER_QR_CODE_ALL_PAYLOAD_MAX_LENGTH,
  MATTER_QR_CODE_SINGLE_PAYLOAD_MAX_LENGTH,
  ManualPairingCodeCodec,
  QrCodeTlvDataDefaultFields,
  QrPairingCodeCodec
};
//# sourceMappingURL=PairingCodeSchema.js.map
