import type { SimulationRevert } from './compile.js';
import type { FlowInput, ResourceInput, SolType } from './flowSchema.js';
import type { Availability, Resource } from './resource.js';
import type {
  HandlePort,
  OpInputPort,
  OpOutputPort,
  ResourceOutputPort,
  ResourcePort,
} from './zodSchemas.js';

export type {
  HandlePort,
  OpInputPort,
  OpOutputPort,
  ResourceOutputPort,
  ResourcePort,
} from './zodSchemas.js';

export interface Port {
  readonly name: string;
  readonly type: SolType;
  readonly mode: 'linear' | 'copy';
  readonly availability?: Availability;
  readonly resource?: Resource;
}

export const isResourceInput = (input: FlowInput): input is ResourceInput =>
  'resource' in input;

export const flowInputType = (input: FlowInput): SolType =>
  isResourceInput(input) ? 'uint256' : input.type;

export const flowInputMode = (input: FlowInput): 'linear' | 'copy' =>
  isResourceInput(input) ? 'linear' : 'copy';

export const flowInputResource = (input: FlowInput): Resource | undefined =>
  isResourceInput(input) ? input.resource : undefined;

export const isResourcePort = (port: OpInputPort): port is ResourcePort =>
  port.kind === 'resource';

export const isHandlePort = (
  port: OpInputPort | OpOutputPort,
): port is HandlePort => port.kind === 'handle';

export const isResourceOutputPort = (
  port: OpInputPort | OpOutputPort,
): port is ResourceOutputPort => port.kind === 'resource_output';

// A resource output whose value becomes available only after the initiating
// transaction settles. Absence of `availability` means `'now'`, so the check is
// a direct comparison against the one non-default mode. Every site that asks
// "is this port's value delivered later?" goes through here, so a rename of the
// property or a new availability mode lands in one place.
export const isFutureResourceOutput = (
  port: OpInputPort | OpOutputPort,
): boolean => isResourceOutputPort(port) && port.availability === 'future';

export type ComposeErrorKind =
  | 'decode_error'
  | 'validation_error'
  | 'linearity_error'
  | 'resolve_error'
  | 'preparation_error'
  | 'provider_error'
  | 'no_route_error'
  | 'resolution_error'
  | 'resolution_rejected'
  | 'lowering_error'
  | 'empty_program'
  | 'simulation_error'
  | 'simulation_setup_error'
  | 'simulation_insufficient_funds'
  | 'simulation_gas_limit'
  | 'simulation_rpc_error'
  | 'simulation_revert'
  | 'subgraph_simulation_revert'
  | 'guard_error'
  | 'continuation_error'
  | 'compilation_error'
  | 'verification_error'
  | 'price_impact_exceeded'
  | 'fee_resolution_error'
  | 'fee_resolution_unavailable'
  | 'flashloan_unauthorized'
  // The request exceeded its wall-clock budget and was cancelled mid-flight.
  | 'deadline_exceeded';

export type GenericComposeErrorKind = Exclude<
  ComposeErrorKind,
  'preparation_error' | 'simulation_revert'
>;

export interface FailedPreparedOp {
  readonly callId: string;
  readonly op: string;
  readonly kind: GenericComposeErrorKind;
  readonly message: string;
  readonly path?: string;
}

interface ComposeErrorBase {
  readonly message: string;
  readonly path?: string;
}

export interface PreparationComposeError extends ComposeErrorBase {
  readonly kind: 'preparation_error';
  readonly failedOps: readonly FailedPreparedOp[];
  readonly succeededOps: readonly string[];
}

export interface SimulationRevertComposeError extends ComposeErrorBase {
  readonly kind: 'simulation_revert';
  readonly details?: SimulationRevert;
}

export interface GenericComposeError extends ComposeErrorBase {
  readonly kind: GenericComposeErrorKind;
  readonly [key: string]: unknown;
}

export type ComposeError =
  PreparationComposeError | SimulationRevertComposeError | GenericComposeError;

export interface MaterialiserInputBase {
  readonly kind: string;
}

export type MaterialiserInput = MaterialiserInputBase & Record<string, unknown>;

export type MaterialiserInputOf<C extends object> = MaterialiserInputBase & C;

export type MaterialiserConfigOf<C extends object> = C;

export const isMaterialiserInput = (s: InputSpec): s is MaterialiserInput =>
  typeof s === 'object' && s !== null && 'kind' in s;

export type InputSpec = bigint | string | MaterialiserInput;

export interface PreconditionBase {
  readonly type: string;
}

export type Precondition = PreconditionBase & Record<string, unknown>;

export type PreconditionOf<C extends object> = PreconditionBase & C;

export type PreconditionConfigOf<C extends object> = Omit<
  PreconditionBase,
  'type'
> &
  C;
