import type { HttpContractConfig } from "./types.js";

type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type ToNumber<S extends string> = S extends `${infer N extends number}`
  ? N
  : never;
type Any2xxCode = ToNumber<`2${Digit}${Digit}`>;
type NumericKey<K> = K extends number
  ? K
  : K extends `${infer N extends number}`
    ? N
    : never;

type UnionToIntersection<T> = (
  T extends unknown
    ? (value: T) => void
    : never
) extends (value: infer I) => void
  ? I
  : never;

/**
 * All declared 2xx status codes from a contract `responses` record, as
 * numeric literal keys.
 */
export type Success2xxKeys<TResponses extends object> = {
  [K in keyof TResponses]: NumericKey<K> extends Any2xxCode
    ? NumericKey<K>
    : never;
}[keyof TResponses];

/**
 * Whether a contract `responses` record declares exactly one 2xx status.
 */
export type IsSingle2xx<TResponses extends object> = [
  Success2xxKeys<TResponses>,
] extends [never]
  ? false
  : [Success2xxKeys<TResponses>] extends [
        UnionToIntersection<Success2xxKeys<TResponses>>,
      ]
    ? true
    : false;

/**
 * List the declared 2xx statuses of a contract at runtime.
 */
export function getSuccess2xxStatuses(
  contract: Pick<HttpContractConfig, "responses">,
): number[] {
  return Object.keys(contract.responses)
    .map((status) => Number(status))
    .filter(
      (status) => Number.isInteger(status) && status >= 200 && status < 300,
    )
    .sort((a, b) => a - b);
}

/**
 * Resolve the sole declared 2xx status of a contract, or `undefined` when the
 * contract declares zero or multiple 2xx statuses.
 */
export function inferSoleSuccessStatus(
  contract: Pick<HttpContractConfig, "responses">,
): number | undefined {
  const statuses = getSuccess2xxStatuses(contract);
  return statuses.length === 1 ? statuses[0] : undefined;
}
