'use strict';

/**
 * Jupiter quote + swap-build client.
 *
 * HOST NOTE — read before changing the default.
 *
 * The legacy host `quote-api.jup.ag/v6` is GONE (verified 2026-07-20: the
 * connection fails outright, it is not merely a 404). Jupiter has consolidated
 * onto:
 *
 *   - lite-api.jup.ag/swap/v1  — keyless, free, reduced rate limits. The
 *                                default here. Fine for dry runs, testing, and
 *                                low poll rates. Jupiter has signalled this
 *                                host is eventually deprecated.
 *   - api.jup.ag/swap/v1       — production host. Jupiter's own migration
 *                                guidance is to use this WITH an API key.
 *
 * Set JUPITER_API_BASE + JUPITER_API_KEY to move to the production host. This
 * is a real, recurring, third-party dependency and potentially a real cost —
 * documented plainly in .env.example rather than buried, in the same spirit as
 * the Jito tip disclosure (TECH_SPEC_V5 §2).
 */

const DEFAULT_BASE = 'https://lite-api.jup.ag/swap/v1';

class JupiterError extends Error {
  constructor(message, { status = null, body = null } = {}) {
    super(message);
    this.name = 'JupiterError';
    this.status = status;
    this.body = body;
  }
}

function createJupiterClient(env = process.env, { fetchImpl = globalThis.fetch } = {}) {
  const base = (env.JUPITER_API_BASE || DEFAULT_BASE).replace(/\/+$/, '');
  const apiKey = env.JUPITER_API_KEY ? env.JUPITER_API_KEY.trim() : null;
  const timeoutMs = Number(env.JUPITER_TIMEOUT_MS || 10000);

  function headers(extra = {}) {
    const h = { Accept: 'application/json', ...extra };
    if (apiKey) h['x-api-key'] = apiKey;
    return h;
  }

  async function request(url, options = {}) {
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), timeoutMs);
    try {
      const res = await fetchImpl(url, { ...options, signal: controller.signal });
      const text = await res.text();
      if (!res.ok) {
        throw new JupiterError(`Jupiter responded ${res.status}`, {
          status: res.status,
          body: text.slice(0, 500),
        });
      }
      try {
        return JSON.parse(text);
      } catch {
        throw new JupiterError('Jupiter returned a non-JSON response', {
          status: res.status,
          body: text.slice(0, 500),
        });
      }
    } catch (err) {
      if (err.name === 'AbortError') {
        throw new JupiterError(`Jupiter request timed out after ${timeoutMs}ms`);
      }
      throw err;
    } finally {
      clearTimeout(timer);
    }
  }

  /**
   * One leg. `amount` is a base-unit STRING — never a float, never rounded.
   * Returns Jupiter's quote object verbatim; callers must not reformat it
   * (the same object is handed back to /swap unchanged).
   */
  async function getQuote({ inputMint, outputMint, amount, slippageBps }) {
    const params = new URLSearchParams({
      inputMint,
      outputMint,
      amount: String(amount),
      slippageBps: String(slippageBps),
    });
    return request(`${base}/quote?${params}`, { headers: headers() });
  }

  /**
   * Builds the swap transaction. The returned base64 transaction is Jupiter's
   * own construction and must be signed but NEVER modified — NFR-14 forbids
   * altering it (e.g. to inject a Jito tip); the tip is always a separate
   * transaction in the same bundle.
   */
  async function buildSwapTransaction({ quoteResponse, userPublicKey, priorityLamports }) {
    const body = {
      quoteResponse,
      userPublicKey,
      wrapAndUnwrapSol: true,
    };
    if (priorityLamports != null) {
      body.prioritizationFeeLamports = priorityLamports;
    }
    const json = await request(`${base}/swap`, {
      method: 'POST',
      headers: headers({ 'Content-Type': 'application/json' }),
      body: JSON.stringify(body),
    });
    if (!json.swapTransaction) {
      throw new JupiterError('Jupiter /swap response contained no swapTransaction');
    }
    return json;
  }

  return { getQuote, buildSwapTransaction, base, hasApiKey: Boolean(apiKey) };
}

module.exports = { createJupiterClient, JupiterError, DEFAULT_BASE };
