"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var NVM3_exports = {};
__export(NVM3_exports, {
  NVM3: () => NVM3
});
module.exports = __toCommonJS(NVM3_exports);
var import_core = require("@zwave-js/core");
var import_shared = require("@zwave-js/shared");
var import_definitions = require("./common/definitions.js");
var import_utils = require("./common/utils.js");
var import_consts = require("./nvm3/consts.js");
var import_files = require("./nvm3/files/index.js");
var import_object = require("./nvm3/object.js");
var import_page = require("./nvm3/page.js");
var import_utils2 = require("./nvm3/utils.js");
class NVM3 {
  static {
    __name(this, "NVM3");
  }
  constructor(io) {
    this._io = io;
  }
  _io;
  _access = import_definitions.NVMAccess.None;
  _info;
  get info() {
    return this._info;
  }
  async ensureReadable() {
    if (this._access === import_definitions.NVMAccess.Read || this._access === import_definitions.NVMAccess.ReadWrite) {
      return;
    }
    if (this._access === import_definitions.NVMAccess.Write) {
      await this._io.close();
    }
    this._access = await this._io.open(import_definitions.NVMAccess.Read);
  }
  async ensureWritable() {
    if (this._access === import_definitions.NVMAccess.Write || this._access === import_definitions.NVMAccess.ReadWrite) {
      return;
    }
    if (this._access === import_definitions.NVMAccess.Read) {
      await this._io.close();
    }
    this._access = await this._io.open(import_definitions.NVMAccess.Write);
  }
  async init() {
    await this.ensureReadable();
    let pageOffset = 0;
    const pages = [];
    let isSharedFileSystem = false;
    while (pageOffset < this._io.size) {
      const header = await readPageHeader(this._io, pageOffset);
      pages.push({
        ...header,
        objects: []
      });
      pageOffset += header.pageSize;
    }
    for (const page of pages) {
      let objectOffset = page.offset + import_consts.NVM3_PAGE_HEADER_SIZE;
      const nextPageOffset = page.offset + page.pageSize;
      while (objectOffset < nextPageOffset) {
        const objectHeader = await readObjectHeader(this._io, objectOffset);
        if (objectHeader) {
          page.objects.push(objectHeader);
          objectOffset += objectHeader.alignedSize;
          if (objectHeader.key === import_files.ApplicationVersionFile800ID) {
            isSharedFileSystem = true;
          }
        } else {
          break;
        }
      }
    }
    let applicationPages;
    let protocolPages;
    if (isSharedFileSystem) {
      applicationPages = pages;
      protocolPages = [];
    } else {
      applicationPages = pages.filter((p) => p.offset < import_consts.ZWAVE_APPLICATION_NVM_SIZE);
      protocolPages = pages.filter((p) => p.offset >= import_consts.ZWAVE_APPLICATION_NVM_SIZE);
    }
    const pageInfoToSectionInfo = /* @__PURE__ */ __name((pages2) => {
      const maxEraseCount = Math.max(...pages2.map((p) => p.eraseCount));
      let currentPageIndex = pages2.findLastIndex((p) => p.eraseCount === maxEraseCount && p.objects.length > 0);
      if (currentPageIndex === -1) {
        currentPageIndex = pages2.findLastIndex((p) => p.objects.length > 0);
      }
      if (currentPageIndex === -1)
        currentPageIndex = 0;
      const currentPage = pages2[currentPageIndex];
      let offset = import_consts.NVM3_PAGE_HEADER_SIZE;
      for (const object of currentPage.objects) {
        offset += object.alignedSize;
      }
      const objectLocations = /* @__PURE__ */ new Map();
      for (let i = 0; i < pages2.length; i++) {
        const page = pages2[i];
        for (const object of page.objects) {
          const location = objectLocations.get(object.key);
          if (location == void 0) {
            objectLocations.set(object.key, i);
          } else if ((object.fragmentType === import_consts.FragmentType.None || object.fragmentType === import_consts.FragmentType.First) && page.eraseCount >= pages2[location].eraseCount) {
            objectLocations.set(object.key, i);
          }
        }
      }
      return {
        pages: pages2,
        offsetInPage: offset,
        currentPage: currentPageIndex,
        objectLocations
      };
    }, "pageInfoToSectionInfo");
    if (isSharedFileSystem) {
      this._info = {
        isSharedFileSystem: true,
        sections: {
          all: pageInfoToSectionInfo(applicationPages)
        }
      };
    } else {
      this._info = {
        isSharedFileSystem: false,
        sections: {
          application: pageInfoToSectionInfo(applicationPages),
          protocol: pageInfoToSectionInfo(protocolPages)
        }
      };
    }
    return this._info;
  }
  getNVMSectionForFile(fileId) {
    return this._info.isSharedFileSystem ? this._info.sections.all : this._info.sections[(0, import_files.getNVMSectionByFileID)(fileId)];
  }
  async has(fileId) {
    this._info ??= await this.init();
    const section = this.getNVMSectionForFile(fileId);
    return section.objectLocations.has(fileId);
  }
  readObjectData(object) {
    return (0, import_utils.nvmReadBuffer)(this._io, object.offset + object.headerSize, object.fragmentSize);
  }
  async get(fileId, section) {
    this._info ??= await this.init();
    section ??= this.getNVMSectionForFile(fileId);
    const pages = section.pages;
    let parts;
    let complete = false;
    let objType;
    const resetFragments = /* @__PURE__ */ __name(() => {
      parts = void 0;
      complete = false;
    }, "resetFragments");
    pages: for (let offset = 0; offset < pages.length; offset++) {
      const index = (section.currentPage - offset + pages.length) % pages.length;
      const page = pages[index];
      objects: for (let j = page.objects.length - 1; j >= 0; j--) {
        const object = page.objects[j];
        const readObject = /* @__PURE__ */ __name(() => this.readObjectData(object), "readObject");
        if (object.key !== fileId) {
          resetFragments();
          continue objects;
        }
        if (object.type === import_consts.ObjectType.Deleted) {
          return;
        } else if (object.fragmentType === import_consts.FragmentType.None) {
          parts = [await readObject()];
          objType = object.type;
          complete = true;
          break pages;
        } else if (object.fragmentType === import_consts.FragmentType.Last) {
          parts = [await readObject()];
          objType = object.type;
          complete = false;
        } else if (object.fragmentType === import_consts.FragmentType.Next) {
          if (parts?.length && objType === object.type) {
            parts.unshift(await readObject());
          } else {
            resetFragments();
          }
        } else if (object.fragmentType === import_consts.FragmentType.First) {
          if (parts?.length && objType === object.type) {
            parts.unshift(await readObject());
            complete = true;
            break pages;
          } else {
            resetFragments();
          }
        }
      }
    }
    if (!parts?.length || !complete || objType == void 0)
      return;
    return import_shared.Bytes.concat(parts);
  }
  async writeObjects(objects) {
    const section = this.getNVMSectionForFile(objects[0].key);
    let page = section.pages[section.currentPage];
    let remainingSpace = page.pageSize - import_consts.NVM3_PAGE_HEADER_SIZE - section.offsetInPage;
    const nextPage = /* @__PURE__ */ __name(async () => {
      section.currentPage = (section.currentPage + 1) % section.pages.length;
      page = section.pages[section.currentPage];
      const toPreserve = [...section.objectLocations].filter(([, pageIndex]) => pageIndex === section.currentPage).map(([fileID]) => page.objects.findLast((h) => h.key === fileID)).filter((h) => h != void 0).filter((h) => h.type !== import_consts.ObjectType.Deleted);
      for (const header of toPreserve) {
        const data = await this.get(header.key);
        console.error(`Need to preserve object ${(0, import_shared.num2hex)(header.key)}
  page index: ${section.currentPage}
  object type: ${(0, import_shared.getEnumMemberName)(import_consts.ObjectType, header.type)}
  data:        ${data != void 0 ? `${data.length} bytes` : "(no data)"}`);
        objects.push({
          key: header.key,
          type: header.type,
          fragmentType: import_consts.FragmentType.None,
          data
        });
      }
      if (page.objects.length > 0) {
        page.eraseCount++;
        page.objects = [];
        const pageHeaderBuffer = (0, import_page.serializePageHeader)(page);
        const pageBuffer = new Uint8Array(page.pageSize).fill(255);
        pageBuffer.set(pageHeaderBuffer, 0);
        await (0, import_utils.nvmWriteBuffer)(this._io, page.offset, pageBuffer);
      }
      section.offsetInPage = import_consts.NVM3_PAGE_HEADER_SIZE;
      remainingSpace = page.pageSize - import_consts.NVM3_PAGE_HEADER_SIZE;
    }, "nextPage");
    for (const object of objects) {
      const isLargeObject = object.type === import_consts.ObjectType.DataLarge || object.type === import_consts.ObjectType.CounterLarge;
      let fragments;
      if (isLargeObject) {
        if (remainingSpace <= import_consts.NVM3_OBJ_HEADER_SIZE_LARGE) {
          await nextPage();
        }
        fragments = (0, import_object.fragmentLargeObject)(object, remainingSpace, page.pageSize - import_consts.NVM3_PAGE_HEADER_SIZE);
      } else {
        const requiredSpace = (0, import_object.getRequiredSpace)(object);
        if (requiredSpace > remainingSpace) {
          await nextPage();
        }
        fragments = [object];
      }
      for (let i = 0; i < fragments.length; i++) {
        if (i > 0)
          await nextPage();
        const fragment = fragments[i];
        const objBuffer = (0, import_object.serializeObject)(fragment);
        const objOffset = page.offset + section.offsetInPage;
        await this._io.write(objOffset, objBuffer);
        const requiredSpace = (0, import_object.getRequiredSpace)(fragment);
        section.offsetInPage += requiredSpace;
        remainingSpace -= requiredSpace;
        page.objects.push((0, import_object.getObjectHeader)(object, objOffset));
        if (object.type === import_consts.ObjectType.Deleted) {
          section.objectLocations.delete(object.key);
        } else if (fragment.fragmentType === import_consts.FragmentType.None || fragment.fragmentType === import_consts.FragmentType.First) {
          section.objectLocations.set(fragment.key, section.currentPage);
        }
      }
    }
  }
  async set(property, value) {
    if (!this._info)
      await this.init();
    await this.ensureWritable();
    await this.writeObjects([{
      key: property,
      type: value.length <= import_consts.NVM3_MAX_OBJ_SIZE_SMALL ? import_consts.ObjectType.DataSmall : import_consts.ObjectType.DataLarge,
      // writeObject deals with fragmentation
      fragmentType: import_consts.FragmentType.None,
      data: value
    }]);
  }
  /** Writes multiple values to the NVM at once. `null` / `undefined` cause the value to be deleted */
  async setMany(values) {
    if (!this._info)
      await this.init();
    await this.ensureWritable();
    const objectsBySection = /* @__PURE__ */ new Map();
    for (const [key, value] of values) {
      const sectionOffset = this.getNVMSectionForFile(key).pages[0].offset;
      if (!objectsBySection.has(sectionOffset)) {
        objectsBySection.set(sectionOffset, []);
      }
      objectsBySection.get(sectionOffset).push([key, value]);
    }
    for (const objectGroups of objectsBySection.values()) {
      await this.writeObjects(objectGroups.map(([key, value]) => value ? {
        key,
        type: value.length <= import_consts.NVM3_MAX_OBJ_SIZE_SMALL ? import_consts.ObjectType.DataSmall : import_consts.ObjectType.DataLarge,
        // writeObject deals with fragmentation
        fragmentType: import_consts.FragmentType.None,
        data: value
      } : {
        key,
        type: import_consts.ObjectType.Deleted,
        fragmentType: import_consts.FragmentType.None
      }));
    }
  }
  async delete(property) {
    if (!this._info)
      await this.init();
    await this.ensureWritable();
    await this.writeObjects([{
      key: property,
      type: import_consts.ObjectType.Deleted,
      fragmentType: import_consts.FragmentType.None
    }]);
  }
  async erase(options) {
    const { deviceFamily = 2047, writeSize = import_consts.PageWriteSize.WRITE_SIZE_16, memoryMapped = true, sharedFileSystem = false } = options ?? {};
    const maxPageSize = sharedFileSystem ? import_consts.FLASH_MAX_PAGE_SIZE_800 : import_consts.FLASH_MAX_PAGE_SIZE_700;
    const pageSize = Math.min(options?.pageSize ?? maxPageSize, maxPageSize);
    if (this._io.size % pageSize !== 0) {
      throw new import_core.ZWaveError(`Invalid page size. NVM size ${this._io.size} must be a multiple of the page size ${pageSize}.`, import_core.ZWaveErrorCodes.Argument_Invalid);
    } else if (!sharedFileSystem && import_consts.ZWAVE_APPLICATION_NVM_SIZE % pageSize !== 0) {
      throw new import_core.ZWaveError(`Invalid page size. The application NVM size ${import_consts.ZWAVE_APPLICATION_NVM_SIZE} must be a multiple of the page size ${pageSize}.`, import_core.ZWaveErrorCodes.Argument_Invalid);
    } else if (!sharedFileSystem && (this._io.size - import_consts.ZWAVE_APPLICATION_NVM_SIZE) % pageSize !== 0) {
      throw new import_core.ZWaveError(`Invalid page size. The protocol NVM size ${this._io.size - import_consts.ZWAVE_APPLICATION_NVM_SIZE} must be a multiple of the page size ${pageSize}.`, import_core.ZWaveErrorCodes.Argument_Invalid);
    }
    await this.ensureWritable();
    const applicationPages = [];
    const protocolPages = [];
    const numPages = this._io.size / pageSize;
    for (let i = 0; i < numPages; i++) {
      const offset = i * pageSize;
      const pageBuffer = new Uint8Array(pageSize).fill(255);
      const pageHeader = {
        offset,
        version: 1,
        eraseCount: 0,
        encrypted: false,
        deviceFamily,
        memoryMapped,
        pageSize,
        status: import_consts.PageStatus.OK,
        writeSize
      };
      pageBuffer.set((0, import_page.serializePageHeader)(pageHeader), 0);
      await (0, import_utils.nvmWriteBuffer)(this._io, offset, pageBuffer);
      if (sharedFileSystem || offset < import_consts.ZWAVE_APPLICATION_NVM_SIZE) {
        applicationPages.push({ ...pageHeader, objects: [] });
      } else {
        protocolPages.push({ ...pageHeader, objects: [] });
      }
    }
    this._info = sharedFileSystem ? {
      isSharedFileSystem: true,
      sections: {
        all: {
          currentPage: 0,
          objectLocations: /* @__PURE__ */ new Map(),
          offsetInPage: import_consts.NVM3_PAGE_HEADER_SIZE,
          pages: applicationPages
        }
      }
    } : {
      isSharedFileSystem: false,
      sections: {
        application: {
          currentPage: 0,
          objectLocations: /* @__PURE__ */ new Map(),
          offsetInPage: import_consts.NVM3_PAGE_HEADER_SIZE,
          pages: applicationPages
        },
        protocol: {
          currentPage: 0,
          objectLocations: /* @__PURE__ */ new Map(),
          offsetInPage: import_consts.NVM3_PAGE_HEADER_SIZE,
          pages: protocolPages
        }
      }
    };
  }
}
async function readPageHeader(io, offset) {
  if (offset > io.size - import_consts.NVM3_PAGE_HEADER_SIZE) {
    throw new import_core.ZWaveError("Incomplete page in buffer!", import_core.ZWaveErrorCodes.NVM_InvalidFormat);
  }
  const buffer = import_shared.Bytes.view((await io.read(offset, import_consts.NVM3_PAGE_HEADER_SIZE)).buffer);
  const { version, eraseCount } = tryGetVersionAndEraseCount(buffer);
  const status = buffer.readUInt32LE(12);
  const devInfo = buffer.readUInt16LE(16);
  const deviceFamily = devInfo & 2047;
  const writeSize = devInfo >> 11 & 1;
  const memoryMapped = !!(devInfo >> 12 & 1);
  let pageSize = (0, import_page.pageSizeFromBits)(devInfo >> 13 & 7);
  if (pageSize > 65535) {
    for (let exponent = 0; exponent < 7; exponent++) {
      const testPageSize = (0, import_page.pageSizeFromBits)(exponent);
      const nextOffset = offset + testPageSize;
      if (
        // exactly end of NVM OR
        io.size === nextOffset || await isValidPageHeaderAtOffset(io, nextOffset)
      ) {
        pageSize = testPageSize;
        break;
      }
    }
  }
  if (pageSize > 65535) {
    throw new import_core.ZWaveError("Could not determine page size!", import_core.ZWaveErrorCodes.NVM_InvalidFormat);
  }
  if (io.size < offset + pageSize) {
    throw new import_core.ZWaveError(`NVM contains incomplete page at offset ${(0, import_shared.num2hex)(offset)}!`, import_core.ZWaveErrorCodes.NVM_InvalidFormat);
  }
  const formatInfo = buffer.readUInt16LE(18);
  const encrypted = !(formatInfo & 1);
  return {
    offset,
    version,
    eraseCount,
    status,
    encrypted,
    pageSize,
    writeSize,
    memoryMapped,
    deviceFamily
  };
}
__name(readPageHeader, "readPageHeader");
function tryGetVersionAndEraseCount(header) {
  const buffer = import_shared.Bytes.view(header);
  const version = buffer.readUInt16LE(0);
  const magic = buffer.readUInt16LE(2);
  if (magic !== import_consts.NVM3_PAGE_MAGIC) {
    throw new import_core.ZWaveError("Not a valid NVM3 page!", import_core.ZWaveErrorCodes.NVM_InvalidFormat);
  }
  if (version !== 1) {
    throw new import_core.ZWaveError(`Unsupported NVM3 page version: ${version}`, import_core.ZWaveErrorCodes.NVM_NotSupported);
  }
  let eraseCount = buffer.readUInt32LE(4);
  const eraseCountCode = eraseCount >>> import_consts.NVM3_PAGE_COUNTER_SIZE;
  eraseCount &= import_consts.NVM3_PAGE_COUNTER_MASK;
  (0, import_utils2.validateBergerCode)(eraseCount, eraseCountCode, import_consts.NVM3_PAGE_COUNTER_SIZE);
  let eraseCountInv = buffer.readUInt32LE(8);
  const eraseCountInvCode = eraseCountInv >>> import_consts.NVM3_PAGE_COUNTER_SIZE;
  eraseCountInv &= import_consts.NVM3_PAGE_COUNTER_MASK;
  (0, import_utils2.validateBergerCode)(eraseCountInv, eraseCountInvCode, import_consts.NVM3_PAGE_COUNTER_SIZE);
  if (eraseCount !== (~eraseCountInv & import_consts.NVM3_PAGE_COUNTER_MASK)) {
    throw new import_core.ZWaveError("Invalid erase count!", import_core.ZWaveErrorCodes.NVM_InvalidFormat);
  }
  return { version, eraseCount };
}
__name(tryGetVersionAndEraseCount, "tryGetVersionAndEraseCount");
async function isValidPageHeaderAtOffset(io, offset) {
  if (offset > io.size - import_consts.NVM3_PAGE_HEADER_SIZE) {
    return false;
  }
  const { buffer } = await io.read(offset, import_consts.NVM3_PAGE_HEADER_SIZE);
  try {
    tryGetVersionAndEraseCount(buffer);
    return true;
  } catch {
    return false;
  }
}
__name(isValidPageHeaderAtOffset, "isValidPageHeaderAtOffset");
async function readObjectHeader(io, offset) {
  let headerSize = 4;
  const hdr1 = await (0, import_utils.nvmReadUInt32LE)(io, offset);
  if (hdr1 === 4294967295)
    return;
  const key = hdr1 >> import_consts.NVM3_OBJ_KEY_SHIFT & import_consts.NVM3_OBJ_KEY_MASK;
  let objType = hdr1 & import_consts.NVM3_OBJ_TYPE_MASK;
  let fragmentSize = 0;
  let hdr2;
  const isLarge = objType === import_consts.ObjectType.DataLarge || objType === import_consts.ObjectType.CounterLarge;
  if (isLarge) {
    hdr2 = await (0, import_utils.nvmReadUInt32LE)(io, offset + 4);
    headerSize += 4;
    fragmentSize = hdr2 & import_consts.NVM3_OBJ_LARGE_LEN_MASK;
  } else if (objType > import_consts.ObjectType.DataSmall) {
    fragmentSize = objType - import_consts.ObjectType.DataSmall;
    objType = import_consts.ObjectType.DataSmall;
  } else if (objType === import_consts.ObjectType.CounterSmall) {
    fragmentSize = import_consts.NVM3_COUNTER_SIZE;
  }
  const fragmentType = isLarge ? hdr1 >>> import_consts.NVM3_OBJ_FRAGTYPE_SHIFT & import_consts.NVM3_OBJ_FRAGTYPE_MASK : import_consts.FragmentType.None;
  if (isLarge) {
    (0, import_utils2.validateBergerCodeMulti)([hdr1, hdr2], 32 + import_consts.NVM3_CODE_LARGE_SHIFT);
  } else {
    (0, import_utils2.validateBergerCodeMulti)([hdr1], import_consts.NVM3_CODE_SMALL_SHIFT);
  }
  if (io.size < offset + headerSize + fragmentSize) {
    throw new import_core.ZWaveError(`NVM contains incomplete object at offset ${(0, import_shared.num2hex)(offset)}!`, import_core.ZWaveErrorCodes.NVM_InvalidFormat);
  }
  const alignedFragmentSize = (0, import_object.getAlignedSize)(fragmentSize);
  const alignedSize = headerSize + alignedFragmentSize;
  return {
    key,
    offset,
    type: objType,
    fragmentType,
    headerSize,
    fragmentSize,
    alignedSize
  };
}
__name(readObjectHeader, "readObjectHeader");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  NVM3
});
//# sourceMappingURL=NVM3.js.map
