import type {
  ComposeCompileRequest,
  ComposeCompileResult,
  InputSpec,
} from '@lifi/compose-spec';

import {
  createFlowBuilderCore,
  type FlowBuilderCore,
  type FlowOptions,
  type TypedFlow,
} from './authoring/FlowBuilderCore.js';
import { type ComposeClient, createComposeClient } from './client.js';
import {
  bindGeneratedOps,
  type GeneratedOps,
} from './generated/operations.generated.js';
import { buildRun, type ComposeRunInput } from './run/inputs.js';
import type { InputSchema } from './types.js';

export type { ComposeRunInput };

/**
 * A flow builder augmented with generated operation methods and a `compile` method
 * that submits the flow to the Compose backend for compilation.
 *
 * Created via {@link ComposeSdk.flow}.
 *
 * @typeParam T - The input schema describing the flow's required inputs.
 */
export type FlowBuilder<T extends InputSchema = InputSchema> =
  FlowBuilderCore<T> &
    GeneratedOps & {
      /**
       * Builds the flow document from the current builder state and submits it
       * to the Compose API for compilation.
       *
       * @param run - Runtime inputs, preconditions, and signer address.
       * @returns The compiled transaction calldata and metadata.
       * @throws {@link ComposeError} on network, validation, or server errors.
       *
       * @example
       * ```ts
       * const result = await builder.compile({
       *   inputs: { token: materialisers.balanceOf({}) },
       *   signer: '0xYourAddress...',
       * });
       * console.log(result.calldata);
       * ```
       */
      readonly compile: (
        run: ComposeRunInput<T>,
      ) => Promise<ComposeCompileResult>;
    };

/**
 * Configuration for creating a Compose SDK instance.
 */
export interface ComposeSdkOptions {
  /** Base URL of the Compose API (e.g. `"https://li.quest"`). */
  readonly baseUrl: string;
  /** Optional custom `fetch` implementation. Defaults to `globalThis.fetch`. */
  readonly fetch?: typeof globalThis.fetch;
  /** Optional LI.FI API key for authenticated access. Sent as the `x-lifi-api-key` header on every request. */
  readonly apiKey?: string;
}

/**
 * The top-level Compose SDK interface.
 *
 * Provides methods to build flows, compile them into executable calldata, and
 * interact with the Compose API directly.
 *
 * @example
 * ```ts
 * import { createComposeSdk, resources, materialisers } from '@lifi/composer-sdk';
 *
 * const sdk = createComposeSdk({ baseUrl: 'https://li.quest' });
 *
 * const builder = sdk.flow(1, {
 *   inputs: { usdc: resources.erc20('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 1) },
 * });
 *
 * builder.lifi.swap('swap1', {
 *   bind: { amountIn: builder.inputs.usdc },
 *   config: { resourceOut: resources.native(1) },
 * });
 *
 * const result = await builder.compile({
 *   inputs: { usdc: materialisers.balanceOf({}) },
 *   signer: '0xYourAddress...',
 * });
 * ```
 */
export interface ComposeSdk {
  /** Low-level HTTP client for the Compose API. */
  readonly client: ComposeClient;
  /**
   * Creates a new flow builder targeting the given chain.
   * @param chainId - The EVM chain ID (e.g. `1` for Ethereum mainnet).
   * @param options - Flow configuration including input declarations.
   * @returns A {@link FlowBuilder} with generated operation methods and a `compile` method.
   * @throws Error if any resource input's `chainId` doesn't match the flow's `chainId`.
   */
  readonly flow: <T extends InputSchema>(
    chainId: number,
    options: FlowOptions<T>,
  ) => FlowBuilder<T>;
  /**
   * Builds a raw compile request without sending it. Useful for inspecting the
   * request payload or submitting it through a custom transport.
   * @param flow - A built flow document (from `builder.build()`).
   * @param run - Runtime inputs, preconditions, and signer address.
   * @returns A {@link ComposeCompileRequest} ready to send to the Compose API.
   */
  readonly request: <T extends InputSchema>(
    flow: TypedFlow<T>,
    run: ComposeRunInput<T>,
  ) => ComposeCompileRequest;
}

/**
 * Creates a new Compose SDK instance.
 *
 * @param options - SDK configuration including the API base URL and optional fetch implementation.
 * @returns A {@link ComposeSdk} instance.
 *
 * @example
 * ```ts
 * const sdk = createComposeSdk({ baseUrl: 'https://li.quest' });
 * ```
 */
export const createComposeSdk = (options: ComposeSdkOptions): ComposeSdk => {
  const composeClient = createComposeClient(options);

  const request = <T extends InputSchema>(
    flowDoc: TypedFlow<T>,
    run: ComposeRunInput<T>,
  ): ComposeCompileRequest => ({
    flow: flowDoc,
    run: buildRun({
      inputs: run.inputs as Record<string, InputSpec>,
      preconditions: run.preconditions,
      signer: run.signer,
      assumptions: run.assumptions as Record<string, bigint> | undefined,
      referrer: run.referrer,
      integratorFeeBps: run.integratorFeeBps,
      maxPriceImpactBps: run.maxPriceImpactBps,
      sweepTo: run.sweepTo,
      simulationPolicy: run.simulationPolicy,
      checkOnChainAllowances: run.checkOnChainAllowances,
    }),
  });

  const flow = <T extends InputSchema>(
    chainId: number,
    opts: FlowOptions<T>,
  ): FlowBuilder<T> => {
    const core = createFlowBuilderCore(chainId, opts);
    const builder = Object.assign(core, bindGeneratedOps(core));

    const compile = async (
      run: ComposeRunInput<T>,
    ): Promise<ComposeCompileResult> => {
      const flowDoc = builder.build();
      const req = request(flowDoc, run);
      return composeClient.compile(req);
    };

    return Object.assign(builder, { compile });
  };

  return { client: composeClient, flow, request };
};
