// Node-runtime patch loaded via `node --require <this>` before 9router boots.
// Rewrites `max_tokens` to `max_completion_tokens` for GPT-5 calls (which 9router still emits) and floors completion tokens at 32K for reasoning headroom.
// Hostname-gated to api.openai.com; every step is try/catch so failure falls back to baseline behavior.

'use strict';

const _https = require('https');
const _http = require('http');

const TARGET_HOSTS = new Set(['api.openai.com']);
const DEBUG = process.env.OPENSWARM_DEBUG_GPT5_PATCH === '1';

function _log(msg) {
  if (DEBUG) {
    try { process.stderr.write('[openswarm-gpt5-patch] ' + msg + '\n'); } catch (_) {}
  }
}

function isGpt5Model(model) {
  if (typeof model !== 'string') return false;
  let m = model.trim().toLowerCase();
  if (!m) return false;
  // Strip 9router prefixes; don't blindly strip cp- (could be a non-OpenAI custom node).
  const prefixes = ['cp-openai/', 'openai/', 'cx/', 'openrouter/', 'or:openai/'];
  for (const p of prefixes) {
    if (m.startsWith(p)) { m = m.slice(p.length); break; }
  }
  return m.startsWith('gpt-5');
}

// GPT-5 burns 8-30K reasoning tokens before any output; the CLI's default 4096 caps before content lands. Floor at 32K and only raise, never lower.
const GPT5_MIN_COMPLETION_TOKENS = 32768;

function maybeRewriteBody(bodyStr) {
  if (typeof bodyStr !== 'string' || bodyStr.length === 0) return bodyStr;
  let parsed;
  try { parsed = JSON.parse(bodyStr); } catch { return bodyStr; }
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return bodyStr;
  if (!isGpt5Model(parsed.model)) return bodyStr;
  let mutated = false;
  // Drop legacy field if both present, else OpenAI 400s on "both specified".
  if ('max_tokens' in parsed && 'max_completion_tokens' in parsed) {
    delete parsed.max_tokens;
    mutated = true;
    _log('dropped redundant max_tokens for ' + parsed.model);
  } else if ('max_tokens' in parsed) {
    parsed.max_completion_tokens = parsed.max_tokens;
    delete parsed.max_tokens;
    mutated = true;
    _log('rewrote max_tokens to max_completion_tokens for ' + parsed.model);
  }
  if (typeof parsed.max_completion_tokens === 'number' && parsed.max_completion_tokens < GPT5_MIN_COMPLETION_TOKENS) {
    const orig = parsed.max_completion_tokens;
    parsed.max_completion_tokens = GPT5_MIN_COMPLETION_TOKENS;
    mutated = true;
    _log('raised max_completion_tokens ' + orig + ' to ' + GPT5_MIN_COMPLETION_TOKENS + ' for ' + parsed.model);
  }
  return mutated ? JSON.stringify(parsed) : bodyStr;
}

function _hostFromOpts(opts) {
  if (!opts) return '';
  const raw = opts.hostname || opts.host || '';
  return String(raw).replace(/:\d+$/, '').toLowerCase();
}

