/**
 * @license
 * Copyright 2025-2026 Open Home Foundation
 * SPDX-License-Identifier: Apache-2.0
 */
import { AttributeId, Bytes, camelize, ClusterId, isObject, Logger } from "@matter/main";
import { FieldValue } from "@matter/main/model";
import { EndpointNumber, MATTER_EPOCH_OFFSET_S, MATTER_EPOCH_OFFSET_US } from "@matter/main/types";
const logger = new Logger("ChipToolWebSocketHandler");
function parseNumber(number) {
  const parsed = number.startsWith("0x") ? BigInt(number) : parseInt(number);
  if (typeof parsed === "number" && isNaN(parsed)) {
    throw new Error(`Failed to parse number: ${number}`);
  }
  return parsed;
}
function convertWebSocketGenericToMatter(value, model, clusterModel) {
  if (typeof value === "number" && model.metabase?.metatype === "bitmap") {
    const bitmapValue = {};
    for (const member of clusterModel.scope.membersOf(model)) {
      const memberName = member.name !== void 0 && model.name !== "FeatureMap" ? member.propertyName : member.title !== void 0 ? camelize(member.title) : void 0;
      if (memberName === void 0) {
        continue;
      }
      const constraintValue = FieldValue.numericValue(member.constraint.value);
      if (constraintValue !== void 0) {
        bitmapValue[memberName] = (value & 1 << constraintValue) !== 0;
      } else {
        const minBit = FieldValue.numericValue(member.constraint.min) ?? 0;
        const maxBit = FieldValue.numericValue(member.constraint.max);
        if (maxBit !== void 0) {
          const mask = (1 << maxBit - minBit + 1) - 1 << minBit;
          bitmapValue[memberName] = (value & mask) >> minBit;
        } else {
          bitmapValue[memberName] = (value & 1 << minBit) !== 0;
        }
      }
    }
    return bitmapValue;
  }
  if (typeof value === "string" && model.metabase?.metatype === "bytes") {
    return Bytes.fromBase64(value);
  }
  if (model.metabase?.metatype === "integer") {
    if (model.type === "epoch-s" && typeof value === "number") {
      return value + MATTER_EPOCH_OFFSET_S;
    } else if (model.type === "epoch-us" && (typeof value === "number" || typeof value === "bigint")) {
      return BigInt(value) + MATTER_EPOCH_OFFSET_US;
    }
  }
  return value;
}
function convertWebSocketTagBasedToMatter(value, model, clusterModel) {
  if (model === void 0 || value === null) {
    return value;
  }
  if (Array.isArray(value) && model.type === "list") {
    return value.map((v) => convertWebSocketTagBasedToMatter(v, model.members.at(0), clusterModel));
  }
  if (isObject(value) && model.metabase?.name === "struct") {
    const valueKeys = Object.keys(value);
    const result = {};
    const memberById = {};
    for (const member of model.members) {
      if (member.id !== void 0) {
        memberById[member.id] = member;
      }
    }
    for (const key of valueKeys) {
      const memberId = parseInt(key);
      if (!isNaN(memberId) && memberById[memberId]) {
        const member = memberById[memberId];
        result[member.propertyName] = convertWebSocketTagBasedToMatter(value[key], member, clusterModel);
      } else {
        result[key] = value[key];
      }
    }
    return result;
  }
  return convertWebSocketGenericToMatter(value, model, clusterModel);
}
function convertCommandDataToMatter(value, model, clusterModel) {
  if (model === void 0 || value === null) {
    return value;
  }
  if (Array.isArray(value) && model.type === "list") {
    return value.map((v) => convertCommandDataToMatter(v, model.members.at(0), clusterModel));
  }
  if (isObject(value) && model.metabase?.name === "struct") {
    const valueKeys = Object.keys(value);
    const result = {};
    const memberByName = {};
    for (const member of model.members) {
      if (member.name !== void 0) {
        memberByName[member.propertyName] = member;
      }
    }
    for (const key of valueKeys) {
      const camelizedKey = camelize(key);
      if (memberByName[camelizedKey]) {
        const member = memberByName[camelizedKey];
        if (value[key] === null && !member.mandatory && !member.nullable) {
          continue;
        }
        result[camelizedKey] = convertCommandDataToMatter(value[key], member, clusterModel);
      } else {
        result[key] = value[key];
      }
    }
    return result;
  }
  return convertWebSocketGenericToMatter(value, model, clusterModel);
}
var ConvKind = /* @__PURE__ */ ((ConvKind2) => {
  ConvKind2[ConvKind2["Passthrough"] = 0] = "Passthrough";
  ConvKind2[ConvKind2["EpochS"] = 1] = "EpochS";
  ConvKind2[ConvKind2["EpochUS"] = 2] = "EpochUS";
  ConvKind2[ConvKind2["Bytes"] = 3] = "Bytes";
  ConvKind2[ConvKind2["Bitmap"] = 4] = "Bitmap";
  ConvKind2[ConvKind2["Struct"] = 5] = "Struct";
  ConvKind2[ConvKind2["List"] = 6] = "List";
  return ConvKind2;
})(ConvKind || {});
const modelKindCache = /* @__PURE__ */ new WeakMap();
const structMemberCache = /* @__PURE__ */ new WeakMap();
function classifyModel(model) {
  let kind = modelKindCache.get(model);
  if (kind !== void 0) return kind;
  if (model.type === "list") {
    kind = 6 /* List */;
  } else if (model.metabase?.name === "struct") {
    kind = 5 /* Struct */;
  } else if (model.metabase?.metatype === "bitmap") {
    kind = 4 /* Bitmap */;
  } else if (model.metabase?.metatype === "bytes") {
    kind = 3 /* Bytes */;
  } else if (model.metabase?.metatype === "integer") {
    kind = model.type === "epoch-s" ? 1 /* EpochS */ : model.type === "epoch-us" ? 2 /* EpochUS */ : 0 /* Passthrough */;
  } else {
    kind = 0 /* Passthrough */;
  }
  modelKindCache.set(model, kind);
  return kind;
}
function getStructMembers(model) {
  let members = structMemberCache.get(model);
  if (members !== void 0) return members;
  members = [];
  for (const member of model.members) {
    if (member.name !== void 0 && member.id !== void 0) {
      members.push({ name: member.propertyName, id: member.id, model: member });
    }
  }
  structMemberCache.set(model, members);
  return members;
}
function convertMatterToWebSocketTagBased(value, model, clusterModel) {
  return convertMatterToWebSocket(value, model, clusterModel, true);
}
function convertMatterToWebSocketNameBased(value, model, clusterModel) {
  return convertMatterToWebSocket(value, model, clusterModel, false);
}
function convertMatterToWebSocket(value, model, clusterModel, tagBased) {
  if (value === null) {
    return null;
  }
  if (model === void 0) {
    if (Bytes.isBytes(value)) {
      return Bytes.toBase64(value);
    }
    if (Array.isArray(value)) {
      return value.map((v) => convertMatterToWebSocket(v, void 0, clusterModel, tagBased));
    }
    if (isObject(value) || !["string", "number", "bigint", "boolean", "undefined"].includes(typeof value)) {
      return null;
    }
    return value;
  }
  switch (classifyModel(model)) {
    case 0 /* Passthrough */:
      return value;
    case 1 /* EpochS */:
      return typeof value === "number" ? value - MATTER_EPOCH_OFFSET_S : value;
    case 2 /* EpochUS */:
      return typeof value === "number" || typeof value === "bigint" ? BigInt(value) - MATTER_EPOCH_OFFSET_US : value;
    case 3 /* Bytes */:
      return value instanceof Uint8Array ? Bytes.toBase64(value) : value;
    case 6 /* List */:
      return Array.isArray(value) ? value.map((v) => convertMatterToWebSocket(v, model.members.at(0), clusterModel, tagBased)) : value;
    case 5 /* Struct */: {
      if (!isObject(value)) return value;
      const result = {};
      for (const { name, id, model: memberModel } of getStructMembers(model)) {
        if (Object.hasOwn(value, name)) {
          result[tagBased ? id : name] = convertMatterToWebSocket(
            value[name],
            memberModel,
            clusterModel,
            tagBased
          );
        }
      }
      return result;
    }
    case 4 /* Bitmap */: {
      if (!isObject(value) || clusterModel === void 0) return value;
      let numberValue = 0;
      for (const member of clusterModel.scope.membersOf(model)) {
        const memberTitle = member.title !== void 0 ? camelize(member.title) : void 0;
        const memberValue = member.name !== void 0 && value[member.propertyName] ? value[member.propertyName] : memberTitle && value[memberTitle] ? value[memberTitle] : void 0;
        if (!memberValue) {
          continue;
        }
        if (typeof memberValue !== "boolean" && typeof memberValue !== "number") {
          throw new Error("Invalid bitmap value", memberValue);
        }
        const constraintValue = FieldValue.numericValue(member.constraint.value);
        if (constraintValue !== void 0) {
          numberValue |= 1 << constraintValue;
        } else {
          const minBit = FieldValue.numericValue(member.constraint.min) ?? 0;
          numberValue |= typeof memberValue === "boolean" ? 1 : memberValue << minBit;
        }
      }
      return numberValue;
    }
  }
}
function toBigIntAwareJson(object, spaces) {
  const replacements = new Array();
  let result = JSON.stringify(
    object,
    (_key, value) => {
      if (typeof value === "bigint") {
        if (value > Number.MAX_SAFE_INTEGER) {
          replacements.push({ from: `"0x${value.toString(16)}"`, to: value.toString() });
          return `0x${value.toString(16)}`;
        } else {
          return Number(value);
        }
      }
      return value;
    },
    spaces
  );
  if (replacements.length > 0) {
    replacements.forEach(({ from, to }) => {
      result = result.replaceAll(from, to);
    });
  }
  return result;
}
const BIGINT_MARKER = "__BIGINT__";
function parseBigIntAwareJson(json) {
  const result = [];
  let i = 0;
  let inString = false;
  while (i < json.length) {
    const char = json[i];
    if (inString) {
      if (char === "\\") {
        result.push(char);
        i++;
        if (i < json.length) {
          result.push(json[i]);
          i++;
        }
      } else if (char === '"') {
        result.push(char);
        inString = false;
        i++;
      } else {
        result.push(char);
        i++;
      }
    } else {
      if (char === '"') {
        result.push(char);
        inString = true;
        i++;
      } else if (char >= "0" && char <= "9") {
        const hasMinus = result.length > 0 && result[result.length - 1] === "-";
        if (hasMinus) {
          result.pop();
        }
        const start = i;
        while (i < json.length && json[i] >= "0" && json[i] <= "9") {
          i++;
        }
        let isFloat = false;
        if (i < json.length && json[i] === ".") {
          isFloat = true;
          i++;
          while (i < json.length && json[i] >= "0" && json[i] <= "9") {
            i++;
          }
        }
        if (i < json.length && (json[i] === "e" || json[i] === "E")) {
          isFloat = true;
          i++;
          if (i < json.length && (json[i] === "+" || json[i] === "-")) {
            i++;
          }
          while (i < json.length && json[i] >= "0" && json[i] <= "9") {
            i++;
          }
        }
        const numberStr = (hasMinus ? "-" : "") + json.slice(start, i);
        if (!isFloat && numberStr.length - (hasMinus ? 1 : 0) >= 15) {
          const num = BigInt(numberStr);
          if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) {
            result.push(`"${BIGINT_MARKER}${numberStr}"`);
          } else {
            result.push(numberStr);
          }
        } else {
          result.push(numberStr);
        }
      } else {
        result.push(char);
        i++;
      }
    }
  }
  const processed = result.join("");
  return JSON.parse(processed, (_key, value) => {
    if (typeof value === "string" && value.startsWith(BIGINT_MARKER)) {
      return BigInt(value.slice(BIGINT_MARKER.length));
    }
    return value;
  });
}
function parseChipJSON(json) {
  json = json.replace(/: (\d{15,})[,}]/g, (match, number) => {
    const num = BigInt(number);
    if (num > Number.MAX_SAFE_INTEGER) {
      return match.replace(number, `"0x${num.toString(16)}"`);
    }
    return match;
  });
  return JSON.parse(json);
}
function convertWebsocketDataToMatter(value, model) {
  if (value === void 0) {
    return void 0;
  }
  if (value === "null" || value === null) {
    return null;
  }
  if (model.type === "list") {
    if (typeof value === "string") {
      value = parseChipJSON(value);
    }
    if (Array.isArray(value)) {
      return value.map((v) => convertWebsocketDataToMatter(v, model.members.at(0)));
    }
  }
  if (model.metabase?.name === "struct") {
    if (typeof value === "string") {
      value = parseChipJSON(value);
    }
    if (typeof value === "object") {
      const members = model.members.reduce(
        (acc, member) => {
          if (member.name !== void 0) {
            acc[member.name.toLowerCase()] = member;
          }
          return acc;
        },
        {}
      );
      const valueKeys = Object.keys(value);
      const result = {};
      valueKeys.forEach((key) => {
        const member = members[camelize(key).toLowerCase()];
        if (member !== void 0) {
          result[member.propertyName] = convertWebsocketDataToMatter(value[key], member);
        }
      });
      return result;
    }
  }
  if ((typeof value === "number" || typeof value === "bigint") && (model.metabase?.metatype === "integer" || model.metabase?.metatype === "enum")) {
    if (model.type === "epoch-s" && typeof value === "number") {
      value += MATTER_EPOCH_OFFSET_S;
    } else if (model.type === "epoch-us") {
      value = BigInt(value) + MATTER_EPOCH_OFFSET_US;
    }
    return value;
  }
  if (typeof value === "string") {
    if (model.metabase?.metatype === "bytes" && value.startsWith("hex:")) {
      return Bytes.fromHex(value.slice(4));
    }
    if (model.metabase?.metatype === "bitmap") {
      const numberValue = parseInt(value);
      if (isNaN(numberValue)) {
        throw new Error("Invalid bitmap value");
      }
      const bitmapValue = {};
      model.members.forEach((member) => {
        if (member.constraint !== void 0 && member.name !== void 0 && numberValue & 1 << parseInt(member.constraint)) {
          bitmapValue[member.propertyName] = true;
        }
      });
      return bitmapValue;
    }
    if ((model.metabase?.metatype === "integer" || model.metabase?.metatype === "enum") && value.startsWith("0x") && value.match(/^0x[\da-fA-F]+$/) || value.match(/^-?[1-9]\d*$/) || value === "0") {
      let numberValue = parseNumber(value);
      if (model.type === "epoch-s" && typeof numberValue === "number") {
        numberValue += MATTER_EPOCH_OFFSET_S;
      } else if (model.type === "epoch-us") {
        numberValue = BigInt(value) + MATTER_EPOCH_OFFSET_US;
      }
      return numberValue;
    }
    if (model.metabase?.metatype === "boolean") {
      return value === "true" || value === "1" || value === "True";
    }
    if (model.metabase?.metatype === "string") {
      return value;
    }
  }
  logger.warn("UNHANDLED value ...", value, model.type, model.metatype, model.metabase?.metatype);
  return value;
}
function getDateAsString(date) {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, "0");
  const day = String(date.getDate()).padStart(2, "0");
  const hours = String(date.getHours()).padStart(2, "0");
  const minutes = String(date.getMinutes()).padStart(2, "0");
  const seconds = String(date.getSeconds()).padStart(2, "0");
  const milliseconds = String(date.getMilliseconds()).padStart(3, "0");
  const microseconds = "000";
  return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${milliseconds}${microseconds}`;
}
function buildAttributePath(endpointId, clusterId, attributeId) {
  return `${endpointId}/${clusterId}/${attributeId}`;
}
function splitAttributePath(path) {
  const [endpointStr, clusterStr, attributeStr] = path.split("/");
  let endpointNum = /^\d+$/.test(endpointStr) ? parseInt(endpointStr, 10) : void 0;
  let clusterNum = /^\d+$/.test(clusterStr) ? parseInt(clusterStr, 10) : void 0;
  let attributeNum = /^\d+$/.test(attributeStr) ? parseInt(attributeStr, 10) : void 0;
  if (endpointNum !== void 0 && endpointNum === 65535) {
    endpointNum = void 0;
  }
  if (clusterNum !== void 0 && clusterNum === 4294967295) {
    clusterNum = void 0;
  }
  if (attributeNum !== void 0 && attributeNum === 4294967295) {
    attributeNum = void 0;
  }
  return {
    endpointId: endpointNum !== void 0 ? EndpointNumber(endpointNum) : void 0,
    clusterId: clusterNum !== void 0 ? ClusterId(clusterNum) : void 0,
    attributeId: attributeNum !== void 0 ? AttributeId(attributeNum) : void 0
  };
}
export {
  buildAttributePath,
  convertCommandDataToMatter,
  convertMatterToWebSocketNameBased,
  convertMatterToWebSocketTagBased,
  convertWebSocketTagBasedToMatter,
  convertWebsocketDataToMatter,
  getDateAsString,
  parseBigIntAwareJson,
  parseNumber,
  splitAttributePath,
  toBigIntAwareJson
};
//# sourceMappingURL=Converters.js.map
