/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { Diagnostic, Logger, UnexpectedDataError } from "@matter/general";
import { Matter } from "@matter/model";
import {
  ArraySchema,
  TlvAny,
  TlvOfModel,
  TlvType
} from "@matter/types";
const logger = Logger.get("AttributeDataDecoder");
function normalizeAndDecodeReadAttributeReport(data, leftoverAttributeReports) {
  if (leftoverAttributeReports !== void 0) {
    let chunkedArrayDataFound = false;
    let cutIndex = data.length;
    for (let i = data.length - 1; i >= 0; i--) {
      const attributeData = data[i].attributeData;
      if (attributeData === void 0) {
        break;
      }
      const { path, data: tlvData } = attributeData;
      if (path.listIndex !== void 0) {
        chunkedArrayDataFound = true;
        continue;
      } else if (chunkedArrayDataFound || // We found chunked elements before so this must be array initial value now
      Array.isArray(tlvData) && tlvData.length > 1 && tlvData[0].typeLength.type === TlvType.Array) {
        cutIndex = i;
      }
      break;
    }
    if (cutIndex < data.length) {
      const ignoreDataForNow = data.splice(cutIndex, data.length - cutIndex);
      ignoreDataForNow.forEach((entry) => leftoverAttributeReports.push(entry));
    }
  }
  const dataValues = data.flatMap(({ attributeData }) => attributeData !== void 0 ? attributeData : []);
  const dataStatus = data.flatMap(({ attributeStatus }) => attributeStatus !== void 0 ? attributeStatus : []);
  return {
    attributeData: normalizeAndDecodeAttributeData(dataValues),
    attributeStatus: normalizeAttributeStatus(dataStatus)
  };
}
function expandPathsInAttributeData(data, acceptWildcardPaths = false) {
  let lastPath;
  data.forEach((value) => {
    if (value === void 0) return;
    const { path } = value;
    if (path.enableTagCompression) {
      if (lastPath === void 0) throw new UnexpectedDataError("Tag compression enabled, but no previous path");
      if (path.nodeId === void 0 && lastPath.nodeId !== void 0) path.nodeId = lastPath.nodeId;
      if (path.endpointId === void 0) path.endpointId = lastPath.endpointId;
      if (path.clusterId === void 0) path.clusterId = lastPath.clusterId;
      if (path.attributeId === void 0) path.attributeId = lastPath.attributeId;
      if (value.dataVersion === void 0 && lastPath.dataVersion !== void 0)
        value.dataVersion = lastPath.dataVersion;
    } else if (path.endpointId !== void 0 && path.clusterId !== void 0 && path.attributeId !== void 0) {
      lastPath = {
        nodeId: path.nodeId,
        endpointId: path.endpointId,
        clusterId: path.clusterId,
        attributeId: path.attributeId,
        dataVersion: value.dataVersion
      };
    } else if (!acceptWildcardPaths) {
      throw new UnexpectedDataError("Tag compression disabled, but path is incomplete: " + Diagnostic.json(path));
    }
  });
  return data;
}
function normalizeAttributeData(data, acceptWildcardPaths = false) {
  const expandedData = expandPathsInAttributeData(data, acceptWildcardPaths);
  const responseList = /* @__PURE__ */ new Map();
  expandedData.forEach((value) => {
    if (!value) return;
    const {
      path: { nodeId, endpointId, clusterId, attributeId }
    } = value;
    const mapId = `${nodeId}-${endpointId}-${clusterId}-${attributeId}`;
    const list = responseList.get(mapId) || [];
    list.push(value);
    responseList.set(mapId, list);
  });
  return Array.from(responseList.values());
}
function normalizeAttributeStatus(data) {
  const result = new Array();
  data.forEach((entry) => {
    const {
      path: { nodeId, endpointId, clusterId, attributeId },
      status
    } = entry;
    if (endpointId === void 0 || clusterId === void 0 || attributeId === void 0) {
      throw new UnexpectedDataError(`Invalid attribute path ${endpointId}/${clusterId}/${attributeId}`);
    }
    const clusterModel = Matter.clusters(clusterId);
    const attributeModel = clusterModel?.attributes(attributeId);
    if (attributeModel === void 0) {
      result.push({
        path: {
          nodeId,
          endpointId,
          clusterId,
          attributeId,
          attributeName: `Unknown (${Diagnostic.hex(attributeId)})`
        },
        status: status.status,
        clusterStatus: status.clusterStatus
      });
      return;
    }
    result.push({
      path: { nodeId, endpointId, clusterId, attributeId, attributeName: attributeModel.propertyName },
      status: status.status,
      clusterStatus: status.clusterStatus
    });
  });
  return result;
}
function normalizeAndDecodeAttributeData(data) {
  const responseList = normalizeAttributeData(data);
  const result = new Array();
  responseList.forEach((values) => {
    const {
      path: { nodeId, endpointId, clusterId, attributeId },
      dataVersion
    } = values[0];
    if (endpointId === void 0 || clusterId === void 0 || attributeId === void 0) {
      throw new UnexpectedDataError(`Invalid attribute path ${endpointId}/${clusterId}/${attributeId}`);
    }
    try {
      const clusterModel = Matter.clusters(clusterId);
      const attributeModel = clusterModel?.attributes(attributeId);
      if (attributeModel === void 0) {
        const attributeName2 = `Unknown (${Diagnostic.hex(attributeId)})`;
        const value2 = decodeUnknownAttributeValue(values);
        result.push({
          path: { nodeId, endpointId, clusterId, attributeId, attributeName: attributeName2 },
          version: dataVersion,
          value: value2
        });
        return;
      }
      const attributeName = attributeModel.propertyName;
      let schema;
      try {
        schema = TlvOfModel(attributeModel);
      } catch {
        const value2 = decodeUnknownAttributeValue(values);
        result.push({
          path: { nodeId, endpointId, clusterId, attributeId, attributeName },
          version: dataVersion,
          value: value2
        });
        return;
      }
      const value = decodeAttributeValueWithSchema(schema, values);
      result.push({
        path: { nodeId, endpointId, clusterId, attributeId, attributeName },
        version: dataVersion,
        value
      });
    } catch (error) {
      logger.warn(
        `Error decoding attribute ${endpointId}/${Diagnostic.hex(clusterId)}/${Diagnostic.hex(attributeId)}: ${error.message}`
      );
    }
  });
  return result;
}
function decodeListAttributeValueWithSchema(schema, values, currentValue) {
  if (!(schema instanceof ArraySchema)) {
    throw new UnexpectedDataError(`Attribute is not a list but multiple values were returned.`);
  }
  return schema.decodeFromChunkedArray(
    values.map(({ data, path: { listIndex } }) => ({ listIndex, element: data })),
    currentValue
  );
}
function decodeAttributeValueWithSchema(schema, values, defaultValue) {
  if (!values.length) {
    return defaultValue;
  }
  if (schema instanceof ArraySchema) {
    return decodeListAttributeValueWithSchema(schema, values, defaultValue);
  }
  if (values.length === 1 && values[0].path.listIndex === void 0) {
    return schema.decodeTlv(values[0].data);
  }
  if (values.some(({ path: { listIndex } }) => listIndex !== void 0)) {
    throw new UnexpectedDataError(`Attribute is not a list but we got actions with list entries`);
  }
  const bestDataVersionValue = values.sort(({ dataVersion: a }, { dataVersion: b }) => (b ?? 0) - (a ?? 0));
  return schema.decodeTlv(bestDataVersionValue[0].data);
}
function decodeUnknownAttributeValue(values) {
  const schema = TlvAny;
  if (!values.length) {
    return void 0;
  }
  if (values.length === 1 && values[0].path.listIndex === void 0) {
    const tlvEncoded = schema.decodeTlv(values[0].data);
    return schema.decodeAnyTlvStream(tlvEncoded);
  } else {
    const tlvEncoded = new ArraySchema(schema).decodeFromChunkedArray(
      values.map(({ data, path: { listIndex } }) => ({ listIndex, element: data }))
    );
    return tlvEncoded.map((element) => schema.decodeAnyTlvStream(element));
  }
}
function structureReadAttributeDataToClusterObject(data) {
  const structure = {};
  for (const {
    path: { endpointId, clusterId, attributeName },
    value
  } of data) {
    if (structure[endpointId] === void 0) {
      if (endpointId === "__proto__") {
        continue;
      }
      structure[endpointId] = {};
    }
    if (structure[endpointId][clusterId] === void 0) {
      if (clusterId === "__proto__") {
        continue;
      }
      structure[endpointId][clusterId] = {};
    }
    structure[endpointId][clusterId][attributeName] = value;
  }
  return structure;
}
export {
  decodeAttributeValueWithSchema,
  decodeListAttributeValueWithSchema,
  decodeUnknownAttributeValue,
  expandPathsInAttributeData,
  normalizeAndDecodeAttributeData,
  normalizeAndDecodeReadAttributeReport,
  normalizeAttributeData,
  normalizeAttributeStatus,
  structureReadAttributeDataToClusterObject
};
//# sourceMappingURL=AttributeDataDecoder.js.map