function patchHttpRequest(orig) {
  return function patchedRequest() {
    const args = Array.prototype.slice.call(arguments);
    let opts = args[0];
    let host = '';
    try {
      if (typeof opts === 'string') host = new URL(opts).hostname.toLowerCase();
      else if (opts instanceof URL) host = opts.hostname.toLowerCase();
      else host = _hostFromOpts(opts);
    } catch (_) { host = ''; }

    if (!TARGET_HOSTS.has(host)) {
      return orig.apply(this, args);
    }

    let req;
    try { req = orig.apply(this, args); } catch (e) { throw e; }
    const origWrite = req.write.bind(req);
    const origEnd = req.end.bind(req);
    const chunks = [];
    let isStringMode = null;

    function recordChunk(chunk) {
      if (chunk == null) return;
      if (typeof chunk === 'string') {
        if (isStringMode === false) {
          for (let i = 0; i < chunks.length; i++) chunks[i] = chunks[i].toString('utf8');
        }
        isStringMode = true;
        chunks.push(chunk);
      } else if (Buffer.isBuffer(chunk)) {
        if (isStringMode === true) {
          for (let i = 0; i < chunks.length; i++) chunks[i] = Buffer.from(chunks[i], 'utf8');
        }
        isStringMode = false;
        chunks.push(chunk);
      } else {
        throw new Error('unknown-chunk-shape');
      }
    }

    req.write = function patchedWrite(chunk) {
      const restArgs = Array.prototype.slice.call(arguments, 1);
      try {
        recordChunk(chunk);
        return true;
      } catch (_) {
        try {
          for (const c of chunks) origWrite(c);
          chunks.length = 0;
        } catch (_) {}
        return origWrite.apply(req, [chunk].concat(restArgs));
      }
    };

    req.end = function patchedEnd(chunk) {
      const restArgs = Array.prototype.slice.call(arguments, 1);
      try {
        recordChunk(chunk);
        let bodyStr = '';
        if (isStringMode === true) bodyStr = chunks.join('');
        else if (isStringMode === false) bodyStr = Buffer.concat(chunks).toString('utf8');
        const rewritten = maybeRewriteBody(bodyStr);
        if (rewritten !== bodyStr) {
          const newBuf = Buffer.from(rewritten, 'utf8');
          try {
            if (req.getHeader && typeof req.getHeader === 'function' && req.getHeader('content-length')) {
              req.setHeader('Content-Length', newBuf.length);
            }
          } catch (_) {}
          return origEnd.call(req, newBuf);
        }
        if (chunks.length === 0) return origEnd.apply(req, restArgs);
        if (isStringMode === true) return origEnd.call(req, chunks.join(''));
        return origEnd.call(req, Buffer.concat(chunks));
      } catch (_) {
        try {
          for (const c of chunks) origWrite(c);
          chunks.length = 0;
        } catch (_) {}
        if (chunk != null) return origEnd.apply(req, [chunk].concat(restArgs));
        return origEnd.apply(req, restArgs);
      }
    };

    return req;
  };
}

if (!_https.__openswarm_gpt5_patched) {
  try {
    _https.request = patchHttpRequest(_https.request);
    _http.request = patchHttpRequest(_http.request);
    _https.__openswarm_gpt5_patched = true;
    _http.__openswarm_gpt5_patched = true;
    _log('installed https.request + http.request interceptors');
  } catch (e) {
    _log('install failed: ' + (e && e.message ? e.message : String(e)));
  }
}

// Node 18+ fetch path; 9router uses fetch in some routes.
if (typeof globalThis.fetch === 'function' && !globalThis.fetch.__openswarm_gpt5_patched) {
  try {
    const origFetch = globalThis.fetch;
    const patchedFetch = async function (input, init) {
      try {
        let url = '';
        if (typeof input === 'string') url = input;
        else if (input && typeof input === 'object') url = input.url || '';
        if (!url) return origFetch.call(this, input, init);
        let host = '';
        try { host = new URL(url).hostname.toLowerCase(); } catch (_) { return origFetch.call(this, input, init); }
        if (!TARGET_HOSTS.has(host)) return origFetch.call(this, input, init);
        if (init && typeof init.body === 'string') {
          const rewritten = maybeRewriteBody(init.body);
          if (rewritten !== init.body) {
            const newInit = Object.assign({}, init, { body: rewritten });
            const newLen = String(Buffer.byteLength(rewritten, 'utf8'));
            if (newInit.headers) {
              try {
                if (typeof Headers !== 'undefined' && newInit.headers instanceof Headers) {
                  if (newInit.headers.has('content-length')) newInit.headers.set('content-length', newLen);
                } else {
                  for (const k of Object.keys(newInit.headers)) {
                    if (k.toLowerCase() === 'content-length') newInit.headers[k] = newLen;
                  }
                }
              } catch (_) {}
            }
            return origFetch.call(this, input, newInit);
          }
        }
        return origFetch.call(this, input, init);
      } catch (_) {
        return origFetch.call(this, input, init);
      }
    };
    patchedFetch.__openswarm_gpt5_patched = true;
    globalThis.fetch = patchedFetch;
    _log('installed fetch interceptor');
  } catch (e) {
    _log('fetch install failed: ' + (e && e.message ? e.message : String(e)));
  }
}
