// src/store.ts
import { AccountSchema, ADDRESS_BYTES, emptyAccount } from "@asentum/types";
import { ClassicLevel } from "classic-level";

// src/errors.ts
var StateError = class extends Error {
  constructor(message, options) {
    super(message, options);
    this.name = "StateError";
  }
};
var NotFoundError = class extends StateError {
  constructor(key, options) {
    super(`State key not found: ${key}`, options);
    this.name = "NotFoundError";
  }
};

// src/root.ts
import { blake3 } from "@noble/hashes/blake3";
function computeStateRoot(entries) {
  const list = [];
  for (const entry of entries) list.push(entry);
  list.sort((a, b) => compareBytes(a.key, b.key));
  const parts = [];
  for (const { key, value } of list) {
    parts.push(u32be(key.length), key, u32be(value.length), value);
  }
  return blake3(concatBytes(parts));
}
function compareBytes(a, b) {
  const len = Math.min(a.length, b.length);
  for (let i = 0; i < len; i++) {
    const ai = a[i] ?? 0;
    const bi = b[i] ?? 0;
    if (ai !== bi) return ai - bi;
  }
  return a.length - b.length;
}
function u32be(n) {
  const out = new Uint8Array(4);
  out[0] = n >>> 24 & 255;
  out[1] = n >>> 16 & 255;
  out[2] = n >>> 8 & 255;
  out[3] = n & 255;
  return out;
}
function concatBytes(parts) {
  let total = 0;
  for (const p of parts) total += p.length;
  const out = new Uint8Array(total);
  let offset = 0;
  for (const p of parts) {
    out.set(p, offset);
    offset += p.length;
  }
  return out;
}

