/**
 * @license
 * Copyright 2025-2026 Open Home Foundation
 * SPDX-License-Identifier: Apache-2.0
 */
import {
  Bytes,
  FabricId,
  FabricIndex,
  ImplementationError,
  isDeepEqual,
  isObject,
  Logger,
  NodeId,
  Time
} from "@matter/main";
import { CertificateAuthority, Fabric, FabricBuilder, Noc, PeerAddress } from "@matter/main/protocol";
import { Global, VendorId } from "@matter/main/types";
import { ClusterMap } from "../model/ModelMapper.js";
import { convertWebSocketTagBasedToMatter } from "../server/Converters.js";
const logger = Logger.get("LegacyDataInjector");
const BASE64_REGEX = /^([0-9a-z+/]{4})*(([0-9a-z+/]{2}==)|([0-9a-z+/]{3}=))?$/i;
const FEATUREMAP_ID = Global.attributes.featureMap.id.toString();
var LegacyDataInjector;
((LegacyDataInjector2) => {
  function isPrimitiveType(value) {
    return typeof value === "number" || value === null || typeof value == "boolean" || typeof value == "string" && !BASE64_REGEX.test(value);
  }
  async function injectCredentials(credentialsStorage, fabricsStorage, crypto, credentialData, fabricData) {
    const rootCertificateAuthority = new CertificateAuthority(crypto, credentialData);
    for (const [key, value] of Object.entries(credentialData)) {
      if (await credentialsStorage.has(key)) {
        if (isDeepEqual(await credentialsStorage.get(key), value)) {
          continue;
        }
        logger.warn(`Overriding credential ${key} with new value!`);
      }
      await credentialsStorage.set(key, value);
    }
    if (fabricData === void 0) {
      logger.warn("Credentials injected, but no fabric data provided. Skipping fabric initialization.");
      return;
    }
    const {
      fabricIndex,
      fabricId,
      nodeId,
      rootNodeId,
      rootCert,
      intermediateCACert,
      operationalCert,
      rootVendorId,
      rootPublicKey,
      identityProtectionKey,
      label
    } = fabricData;
    const tempFabric = await Fabric.create(crypto, {
      fabricIndex: FabricIndex(fabricIndex),
      fabricId: FabricId(fabricId),
      nodeId: NodeId(nodeId),
      rootNodeId: NodeId(rootNodeId),
      rootCert,
      intermediateCACert,
      operationalCert,
      rootVendorId: VendorId(rootVendorId),
      rootPublicKey,
      identityProtectionKey,
      label,
      keyPair: await crypto.createKeyPair()
      // Just use a new keypair temporarily because chip data does not have it
    });
    let keyRewritten = false;
    if (await credentialsStorage.has("fabric")) {
      const storedFabric = await credentialsStorage.get("fabric");
      if (!Bytes.areEqual(storedFabric.rootPublicKey, tempFabric.rootPublicKey)) {
        logger.warn("Existing fabric root public key changed. Rewriting from legacy data");
        keyRewritten = true;
      } else {
        logger.info("Fabric root public key unchanged. Skipping rewrite.");
        await syncFabrics(fabricsStorage, storedFabric, false);
        return;
      }
    }
    const builder = await FabricBuilder.create(crypto);
    builder.initializeFromFabricForUpdate(tempFabric);
    const {
      subject: { nodeId: certNodeId, fabricId: certFabricId }
    } = Noc.fromTlv(tempFabric.operationalCert).cert;
    if (certNodeId !== nodeId || certFabricId !== fabricId) {
      throw new ImplementationError(`Cannot rotate NOC for fabric because root node ID changed`);
    }
    await builder.setOperationalCert(
      await rootCertificateAuthority.generateNoc(builder.publicKey, certFabricId, certNodeId),
      tempFabric.intermediateCACert
    );
    const rootFabric = await builder.build(tempFabric.fabricIndex);
    await credentialsStorage.set("fabric", rootFabric.config);
    await syncFabrics(fabricsStorage, rootFabric.config, keyRewritten);
  }
  LegacyDataInjector2.injectCredentials = injectCredentials;
  async function syncFabrics(fabricsStorage, fabricConfig, overwriteKey) {
    if (!await fabricsStorage.has("fabrics")) {
      await fabricsStorage.set("fabrics", [fabricConfig]);
      logger.info("Initializing FabricManager storage");
      return;
    }
    let fabrics = await fabricsStorage.get("fabrics", []);
    if (overwriteKey) {
      fabrics = fabrics.filter((f) => f.fabricIndex !== fabricConfig.fabricIndex);
    } else if (fabrics.some(({ fabricIndex }) => fabricIndex === fabricConfig.fabricIndex)) {
      logger.info("FabricManager storage already has fabric index. Skipping update.");
      return;
    }
    fabrics.push(fabricConfig);
    await fabricsStorage.set("fabrics", fabrics);
    logger.info("Added fabric to FabricManager storage");
  }
  async function injectNodeData(baseStorage, nodeData, fabricIndex) {
    const nodesListStorage = baseStorage.createContext("nodes");
    const hasCommissionedNodes = await nodesListStorage.has("commissionedNodes");
    if (nodeData === void 0) {
      if (!hasCommissionedNodes) {
        await nodesListStorage.set("commissionedNodes", []);
      }
      return false;
    }
    if (typeof fabricIndex !== "number" || fabricIndex < 1 || fabricIndex > 254) {
      fabricIndex = void 0;
    }
    if (!fabricIndex || hasCommissionedNodes || (await nodesListStorage.contexts()).length) {
      return injectAsFullInjection(baseStorage, nodesListStorage, nodeData);
    }
    return injectAsDirectInjection(baseStorage, nodesListStorage, nodeData, FabricIndex(fabricIndex));
  }
  LegacyDataInjector2.injectNodeData = injectNodeData;
  async function injectAsFullInjection(baseStorage, nodesListStorage, nodeData) {
    const commissionedNodes = new Array();
    let injectedNodes = 0;
    const writes = new Array();
    for (const [nodeId, nodeDetails] of Object.entries(nodeData.nodes)) {
      const nodeStorage = baseStorage.createContext(`node-${nodeId}`);
      if (nodeId !== nodeDetails.node_id.toString()) {
        logger.warn(`Node ID mismatch in node data: ${nodeId} != ${nodeDetails.node_id}`);
      }
      commissionedNodes.push([NodeId(BigInt(nodeId)), {}]);
      let newNode = true;
      const attributes = Object.entries(nodeDetails.attributes);
      if (attributes.length === 0) {
        continue;
      }
      logger.info(`Injecting node ${nodeId} into storage`);
      let currentEndpointId;
      let currentClusterId;
      let endpointStorage;
      let clusterStorage;
      for (const [attributeKey, value] of attributes) {
        const [endpointId, clusterId, attributeId] = attributeKey.split("/");
        if (currentEndpointId !== endpointId) {
          endpointStorage = nodeStorage.createContext(endpointId);
          currentEndpointId = endpointId;
          currentClusterId = void 0;
        }
        if (currentClusterId !== clusterId) {
          clusterStorage = endpointStorage.createContext(clusterId);
          currentClusterId = clusterId;
          if (newNode) {
            if (await clusterStorage.has("__version__")) {
              logger.info(`Node ${nodeId} already exists. Skipping injection.`);
              break;
            }
            newNode = false;
            injectedNodes++;
          }
          writes.push(clusterStorage.set("__version__", 1));
        }
        const clusterModel = ClusterMap[clusterId];
        const model = clusterModel?.attributes?.[attributeId];
        if (clusterModel === void 0 || model === void 0) {
          if (attributeId === FEATUREMAP_ID) {
            logger.debug(
              `Node ${nodeId}: Attribute ${attributeKey}, unknown featuremap converted to empty featuremap`
            );
            writes.push(clusterStorage.set(attributeId, { value: {} }));
          } else if (isPrimitiveType(value) || Array.isArray(value) && value.every(isPrimitiveType)) {
            logger.debug(
              `Node ${nodeId}: Attribute ${attributeKey}, unknown primary type converted generically`,
              value
            );
            writes.push(clusterStorage.set(attributeId, { value }));
          } else {
            logger.debug(
              `Node ${nodeId}: Attribute ${attributeKey}, not found in and unclear value. Skipping injection.`,
              value
            );
          }
        } else {
          const convertedValue = isObject(value) && ("TLVValue" in value || "Reason" in value) ? void 0 : convertWebSocketTagBasedToMatter(value, model, clusterModel.model);
          if (convertedValue !== void 0) {
            writes.push(
              clusterStorage.set(attributeId, { value: convertedValue })
            );
          } else {
            logger.debug(`Attribute ${attributeKey} could not be converted. Skipping injection.`);
          }
        }
      }
    }
    if (writes.length) {
      logger.info(`... wait for all ${writes.length} records to be written ... be patient!`);
      await Promise.allSettled(writes);
    }
    if (injectedNodes > 0) {
      const knownNodes = await nodesListStorage.get("commissionedNodes", []);
      for (const [nodeId] of commissionedNodes) {
        if (!knownNodes.find(([knownNodeId]) => knownNodeId === nodeId)) {
          knownNodes.push([nodeId, {}]);
        }
      }
      await nodesListStorage.set("commissionedNodes", knownNodes);
    }
    return injectedNodes > 0;
  }
  async function injectAsDirectInjection(baseStorage, nodesStorage, nodeData, fabricIndex) {
    const commissionedNodes = new Array();
    let injectedNodes = 0;
    const writes = new Array();
    let peerCounter = 1;
    for (const [nodeId, nodeDetails] of Object.entries(nodeData.nodes)) {
      const peerId = `peer${peerCounter++}`;
      const legacyNodeStorage = baseStorage.createContext(`node-${nodeId}`);
      const peerStorage = nodesStorage.createContext(peerId).createContext("endpoints");
      if (nodeId !== nodeDetails.node_id.toString()) {
        logger.warn(`Node ID mismatch in node data: ${nodeId} != ${nodeDetails.node_id}`);
      }
      commissionedNodes.push([NodeId(BigInt(nodeId)), {}]);
      let newNode = true;
      logger.info(`Injecting node ${nodeId} directly into peer storage as ${peerId}`);
      writes.push(
        peerStorage.createContext("0").createContext("commissioning").set("peerAddress", PeerAddress({ fabricIndex, nodeId: NodeId(BigInt(nodeId)) }))
      );
      writes.push(peerStorage.createContext("0").createContext("commissioning").set("discoveredAt", Time.nowMs));
      let currentEndpointId;
      let currentClusterId;
      let endpointStorage;
      let clusterStorage;
      for (const [attributeKey, value] of Object.entries(nodeDetails.attributes)) {
        const [endpointId, clusterId, attributeId] = attributeKey.split("/");
        if (currentEndpointId !== endpointId) {
          endpointStorage = peerStorage.createContext(endpointId);
          currentEndpointId = endpointId;
          currentClusterId = void 0;
        }
        if (currentClusterId !== clusterId) {
          clusterStorage = endpointStorage.createContext(clusterId);
          currentClusterId = clusterId;
          if (newNode) {
            writes.push(
              legacyNodeStorage.createContext(endpointId).createContext(clusterId).set("__version__", 1)
            );
            newNode = false;
            injectedNodes++;
          }
          writes.push(clusterStorage.set("__version__", 1));
        }
        const clusterModel = ClusterMap[clusterId];
        const model = clusterModel?.attributes?.[attributeId];
        if (clusterModel === void 0 || model === void 0) {
          if (attributeId === FEATUREMAP_ID) {
            logger.debug(
              `Node ${nodeId}: Attribute ${attributeKey}, unknown featuremap converted to empty featuremap`
            );
            writes.push(clusterStorage.set(attributeId, {}));
          } else if (isPrimitiveType(value) || Array.isArray(value) && value.every(isPrimitiveType)) {
            logger.debug(
              `Node ${nodeId}: Attribute ${attributeKey}, unknown primary type converted generically`,
              value
            );
            writes.push(clusterStorage.set(attributeId, value));
          } else {
            logger.debug(
              `Node ${nodeId}: Attribute ${attributeKey}, not found in and unclear value. Skipping injection.`,
              value
            );
          }
        } else {
          const convertedValue = isObject(value) && ("TLVValue" in value || "Reason" in value) ? void 0 : convertWebSocketTagBasedToMatter(value, model, clusterModel.model);
          if (convertedValue !== void 0) {
            writes.push(clusterStorage.set(attributeId, convertedValue));
          } else {
            logger.debug(`Attribute ${attributeKey} could not be converted. Skipping injection.`);
          }
        }
      }
    }
    if (writes.length) {
      logger.info(`... wait for all ${writes.length} records to be written ... be patient!`);
      await Promise.allSettled(writes);
    }
    if (injectedNodes > 0) {
      const knownNodes = await nodesStorage.get("commissionedNodes", []);
      for (const [nodeId] of commissionedNodes) {
        if (!knownNodes.find(([knownNodeId]) => knownNodeId === nodeId)) {
          knownNodes.push([nodeId, {}]);
        }
      }
      await nodesStorage.set("commissionedNodes", knownNodes);
    }
    return injectedNodes > 0;
  }
})(LegacyDataInjector || (LegacyDataInjector = {}));
export {
  LegacyDataInjector
};
//# sourceMappingURL=LegacyDataInjector.js.map
