import {
  compareQueryTransportFields,
  formatQueryTransportMismatch,
  getObjectSchemaShape,
} from "./schema-shape.js";
import type { HttpContractConfig } from "./types.js";

/** Assert that a plain contract config declares one transport per query. */
export function assertContractQueryTransport(
  contract: HttpContractConfig,
): void {
  if (contract.query && !contract.queryTransport) {
    throw new Error(
      `Contract "${contract.name}" declares a query schema without a query transport. Pass defineQueryTransport(...) as the second argument to .query(...).`,
    );
  }
  if (!contract.query && contract.queryTransport) {
    throw new Error(
      `Contract "${contract.name}" declares a query transport without a query schema.`,
    );
  }
  if (contract.query && contract.queryTransport) {
    const shape = getObjectSchemaShape(contract.query);
    if (!shape) return;

    const schemaKeys = Object.keys(shape);
    const transportKeys = Object.keys(contract.queryTransport.fields);
    if (!compareQueryTransportFields({ schemaKeys, transportKeys })) {
      throw new Error(
        formatQueryTransportMismatch({
          contractName: contract.name,
          schemaKeys,
          transportKeys,
        }),
      );
    }
  }
}

/**
 * Contract input accepted by APIs that can unwrap builders.
 */
export type ContractLike = HttpContractConfig | { config: HttpContractConfig };

/**
 * Resolve a contract-like type to its plain `HttpContractConfig`.
 */
export type ResolveContract<T> = T extends { config: infer C }
  ? C extends HttpContractConfig
    ? C
    : never
  : T extends HttpContractConfig
    ? T
    : never;

/**
 * Resolve a contract builder or plain config to the underlying contract config.
 *
 * @param c - The contract-like object to resolve
 * @returns The underlying HttpContractConfig
 */
export function resolveContract<T extends ContractLike>(
  c: T,
): ResolveContract<T> {
  const contract = ("config" in c ? c.config : c) as ResolveContract<T>;
  assertContractQueryTransport(contract);
  return contract;
}