// src/store.ts
var ACCOUNT_PREFIX = new Uint8Array([97, 58]);
var ACCOUNT_PREFIX_END = new Uint8Array([97, 59]);
var CODE_PREFIX = new Uint8Array([107, 58]);
var STORAGE_PREFIX = new Uint8Array([115, 58]);
var STORAGE_KEY_SEP = 58;
var StateStore = class {
  db;
  constructor(options) {
    this.db = new ClassicLevel(options.path, {
      keyEncoding: "view",
      valueEncoding: "view"
    });
  }
  /** Opens the underlying database. Must be called before any other operation. */
  async open() {
    await this.db.open();
  }
  /** Closes the underlying database. Safe to call multiple times. */
  async close() {
    if (this.db.status === "open" || this.db.status === "opening") {
      await this.db.close();
    }
  }
  /**
   * Returns the account at `address`, or `null` if none exists.
   *
   * @param address 20 raw address bytes.
   */
  async getAccount(address) {
    validateAddress(address);
    const key = accountKey(address);
    try {
      const value = await this.db.get(key);
      return deserializeAccount(value);
    } catch (err) {
      if (isNotFoundError(err)) return null;
      throw new StateError("getAccount failed", { cause: err });
    }
  }
  /**
   * Convenience: returns the account at `address`, or a fresh empty account
   * if none exists. Use this when you want "read-or-default" semantics.
   */
  async getOrCreate(address) {
    const existing = await this.getAccount(address);
    return existing ?? emptyAccount();
  }
  /**
   * Writes `account` as the state at `address`, overwriting any existing
   * value.
   *
   * @param address 20 raw address bytes.
   */
  async setAccount(address, account) {
    validateAddress(address);
    const key = accountKey(address);
    const value = AccountSchema.serialize(account);
    try {
      await this.db.put(key, value);
    } catch (err) {
      throw new StateError("setAccount failed", { cause: err });
    }
  }
  /**
   * Deletes the account at `address`. Throws if no such account exists.
   *
   * @param address 20 raw address bytes.
   */
  async deleteAccount(address) {
    validateAddress(address);
    const existing = await this.getAccount(address);
    if (!existing) {
      throw new NotFoundError(bytesToHex(address));
    }
    const key = accountKey(address);
    await this.db.del(key);
  }
  /**
   * Iterates all accounts in byte-lexicographic order of address.
   *
   * @dev Loads the entire account set into memory. Will need a streaming
   * or paginated variant once state grows large.
   */
  async allAccounts() {
    const out = [];
    for await (const [key, value] of this.db.iterator({
      gte: ACCOUNT_PREFIX,
      lt: ACCOUNT_PREFIX_END
    })) {
      const address = key.slice(ACCOUNT_PREFIX.length);
      const account = deserializeAccount(value);
      out.push({ address, account });
    }
    return out;
  }
  /**
   * Computes the deterministic state root over all stored accounts.
   * Equivalent to collecting every entry and passing it to
   * `computeStateRoot`.
   */
  async stateRoot() {
    const entries = [];
    for await (const [key, value] of this.db.iterator({
      gte: ACCOUNT_PREFIX,
      lt: ACCOUNT_PREFIX_END
    })) {
      entries.push({ key: key.slice(ACCOUNT_PREFIX.length), value });
    }
    return computeStateRoot(entries);
  }
  /**
   * Returns the number of accounts currently stored. O(n) — iterates the
   * full account range.
   */
  async accountCount() {
    let count = 0;
    for await (const _ of this.db.iterator({
      gte: ACCOUNT_PREFIX,
      lt: ACCOUNT_PREFIX_END,
      values: false
    })) {
      count++;
    }
    return count;
  }
  // --- Contract code storage -----------------------------------------------
  /**
   * Store contract source code keyed by its hash. Content-addressed: the
   * same source deployed twice shares storage.
   */
  async putCode(codeHash, source) {
    if (codeHash.length !== 32) {
      throw new StateError(`code hash must be 32 bytes, got ${codeHash.length}`);
    }
    const key = codeKey(codeHash);
    try {
      await this.db.put(key, source);
    } catch (err) {
      throw new StateError("putCode failed", { cause: err });
    }
  }
  /**
   * Retrieve contract source by its hash. Returns `null` if unknown.
   */
  async getCode(codeHash) {
    if (codeHash.length !== 32) {
      throw new StateError(`code hash must be 32 bytes, got ${codeHash.length}`);
    }
    try {
      const bytes = await this.db.get(codeKey(codeHash));
      return bytes instanceof Uint8Array ? new Uint8Array(bytes) : bytes;
    } catch (err) {
      if (isNotFoundError(err)) return null;
      throw new StateError("getCode failed", { cause: err });
    }
  }
  // --- Contract key/value storage -------------------------------------------
  /**
   * Read a single storage slot for a contract. Returns `null` if the key
   * is not set.
   */
  async getStorageValue(contractAddr, key) {
    validateAddress(contractAddr);
    try {
      const bytes = await this.db.get(storageKey(contractAddr, key));
      return new TextDecoder("utf-8").decode(bytes);
    } catch (err) {
      if (isNotFoundError(err)) return null;
      throw new StateError("getStorageValue failed", { cause: err });
    }
  }
  /**
   * Write a single storage slot for a contract.
   */
  async setStorageValue(contractAddr, key, value) {
    validateAddress(contractAddr);
    try {
      await this.db.put(
        storageKey(contractAddr, key),
        new TextEncoder().encode(value)
      );
    } catch (err) {
      throw new StateError("setStorageValue failed", { cause: err });
    }
  }
  /**
   * Delete a single storage slot for a contract. No-op if the key is not set.
   */
  async deleteStorageValue(contractAddr, key) {
    validateAddress(contractAddr);
    try {
      await this.db.del(storageKey(contractAddr, key));
    } catch (err) {
      if (isNotFoundError(err)) return;
      throw new StateError("deleteStorageValue failed", { cause: err });
    }
  }
  /**
   * List all storage entries for a contract, in byte-sorted key order.
   */
  async allStorageForContract(contractAddr) {
    validateAddress(contractAddr);
    const prefix = storageRangePrefix(contractAddr);
    const end = incrementLastByte(prefix);
    const results = [];
    const decoder = new TextDecoder("utf-8");
    for await (const [rawKey, rawValue] of this.db.iterator({
      gte: prefix,
      lt: end
    })) {
      const keyStr = decoder.decode(rawKey.slice(prefix.length));
      const valueStr = decoder.decode(rawValue);
      results.push({ key: keyStr, value: valueStr });
    }
    return results;
  }
  /**
   * Compute a deterministic 32-byte root over the contract's storage.
   * Used to populate the contract account's `storageRoot` field after
   * a contract invocation mutates state.
   */
  async computeContractStorageRoot(contractAddr) {
    validateAddress(contractAddr);
    const entries = [];
    const prefix = storageRangePrefix(contractAddr);
    const end = incrementLastByte(prefix);
    for await (const [rawKey, rawValue] of this.db.iterator({
      gte: prefix,
      lt: end
    })) {
      entries.push({
        key: rawKey.slice(prefix.length),
        value: rawValue instanceof Uint8Array ? new Uint8Array(rawValue) : rawValue
      });
    }
    return computeStateRoot(entries);
  }
};
function validateAddress(address) {
  if (address.length !== ADDRESS_BYTES) {
    throw new StateError(
      `Invalid address length: expected ${ADDRESS_BYTES} bytes, got ${address.length}`
    );
  }
}
function accountKey(address) {
  const key = new Uint8Array(ACCOUNT_PREFIX.length + address.length);
  key.set(ACCOUNT_PREFIX, 0);
  key.set(address, ACCOUNT_PREFIX.length);
  return key;
}
function codeKey(codeHash) {
  const key = new Uint8Array(CODE_PREFIX.length + codeHash.length);
  key.set(CODE_PREFIX, 0);
  key.set(codeHash, CODE_PREFIX.length);
  return key;
}
function storageKey(contractAddr, key) {
  const keyBytes = new TextEncoder().encode(key);
  const out = new Uint8Array(
    STORAGE_PREFIX.length + contractAddr.length + 1 + keyBytes.length
  );
  let off = 0;
  out.set(STORAGE_PREFIX, off);
  off += STORAGE_PREFIX.length;
  out.set(contractAddr, off);
  off += contractAddr.length;
  out[off] = STORAGE_KEY_SEP;
  off += 1;
  out.set(keyBytes, off);
  return out;
}
function storageRangePrefix(contractAddr) {
  const out = new Uint8Array(STORAGE_PREFIX.length + contractAddr.length + 1);
  out.set(STORAGE_PREFIX, 0);
  out.set(contractAddr, STORAGE_PREFIX.length);
  out[STORAGE_PREFIX.length + contractAddr.length] = STORAGE_KEY_SEP;
  return out;
}
function incrementLastByte(bytes) {
  const out = new Uint8Array(bytes);
  for (let i = out.length - 1; i >= 0; i--) {
    if (out[i] !== 255) {
      out[i] = (out[i] ?? 0) + 1;
      return out;
    }
    out[i] = 0;
  }
  return new Uint8Array([...out, 0]);
}
function deserializeAccount(bytes) {
  const view = bytes instanceof Uint8Array ? new Uint8Array(bytes) : bytes;
  return AccountSchema.deserialize(view);
}
function isNotFoundError(err) {
  if (err === null || typeof err !== "object") return false;
  const code = err.code;
  return code === "LEVEL_NOT_FOUND";
}
function bytesToHex(bytes) {
  let hex = "0x";
  for (let i = 0; i < bytes.length; i++) {
    hex += (bytes[i] ?? 0).toString(16).padStart(2, "0");
  }
  return hex;
}
export {
  NotFoundError,
  StateError,
  StateStore,
  computeStateRoot
};
//# sourceMappingURL=index.js.map