"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var FirmwareUpdateService_exports = {};
__export(FirmwareUpdateService_exports, {
  downloadFirmwareUpdate: () => downloadFirmwareUpdate,
  getAvailableFirmwareUpdates: () => getAvailableFirmwareUpdates,
  getAvailableFirmwareUpdatesBulk: () => getAvailableFirmwareUpdatesBulk
});
module.exports = __toCommonJS(FirmwareUpdateService_exports);
var import_core = require("@zwave-js/core");
var import_shared = require("@zwave-js/shared");
function serviceURL() {
  return (0, import_shared.getenv)("ZWAVEJS_FW_SERVICE_URL") || "https://firmware.zwave-js.io";
}
__name(serviceURL, "serviceURL");
const DOWNLOAD_TIMEOUT = 6e4;
const CHECK_TIMEOUT = 3e4;
const MAX_CACHE_SECONDS = 60 * 60 * 24;
const CLEAN_CACHE_INTERVAL_MS = 60 * 60 * 1e3;
const deviceFirmwareCache = new import_shared.ObjectKeyMap();
let requestQueue;
let cleanCacheTimeout;
function cleanCache() {
  cleanCacheTimeout?.clear();
  cleanCacheTimeout = void 0;
  const now = Date.now();
  for (const [deviceKey, cached] of deviceFirmwareCache) {
    if (cached.staleDate < now) {
      deviceFirmwareCache.delete(deviceKey);
    }
  }
  if (deviceFirmwareCache.size > 0) {
    cleanCacheTimeout = (0, import_shared.setTimer)(cleanCache, CLEAN_CACHE_INTERVAL_MS).unref();
  }
}
__name(cleanCache, "cleanCache");
function calculateCacheExpiry(response) {
  if (response.status === 200 && response.headers.has("cache-control")) {
    const cacheControl = response.headers.get("cache-control");
    const age = response.headers.get("age");
    const date = response.headers.get("date");
    let maxAge;
    const maxAgeMatch = cacheControl.match(/max-age=(\d+)/);
    if (maxAgeMatch) {
      maxAge = Math.max(0, parseInt(maxAgeMatch[1], 10));
    }
    if (maxAge) {
      let currentAge;
      if (age) {
        currentAge = parseInt(age, 10);
      } else if (date) {
        currentAge = (Date.now() - Date.parse(date)) / 1e3;
      } else {
        currentAge = 0;
      }
      currentAge = Math.max(0, currentAge);
      if (maxAge > currentAge) {
        return Date.now() + Math.min(MAX_CACHE_SECONDS, maxAge - currentAge) * 1e3;
      }
    }
  }
  return Date.now() + MAX_CACHE_SECONDS * 1e3;
}
__name(calculateCacheExpiry, "calculateCacheExpiry");
async function makeRequest(url, config) {
  const { default: ky } = await import("ky");
  const response = await ky(url, config);
  const responseJson = await response.json();
  return { data: responseJson, expiry: calculateCacheExpiry(response) };
}
__name(makeRequest, "makeRequest");
function hasExtension(pathname) {
  return /\.[a-z0-9_]+$/i.test(pathname);
}
__name(hasExtension, "hasExtension");
function rfRegionToUpdateServiceRegion(rfRegion) {
  switch (rfRegion) {
    case import_core.RFRegion.Europe:
    case import_core.RFRegion["Europe (Long Range)"]:
      return "europe";
    case import_core.RFRegion.USA:
    case import_core.RFRegion["USA (Long Range)"]:
      return "usa";
    case import_core.RFRegion["Australia/New Zealand"]:
      return "australia/new zealand";
    case import_core.RFRegion["Hong Kong"]:
      return "hong kong";
    case import_core.RFRegion.India:
      return "india";
    case import_core.RFRegion.Israel:
      return "israel";
    case import_core.RFRegion.Russia:
      return "russia";
    case import_core.RFRegion.China:
      return "china";
    case import_core.RFRegion.Japan:
      return "japan";
    case import_core.RFRegion.Korea:
      return "korea";
  }
}
__name(rfRegionToUpdateServiceRegion, "rfRegionToUpdateServiceRegion");
async function getAvailableFirmwareUpdatesBulk(deviceIds, options) {
  const uniqueDeviceIds = deviceIds.filter((device, index) => index === deviceIds.findIndex((d) => d.manufacturerId === device.manufacturerId && d.productType === device.productType && d.productId === device.productId && d.firmwareVersion === device.firmwareVersion));
  const now = Date.now();
  const freshDevices = [];
  const staleDevices = [];
  for (const device of uniqueDeviceIds) {
    const cached = deviceFirmwareCache.get(device);
    if (cached && cached.staleDate > now) {
      freshDevices.push(device);
    } else {
      staleDevices.push(device);
    }
  }
  if (staleDevices.length > 0) {
    const headers = new Headers({
      "User-Agent": options.userAgent,
      "Content-Type": "application/json"
    });
    if (options.apiKey) {
      headers.set("X-API-Key", options.apiKey);
    }
    const body = {
      devices: staleDevices.map((device) => ({
        manufacturerId: (0, import_shared.formatId)(device.manufacturerId),
        productType: (0, import_shared.formatId)(device.productType),
        productId: (0, import_shared.formatId)(device.productId),
        firmwareVersion: device.firmwareVersion
      }))
    };
    const rfRegion = rfRegionToUpdateServiceRegion(options.rfRegion);
    if (rfRegion) {
      body.region = rfRegion;
    }
    const url = `${serviceURL()}/api/v4/updates`;
    const config = {
      method: "POST",
      json: body,
      headers,
      timeout: CHECK_TIMEOUT
    };
    if (!requestQueue) {
      const PQueue = (await import("p-queue")).default;
      requestQueue = new PQueue({ concurrency: 2 });
    }
    const { data: result, expiry } = await requestQueue.add(() => makeRequest(url, config));
    for (const deviceResponse of result) {
      const originalDevice = staleDevices.find((device) => (0, import_shared.formatId)(device.manufacturerId) === deviceResponse.manufacturerId && (0, import_shared.formatId)(device.productType) === deviceResponse.productType && (0, import_shared.formatId)(device.productId) === deviceResponse.productId && (0, import_shared.padVersion)(device.firmwareVersion) === (0, import_shared.padVersion)(deviceResponse.firmwareVersion));
      if (originalDevice) {
        const updates = deviceResponse.updates.map((update) => ({
          device: originalDevice,
          ...update,
          channel: update.channel ?? "stable"
        }));
        deviceFirmwareCache.set(originalDevice, {
          updates,
          staleDate: expiry
        });
      }
    }
  }
  const ret = new import_shared.ObjectKeyMap();
  for (const deviceId of uniqueDeviceIds) {
    const updates = deviceFirmwareCache.get(deviceId)?.updates;
    if (updates) {
      ret.set(deviceId, updates);
    }
  }
  if (!cleanCacheTimeout) {
    cleanCacheTimeout = (0, import_shared.setTimer)(cleanCache, CLEAN_CACHE_INTERVAL_MS).unref();
  }
  return ret;
}
__name(getAvailableFirmwareUpdatesBulk, "getAvailableFirmwareUpdatesBulk");
async function getAvailableFirmwareUpdates(deviceId, options) {
  const bulkResult = await getAvailableFirmwareUpdatesBulk([deviceId], options);
  return bulkResult.get(deviceId) || [];
}
__name(getAvailableFirmwareUpdates, "getAvailableFirmwareUpdates");
async function downloadFirmwareUpdate(file) {
  const [hashAlgorithm, expectedHash] = file.integrity.split(":", 2);
  if (hashAlgorithm !== "sha256") {
    throw new import_core.ZWaveError(`Unsupported hash algorithm ${hashAlgorithm} for integrity check`, import_core.ZWaveErrorCodes.Argument_Invalid);
  }
  const { default: ky } = await import("ky");
  const downloadResponse = await ky.get(file.url, {
    timeout: DOWNLOAD_TIMEOUT
    // TODO: figure out how to do maxContentLength: MAX_FIRMWARE_SIZE,
  });
  const rawData = new Uint8Array(await downloadResponse.arrayBuffer());
  const requestedPathname = new URL(file.url).pathname;
  let actualPathname;
  try {
    actualPathname = new URL(downloadResponse.url).pathname;
  } catch {
  }
  let filename;
  const contentDisposition = downloadResponse.headers.get("content-disposition");
  if (contentDisposition?.startsWith("attachment; filename=")) {
    filename = contentDisposition.split("filename=")[1].replace(/^"/, "").replace(/[";]$/, "");
  } else if (actualPathname && hasExtension(actualPathname)) {
    filename = actualPathname;
  } else {
    filename = requestedPathname;
  }
  const format = (0, import_core.guessFirmwareFileFormat)(filename, rawData);
  const firmware = await (0, import_core.extractFirmware)(rawData, format);
  const actualHash = import_shared.Bytes.view(await (0, import_core.digest)("sha-256", firmware.data)).toString("hex");
  if (actualHash !== expectedHash) {
    throw new import_core.ZWaveError(`Integrity check failed. Expected hash ${expectedHash}, got ${actualHash}`, import_core.ZWaveErrorCodes.FWUpdateService_IntegrityCheckFailed);
  }
  return {
    data: firmware.data,
    // Don't trust the guessed firmware target, use the one from the provided info
    firmwareTarget: file.target
  };
}
__name(downloadFirmwareUpdate, "downloadFirmwareUpdate");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  downloadFirmwareUpdate,
  getAvailableFirmwareUpdates,
  getAvailableFirmwareUpdatesBulk
});
//# sourceMappingURL=FirmwareUpdateService.js.map
