/**
 * Canonical wire-format types for the standalone `POST /simulate` endpoint.
 *
 * `/simulate` takes a raw, pre-encoded transaction (a `to` address, hex `data`,
 * optional native `value`), funds a sender, runs it in one `eth_call`, and
 * reports the watched balances before and after, their signed deltas, and the
 * inner-call gas. Unlike `POST /compose`, its response body is **un-enveloped**:
 * `{ status, block, timestamp, ... }` lives at the top level (no `{ data }`
 * wrapper).
 *
 * These are the single source of truth for the request/response shapes. The
 * server-side Zod schemas in `@lifi/api-schemas` (`src/routes/simulate.ts`)
 * validate the same contract and are kept in lockstep by a compile-time
 * conformance assertion (`src/routes/simulate.typecheck.ts`).
 *
 * Mirrors the hand-authored style of `src/compile.ts`.
 */

/**
 * Maximum number of watched `(token, owner)` pairs accepted by `POST /simulate`.
 *
 * The limit comes from the VM's hard register budget (~123 usable registers,
 * checked on the final ISA layout). Each pair costs two result registers — a
 * before-read and an after-read — that are both live at the closing abi-encode,
 * so the compiler cannot coalesce them; that `2 × N` is the cost that scales.
 * Token and owner address literals do not scale it, since constant-propagation
 * merges identical-valued literals into one register. The cap leaves headroom
 * for the inner-call / gas-measurement / abi-encode scaffolding.
 */
export const SIMULATE_MAX_TRACKED_BALANCES = 40;

/**
 * Maximum number of funding `requirements` accepted by `POST /simulate`.
 *
 * Each requirement triggers a slot-discovery cycle (access-list probe + sentinel
 * verification + RPC round trips) in slot-finder, so an unbounded array lets a
 * single request amplify into arbitrary upstream RPC work. Bounded here at the
 * trust boundary and re-enforced server-side in `rawSimulation.ts`.
 */
export const SIMULATE_MAX_REQUIREMENTS = 40;

/** A `(token, owner)` pair whose balance is watched across the simulation. */
export interface TrackedBalance {
  /** Token to watch (use the zero address for native balance). */
  readonly token: string;
  /** Account whose balance of `token` is watched. */
  readonly owner: string;
}

/**
 * Funding instruction: seed an ERC-20 balance on a wallet before simulation.
 * Amounts accept `bigint | string` — the SDK serialises `bigint` to a decimal
 * string; the string side covers values already stringified.
 */
export interface Erc20BalanceRequirement {
  readonly type: "Erc20Balance";
  readonly wallet: string;
  readonly token: string;
  readonly balance: bigint | string;
}

/** Funding instruction: seed a native balance on a wallet before simulation. */
export interface NativeBalanceRequirement {
  readonly type: "NativeBalance";
  readonly wallet: string;
  readonly balance: bigint | string;
}

/** Funding instruction: seed an ERC-20 allowance before simulation. */
export interface Erc20AllowanceRequirement {
  readonly type: "Erc20Allowance";
  readonly owner: string;
  readonly spender: string;
  readonly token: string;
  readonly allowance: bigint | string;
}

/**
 * The funding-instruction union applied before a simulation. Three variants,
 * discriminated by `type`.
 */
export type SlotFinderRequirement =
  | Erc20BalanceRequirement
  | NativeBalanceRequirement
  | Erc20AllowanceRequirement;

/** Request body for `POST /simulate`. */
export interface SimulateRequest {
  /** EVM chain id. */
  readonly chainId: number;
  /**
   * Sender of the simulated transaction. The VM bytecode is injected here so
   * the inner call carries `msg.sender == from`.
   */
  readonly from: string;
  /** Target contract of the raw transaction. */
  readonly to: string;
  /** Pre-encoded transaction calldata (`0x`-prefixed hex). */
  readonly data: string;
  /**
   * Native value (wei) sent with the inner call. Accepts `bigint | string`;
   * defaults to `"0"` server-side when omitted.
   */
  readonly value?: bigint | string;
  /**
   * Block number to simulate against. Omitted ⇒ the chain head. Named tags
   * (e.g. "latest") are rejected with HTTP 400.
   */
  readonly block?: number;
  /** Funding instructions applied before simulation (max 40). */
  readonly requirements?: readonly SlotFinderRequirement[];
  /** Balances to read before and after the transaction (1–40 pairs). */
  readonly trackedBalances: readonly TrackedBalance[];
}

/**
 * One watched balance in a simulation response. `amount` is a decimal string;
 * for `deltas` it is signed (`after - before`).
 */
export interface SimulateBalanceEntry {
  readonly token: string;
  readonly owner: string;
  readonly amount: string;
}

/**
 * HTTP 200, `status: "ok"`: the simulation ran successfully. Balance arrays are
 * ordered to match the request `trackedBalances`. `gasUsed` is inner-call
 * execution gas only.
 *
 * Note: the array element types are intentionally mutable (`SimulateBalanceEntry[]`,
 * not `readonly SimulateBalanceEntry[]`) so they stay structurally identical to
 * the Zod-inferred wire types, which `z.array(...)` infers as mutable `T[]`. The
 * bidirectional conformance assertion in `@lifi/api-schemas` relies on this.
 */
export interface SimulateOkResult {
  readonly status: "ok";
  readonly block: number;
  readonly timestamp: number;
  readonly balancesBefore: SimulateBalanceEntry[];
  readonly balancesAfter: SimulateBalanceEntry[];
  readonly deltas: SimulateBalanceEntry[];
  readonly gasUsed: string;
}

/** Decoded revert diagnostics, when slot-finder can parse the revert reason. */
export interface SimulateRevertDecodeResult {
  readonly errorCandidates?: {
    readonly decodedErrorSignature: string;
    readonly decodedParams: string[];
  }[];
  readonly error?: string;
}

/**
 * HTTP 200, `status: "revert"`: the simulation ran but the transaction reverted
 * on-chain. A revert is a *successful simulation*, not a transport error.
 */
export interface SimulateRevertResult {
  readonly status: "revert";
  readonly block: number;
  readonly timestamp: number;
  readonly revertReason?: string;
  readonly code?: number;
  readonly rawErrorBytes?: string;
  readonly decodeResult?: SimulateRevertDecodeResult;
}

/**
 * HTTP 422, `status: "error"`: the request was well-formed but the simulation
 * could not be set up or run. `message` is intentionally generic.
 */
export interface SimulateSetupErrorResult {
  readonly status: "error";
  readonly message: string;
}

/**
 * Discriminated result of `POST /simulate`. `switch (result.status)` narrows to
 * one member: `ok` and `revert` come back with HTTP 200, `error` with HTTP 422.
 */
export type SimulateResult =
  | SimulateOkResult
  | SimulateRevertResult
  | SimulateSetupErrorResult;
