// {{ display_name }} MCP server — generated by mcpify. Do not hand-edit.
//
// Generic operationId -> HTTP request dispatcher (rather than one method
// per operation), so generation stays robust against specs with hundreds
// of operations and this file's size never grows with the spec.

import axios, { type AxiosRequestConfig, type AxiosResponse } from 'axios';

import type { AuthManager } from '../auth/auth-manager.js';
import type { RequestCredentials } from '../auth/request-credentials.js';
import { buildApiUrl } from '../core/api-url-builder.js';
import { CircuitBreaker } from '../core/circuit-breaker.js';
import type { Config } from '../core/config-schema.js';
import { RateLimiter } from '../core/rate-limiter.js';
import type { EndpointRecord } from '../data/store-repository.js';

interface OpenApiParameterLike {
  in?: string;
  name?: string;
}

function parametersOf(endpoint: EndpointRecord): OpenApiParameterLike[] {
  const schema = endpoint.inputSchema as { parameters?: OpenApiParameterLike[] };
  return schema.parameters ?? [];
}

function namesWithLocation(endpoint: EndpointRecord, location: string): string[] {
  return parametersOf(endpoint)
    .filter((parameter) => parameter.in === location && typeof parameter.name === 'string')
    .map((parameter) => parameter.name as string);
}

function applyPathParams(endpoint: EndpointRecord, params: Record<string, unknown>): string {
  return endpoint.path.replace(/\{(\w+)\}/g, (_match, name: string) => encodeURIComponent(String(params[name] ?? '')));
}

function pickByLocation(endpoint: EndpointRecord, params: Record<string, unknown>, location: string): Record<string, string> {
  const picked: Record<string, string> = {};
  for (const name of namesWithLocation(endpoint, location)) {
    if (params[name] !== undefined) picked[name] = String(params[name]);
  }
  return picked;
}

export class ApiClient {
  private readonly circuitBreaker: CircuitBreaker;
  private readonly rateLimiter: RateLimiter;

  constructor(
    private readonly config: Config,
    private readonly authManager: AuthManager,
  ) {
    this.circuitBreaker = new CircuitBreaker();
    this.rateLimiter = new RateLimiter(config.rate_limit);
  }

  /**
   * Executes `endpoint` against the configured target API, retrying
   * transient failures up to `config.retry_attempts` times, all inside the
   * circuit breaker and rate limiter (REQ-2.3.3).
   */
  async execute(
    endpoint: EndpointRecord,
    params: Record<string, unknown>,
    requestOverride?: RequestCredentials,
  ): Promise<AxiosResponse> {
    this.rateLimiter.acquire();

    const query = pickByLocation(endpoint, params, 'query');
    const url = buildApiUrl(this.config.url, applyPathParams(endpoint, params), query);
    const extraHeaders = pickByLocation(endpoint, params, 'header');

    let headers: Record<string, string> = { 'Content-Type': 'application/json', ...extraHeaders };
    headers = await this.authManager.applyAuthHeaders(
      headers,
      endpoint.method,
      url,
      this.config.transport,
      requestOverride,
    );

    return this.circuitBreaker.execute(() => this.dispatch(endpoint.method, url, params.body, headers));
  }

  private async dispatch(
    method: string,
    url: string,
    data: unknown,
    headers: Record<string, string>,
  ): Promise<AxiosResponse> {
    const requestConfig: AxiosRequestConfig = { method, url, data, headers, timeout: this.config.timeout_ms };

    let attempt = 0;
    for (;;) {
      try {
        return await axios.request(requestConfig);
      } catch (error) {
        attempt += 1;
        if (attempt > this.config.retry_attempts) throw error;
      }
    }
  }
}
