/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { decodeAttributeValueWithSchema, decodeUnknownAttributeValue } from "#interaction/AttributeDataDecoder.js";
import { decodeUnknownEventValue } from "#interaction/EventDataDecoder.js";
import { Diagnostic, Logger, UnexpectedDataError } from "@matter/general";
import {
  AcceptedCommandList,
  AttributeList,
  ClusterRevision,
  EventList,
  FeatureMap,
  GeneratedCommandList,
  Matter
} from "@matter/model";
import {
  Status,
  TlvAny,
  TlvOfModel,
  TlvType
} from "@matter/types";
const logger = Logger.get("InputChunk");
const DATA_REPORT_DECODE_OPTIONS = { relaxNumberTypeChecks: true };
function schemaOfModel(model) {
  try {
    return TlvOfModel(model);
  } catch {
    return void 0;
  }
}
const globalAttributeSchemas = /* @__PURE__ */ new Map();
for (const id of [
  ClusterRevision.id,
  FeatureMap.id,
  AttributeList.id,
  EventList.id,
  AcceptedCommandList.id,
  GeneratedCommandList.id
]) {
  const model = Matter.attributes(id);
  const schema = model === void 0 ? void 0 : schemaOfModel(model);
  if (schema !== void 0) {
    globalAttributeSchemas.set(id, schema);
  }
}
const seededGlobalAttributeSchemas = new Map(globalAttributeSchemas);
seededGlobalAttributeSchemas.delete(FeatureMap.id);
const clusterCache = /* @__PURE__ */ new Map();
function clusterCacheOf(clusterId) {
  let cache = clusterCache.get(clusterId);
  if (cache === void 0) {
    const model = Matter.clusters(clusterId);
    if (model === void 0) {
      return void 0;
    }
    cache = { model, attributes: new Map(seededGlobalAttributeSchemas), events: /* @__PURE__ */ new Map() };
    clusterCache.set(clusterId, cache);
  }
  return cache;
}
function attributeSchemaOf(clusterId, attributeId) {
  const cache = clusterCacheOf(clusterId);
  if (cache === void 0) {
    return globalAttributeSchemas.get(attributeId);
  }
  const cached = cache.attributes.get(attributeId);
  if (cached !== void 0) {
    return cached;
  }
  const attributeModel = cache.model.attributes(attributeId);
  if (attributeModel === void 0) {
    return void 0;
  }
  const schema = schemaOfModel(attributeModel);
  if (schema !== void 0) {
    cache.attributes.set(attributeId, schema);
  }
  return schema;
}
function eventSchemaOf(clusterId, eventId) {
  const cache = clusterCacheOf(clusterId);
  if (cache === void 0) {
    return void 0;
  }
  const cached = cache.events.get(eventId);
  if (cached !== void 0) {
    return cached;
  }
  const eventModel = cache.model.events(eventId);
  if (eventModel === void 0) {
    return void 0;
  }
  const schema = TlvOfModel(eventModel);
  cache.events.set(eventId, schema);
  return schema;
}
async function* InputChunk(input, leftoverAttributeReports) {
  yield* emitAttributes(input, leftoverAttributeReports);
  yield* emitEvents(input);
}
function* emitAttributes(input, leftover) {
  let attrs = input.attributeReports;
  if (leftover?.length) {
    attrs = attrs === void 0 ? [...leftover] : [...leftover, ...attrs];
    leftover.length = 0;
  }
  if (attrs === void 0 || attrs.length === 0) {
    return;
  }
  let lastPath;
  let group;
  for (const entry of attrs) {
    try {
      if (entry.attributeData !== void 0) {
        const data = entry.attributeData;
        const resolved = resolvePath(data.path, lastPath);
        if (data.path.enableTagCompression && data.dataVersion === void 0 && lastPath?.dataVersion !== void 0) {
          data.dataVersion = lastPath.dataVersion;
        }
        if (!data.path.enableTagCompression) {
          lastPath = { ...resolved, dataVersion: data.dataVersion };
        }
        if (group !== void 0 && !samePath(group.path, resolved)) {
          yield* flushDataGroup(group);
          group = void 0;
        }
        if (group === void 0) {
          group = { path: resolved, dataVersion: data.dataVersion, entries: [data] };
        } else {
          group.entries.push(data);
          if (group.dataVersion === void 0) group.dataVersion = data.dataVersion;
        }
      } else if (entry.attributeStatus !== void 0) {
        const statusSrc = entry.attributeStatus;
        const resolved = resolvePath(statusSrc.path, lastPath);
        if (!statusSrc.path.enableTagCompression) {
          lastPath = { ...resolved, dataVersion: lastPath?.dataVersion };
        }
        if (group !== void 0) {
          yield* flushDataGroup(group);
          group = void 0;
        }
        yield buildAttrStatus(resolved, statusSrc);
      }
    } catch (error) {
      UnexpectedDataError.accept(error);
      logger.warn("Skipping malformed attribute report entry:", Diagnostic.errorMessage(error));
    }
  }
  if (group !== void 0 && input.moreChunkedMessages && leftover !== void 0 && mayContinueInNextReport(group.entries[group.entries.length - 1])) {
    for (const d of group.entries) leftover.push({ attributeData: d });
    return;
  }
  if (group !== void 0) {
    yield* flushDataGroup(group);
  }
}
function* flushDataGroup(group) {
  const value = decodeAttributeGroup(group.path, group.dataVersion, group.entries);
  if (value !== void 0) yield value;
}
function resolvePath(path, lastPath) {
  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;
  } else if (path.endpointId === void 0 || path.clusterId === void 0 || path.attributeId === void 0) {
    throw new UnexpectedDataError("Tag compression disabled, but path is incomplete: " + Diagnostic.json(path));
  }
  return {
    nodeId: path.nodeId,
    endpointId: path.endpointId,
    clusterId: path.clusterId,
    attributeId: path.attributeId
  };
}
function samePath(a, b) {
  return a.nodeId === b.nodeId && a.endpointId === b.endpointId && a.clusterId === b.clusterId && a.attributeId === b.attributeId;
}
function mayContinueInNextReport(data) {
  if (data.path.listIndex !== void 0) return true;
  const tlvData = data.data;
  return Array.isArray(tlvData) && tlvData.length > 1 && tlvData[0].typeLength.type === TlvType.Array;
}
function decodeAttributeGroup(path, dataVersion, entries) {
  const { nodeId, endpointId, clusterId, attributeId } = path;
  try {
    const schema = attributeSchemaOf(clusterId, attributeId);
    const value = schema === void 0 ? decodeUnknownAttributeValue(entries) : decodeAttributeValueWithSchema(schema, entries, void 0, DATA_REPORT_DECODE_OPTIONS);
    return {
      kind: "attr-value",
      path: { nodeId, endpointId, clusterId, attributeId },
      tlv: TlvAny,
      value,
      // dataVersion is optional on the wire; default to 0 to satisfy the version: number contract
      version: dataVersion ?? 0
    };
  } catch (error) {
    UnexpectedDataError.accept(error);
    logger.warn(
      `Error decoding attribute ${endpointId}/${Diagnostic.hex(clusterId)}/${Diagnostic.hex(attributeId)}:`,
      Diagnostic.errorMessage(error)
    );
    return void 0;
  }
}
function buildAttrStatus(path, src) {
  const { nodeId, endpointId, clusterId, attributeId } = path;
  return {
    kind: "attr-status",
    path: { nodeId, endpointId, clusterId, attributeId },
    status: src.status.status ?? Status.Failure,
    clusterStatus: src.status.clusterStatus
  };
}
function* emitEvents(input) {
  const events = input.eventReports;
  if (events === void 0 || events.length === 0) return;
  for (const entry of events) {
    try {
      if (entry.eventData !== void 0) {
        const value = decodeEventValue(entry.eventData);
        if (value !== void 0) yield value;
      } else if (entry.eventStatus !== void 0) {
        const status = buildEventStatus(entry.eventStatus);
        if (status !== void 0) yield status;
      }
    } catch (error) {
      UnexpectedDataError.accept(error);
      logger.warn("Skipping malformed event report entry:", Diagnostic.errorMessage(error));
    }
  }
}
function decodeEventValue(eventData) {
  const {
    path: { nodeId, endpointId, clusterId, eventId },
    eventNumber,
    priority,
    epochTimestamp,
    systemTimestamp,
    deltaEpochTimestamp,
    deltaSystemTimestamp,
    data
  } = eventData;
  if (endpointId === void 0 || clusterId === void 0 || eventId === void 0) {
    throw new UnexpectedDataError(`Invalid event path ${endpointId}/${clusterId}/${eventId}`);
  }
  try {
    const schema = eventSchemaOf(clusterId, eventId);
    if (schema === void 0) {
      logger.debug(
        `Decode unknown event ${Diagnostic.hex(clusterId)}/${Diagnostic.hex(eventId)} via the AnySchema.`
      );
    }
    let value;
    if (data === void 0) {
      value = void 0;
    } else if (schema === void 0) {
      value = decodeUnknownEventValue(data);
    } else {
      value = schema.decodeTlv(data, DATA_REPORT_DECODE_OPTIONS);
    }
    const timestamp = Number(epochTimestamp ?? systemTimestamp ?? deltaEpochTimestamp ?? deltaSystemTimestamp ?? 0);
    return {
      kind: "event-value",
      path: { nodeId, endpointId, clusterId, eventId },
      value,
      number: eventNumber,
      priority,
      timestamp,
      epochTimestamp,
      systemTimestamp,
      deltaEpochTimestamp,
      deltaSystemTimestamp,
      tlv: TlvAny
    };
  } catch (error) {
    UnexpectedDataError.accept(error);
    logger.warn(
      `Error decoding event ${endpointId}/${Diagnostic.hex(clusterId)}/${Diagnostic.hex(eventId)}:`,
      Diagnostic.errorMessage(error)
    );
    return void 0;
  }
}
function buildEventStatus(src) {
  const {
    path: { nodeId, endpointId, clusterId, eventId },
    status
  } = src;
  if (endpointId === void 0 || clusterId === void 0 || eventId === void 0) {
    throw new UnexpectedDataError(`Invalid event path ${endpointId}/${clusterId}/${eventId}`);
  }
  if (status.status === void 0 && status.clusterStatus === void 0) {
    return void 0;
  }
  return {
    kind: "event-status",
    path: { nodeId, endpointId, clusterId, eventId },
    status: status.status ?? Status.Failure,
    clusterStatus: status.clusterStatus
  };
}
export {
  InputChunk
};
//# sourceMappingURL=InputChunk.js.map
