import type {
  ComposeRun,
  InputSpec,
  MaterialiserInput,
  Precondition,
  SimulationPolicy,
  SweepTo,
} from '@lifi/compose-spec';

import type { Address, InputSchema, InputSpecOf } from '../types.js';

/**
 * Runtime inputs for compiling a flow. Passed to `builder.compile()` or `sdk.request()`.
 *
 * @typeParam T - The flow's input schema, used to type-check `inputs` and `assumptions`.
 */
export interface ComposeRunInput<T extends InputSchema = InputSchema> {
  /**
   * Concrete values for each declared flow input. Resource inputs accept a
   * materialiser or a literal integer amount; scalar inputs accept the matching type.
   */
  readonly inputs: { readonly [K in keyof T]: InputSpecOf<T[K]> };
  /** Optional preconditions that must hold before the flow executes (e.g. minimum balances). */
  readonly preconditions?: readonly Precondition[];
  /** The `0x`-prefixed address of the transaction signer. */
  readonly signer: Address;
  /**
   * Optional assumed token amounts for resource inputs, keyed by input name.
   * When a materialiser resolves amounts at execution time rather than upfront,
   * assumptions let the compiler plan routes using estimated values. The keys
   * must match the input names declared in the flow schema.
   */
  readonly assumptions?: { readonly [K in keyof T]?: bigint };
  /** Optional integrator referrer identifier. */
  readonly referrer?: string;
  /**
   * Optional integrator fee in basis points (1bp = 0.01%, max `9000` = 90%).
   * A non-zero value requires the request to be authenticated with an
   * integration-scoped API key — the integration is derived from the key, not
   * this field. Defaults to `0` (no fee) when omitted.
   */
  readonly integratorFeeBps?: number;
  /** Maximum acceptable price impact in basis points (e.g. `300` = 3%). */
  readonly maxPriceImpactBps?: number;
  /**
   * Destination for sweeping all proxy-held terminal resources after execution.
   * Pass a literal `0x`-prefixed address or `{ $ref: "context.sender" }` to
   * send remaining tokens to the signer.
   */
  readonly sweepTo?: SweepTo;
  /**
   * Controls behavior when simulation detects a revert.
   * `'strict'` (default) returns an error; `'allow-revert'` returns a partial
   * result with the transaction and revert diagnostics.
   */
  readonly simulationPolicy?: SimulationPolicy;
  /**
   * When `true`, the server checks on-chain ERC-20 allowances and omits
   * approvals that are already sufficient. Defaults to `false`.
   */
  readonly checkOnChainAllowances?: boolean;
}

/**
 * Creates an untyped materialiser input spec. This is a low-level escape hatch
 * for materialiser kinds not yet covered by the generated helpers.
 *
 * Prefer the typed helpers in the `materialisers` namespace (e.g.
 * `materialisers.balanceOf()`, `materialisers.directDeposit()`) which provide
 * compile-time config validation.
 *
 * @param kind - The materialiser type (e.g. `"balanceOf"`, `"directDeposit"`).
 * @param config - Optional materialiser-specific configuration.
 * @returns A {@link MaterialiserInput} for use in {@link ComposeRunInput.inputs}.
 *
 * @example
 * ```ts
 * const run = {
 *   inputs: { token: materialiser('customKind', { myParam: '0x...' }) },
 *   signer: '0x...',
 * };
 * ```
 */
export const materialiser = (
  kind: string,
  config: Record<string, unknown> = {},
): MaterialiserInput => ({
  kind,
  ...config,
});

/**
 * Assembles a {@link ComposeRun} object from individual parameters.
 * Used internally by the SDK; prefer {@link ComposeRunInput} for external use.
 *
 * @internal
 */
export const buildRun = (run: {
  inputs: Record<string, InputSpec>;
  preconditions?: readonly Precondition[];
  signer: string;
  assumptions?: Record<string, bigint>;
  referrer?: string;
  integratorFeeBps?: number;
  maxPriceImpactBps?: number;
  sweepTo?: SweepTo;
  simulationPolicy?: SimulationPolicy;
  checkOnChainAllowances?: boolean;
}): ComposeRun => ({
  inputs: run.inputs,
  ...(run.preconditions?.length && { preconditions: run.preconditions }),
  signer: run.signer,
  ...(run.assumptions && { assumptions: run.assumptions }),
  ...(run.referrer !== undefined && { referrer: run.referrer }),
  ...(run.integratorFeeBps !== undefined && {
    integratorFeeBps: run.integratorFeeBps,
  }),
  ...(run.maxPriceImpactBps !== undefined && {
    maxPriceImpactBps: run.maxPriceImpactBps,
  }),
  ...(run.sweepTo !== undefined && { sweepTo: run.sweepTo }),
  ...(run.simulationPolicy !== undefined && {
    simulationPolicy: run.simulationPolicy,
  }),
  ...(run.checkOnChainAllowances !== undefined && {
    checkOnChainAllowances: run.checkOnChainAllowances,
  }),
});
