import { type WalletState } from "./wallet.js"; /** * A price tag: how much of a free-string `currency` an entry costs (buy) or returns (sell). * `amount` is a non-negative finite number; `0` means free (no wallet charge/grant happens). */ export interface ShopPrice { /** Free-string currency key the game owns — matches a {@link WalletState} balance key. Never interpreted. */ currency: string; /** Non-negative finite quantity of `currency`. */ amount: number; } /** One item a vendor stocks. Fully serializable; `kind` and `currency` are free strings the model never branches on. */ export interface ShopStockEntry { /** Stable unique id within the shop. */ id: string; /** Free-string label the game owns and styles ("potion", "blade", "charm", …). The model never interprets it. */ kind: string; /** What it costs to buy one. */ price: ShopPrice; /** Units on hand: a non-negative integer, or `null` for unlimited (never decrements, never sells out). */ qty: number | null; /** Optional buy-back price paid to the wallet on {@link ShopStock.sell}. Omitted means the item is not sellable. */ sellPrice?: ShopPrice; } /** Config for {@link createShopStock}: the initial set of entries. */ export interface ShopStockConfig { /** Initial stock. Ids must be unique; a duplicate id throws. */ entries?: readonly ShopStockEntry[]; } /** Why a {@link ShopStock.buy} was refused. */ export type ShopBuyRejection = "unknown-item" | "out-of-stock" | "insufficient-funds"; /** Why a {@link ShopStock.sell} was refused. */ export type ShopSellRejection = "unknown-item" | "not-sellable"; /** * Result of {@link ShopStock.buy}: on success the caller receives the debited `wallet` to adopt and a * snapshot of the purchased `entry` (post-decrement); on failure a `reason` and the wallet is untouched. */ export type ShopBuyResult = { ok: true; wallet: WalletState; entry: ShopStockEntry; } | { ok: false; reason: ShopBuyRejection; }; /** * Result of {@link ShopStock.sell}: on success the caller receives the credited `wallet` and a snapshot * of the sold `entry` (post-restock); on failure a `reason` and the wallet is untouched. */ export type ShopSellResult = { ok: true; wallet: WalletState; entry: ShopStockEntry; } | { ok: false; reason: ShopSellRejection; }; /** Serializable state of a shop's stock, for save/restore. */ export interface ShopStockSnapshot { entries: readonly ShopStockEntry[]; } /** A live, observable vendor/shop stock operating over a caller-owned wallet. */ export interface ShopStock { /** * Buy one unit of `id` against `wallet` (the shop never owns the wallet). Debits the entry's `price` * via {@link charge}, decrements a finite `qty`, and returns the new wallet to adopt plus the entry * snapshot. Rejects `"unknown-item"`, `"out-of-stock"` (finite qty at 0), or `"insufficient-funds"` * — the passed wallet is never mutated. */ buy(id: string, wallet: WalletState): ShopBuyResult; /** * Sell one unit of `id` back to the vendor, crediting the entry's `sellPrice` to `wallet` via * {@link grant} and incrementing a finite `qty`. Rejects `"unknown-item"` or `"not-sellable"` * (no `sellPrice`). The passed wallet is never mutated. */ sell(id: string, wallet: WalletState): ShopSellResult; /** Whether `wallet` can currently afford one unit of `id` (false for unknown or sold-out items). */ canAfford(id: string, wallet: WalletState): boolean; /** Add `n` units to a finite-`qty` entry (no-op for unlimited stock). Notifies subscribers. */ restock(id: string, n: number): void; /** Replace an entry's buy `price`. Notifies subscribers. */ setPrice(id: string, price: ShopPrice): void; /** Insert a new entry. Throws if its id already exists. Notifies subscribers. */ add(entry: ShopStockEntry): void; /** Remove an entry by id (no-op if absent). Notifies subscribers. */ remove(id: string): void; /** A snapshot array of every entry, in insertion order (safe to retain — copies, not live records). */ list(): ShopStockEntry[]; /** A snapshot of one entry, or `null` if unknown (safe to retain). */ get(id: string): ShopStockEntry | null; /** Observe changes (buy, sell, restock, setPrice, add, remove, restore). Returns an unsubscribe fn. */ subscribe(listener: () => void): () => void; /** Serializable state for a save. */ snapshot(): ShopStockSnapshot; /** Restore from a {@link ShopStockSnapshot}, replacing all entries. Notifies subscribers. */ restore(snapshot: ShopStockSnapshot): void; } /** * A serializable, observable vendor/shop stock. Each entry carries a game-owned free-string `kind`, a * `price` in a free-string `currency`, a finite or unlimited (`qty: null`) count, and an optional * `sellPrice` buy-back. All currency math is delegated to the existing pure `wallet` model — `buy` * charges and `sell` grants a **caller-owned** {@link WalletState} (the shop never holds the wallet, it * returns the new wallet for the caller to adopt), so the same stock composes with any wallet the game * owns. Nothing here is genre-specific: `kind` and `currency` are free labels the game styles and the * model never branches on. `restock`/`setPrice`/`add`/`remove` mutate the catalog, `subscribe` observes * every change, and `snapshot`/`restore` round-trips the whole stock through a save. * * @capability shop-grid serializable vendor/shop stock with buy/sell over a caller-owned wallet — finite/unlimited qty, sell-back, restock/setPrice, snapshot/restore */ export declare function createShopStock(config?: ShopStockConfig): ShopStock;