'use strict';

const {
  ANTHROPIC_SAMPLING_PARAMS,
  anthropicModelRejectsSamplingParams,
} = require('../llm/request-compat');

const DEFAULT_BODY_LIMIT = 50 * 1024 * 1024;

const PROVIDER_BODY_LIMITS = Object.freeze({
  openai: 100 * 1024 * 1024,
  deepseek: 50 * 1024 * 1024,
  dashscope: 6 * 1024 * 1024,
  qwen: 6 * 1024 * 1024,
  xai: 50 * 1024 * 1024,
  ollama: 100 * 1024 * 1024,
  lmstudio: 100 * 1024 * 1024,
});

const STRIPPED_REASONING_PARAMS = [
  'temperature',
  'top_p',
  'topP',
  'top_k',
  'topK',
  'presencePenalty',
  'frequencyPenalty',
  'frequency_penalty',
  'presence_penalty',
];

function applyProviderCompatibility(request = {}, { provider = request.provider, model = request.model } = {}) {
  const pid = providerId(provider);
  const mid = modelId(model || request.model);
  const next = cloneRequest(request);
  const warnings = [];
  const errors = [];

  if (modelRejectsIsErrorField(mid, pid)) {
    const stripped = stripIsErrorFields(next.messages);
    if (stripped > 0) warnings.push(`Stripped ${stripped} is_error field(s) for ${mid}.`);
  }

  if (isReasoningModel(mid, pid)) {
    for (const key of STRIPPED_REASONING_PARAMS) {
      if (next[key] !== undefined) {
        delete next[key];
        warnings.push(`Removed unsupported reasoning-model parameter: ${key}.`);
      }
    }
  }

  if (pid === 'anthropic' && anthropicModelRejectsSamplingParams(mid)) {
    for (const key of ANTHROPIC_SAMPLING_PARAMS) {
      if (next[key] !== undefined) {
        delete next[key];
        warnings.push(`Removed unsupported Anthropic model parameter: ${key}.`);
      }
    }
  }

  if (usesMaxCompletionTokens(mid, pid) && next.maxTokens !== undefined && next.max_completion_tokens === undefined) {
    next.max_completion_tokens = next.maxTokens;
    warnings.push(`Mapped maxTokens to max_completion_tokens for ${mid}.`);
  }

  const size = requestBodySize(next);
  const limit = providerBodyLimitBytes(pid);
  if (size > limit) {
    errors.push(`Request body is ${size} bytes, above ${pid || 'provider'} limit ${limit} bytes.`);
  }

  return {
    request: next,
    warnings,
    errors,
    bodyBytes: size,
    bodyLimitBytes: limit,
    provider: pid,
    model: mid,
  };
}

function assertProviderCompatible(request = {}, opts = {}) {
  const result = applyProviderCompatibility(request, opts);
  if (result.errors.length > 0) {
    const err = new Error(result.errors.join(' '));
    err.compatibility = result;
    throw err;
  }
  return result.request;
}

function codingCapability(model = {}, provider = {}) {
  const mid = modelId(model.id || model.model || model.name || model);
  const pid = providerId(provider || model.provider || model.providerId);
  const toolUse = model.toolUse ?? model.tools ?? model.supportsTools ?? !/sora|tts|whisper|embedding|image|vision-only/i.test(mid);
  const streaming = model.streaming ?? model.supportsStreaming ?? true;
  const available = model.available !== false && provider.available !== false && provider.enabled !== false;
  return {
    provider: pid,
    model: mid,
    available: Boolean(available),
    toolUse: Boolean(toolUse),
    streaming: Boolean(streaming),
    coding: Boolean(available && toolUse && streaming),
    reason: !available ? 'provider_or_model_unavailable'
      : !toolUse ? 'model_lacks_tool_use'
        : !streaming ? 'model_lacks_streaming'
          : '',
  };
}

function isReasoningModel(model, provider = '') {
  const id = modelId(model).toLowerCase();
  const pid = providerId(provider);
  return /^o[134](?:-|$)/.test(id)
    || /^gpt-5/.test(id)
    || /grok-3-mini/.test(id)
    || /qwq|qwen.*thinking|qwen3.*think/.test(id)
    || (pid === 'qwen' && /reason|thinking/.test(id));
}

function usesMaxCompletionTokens(model, provider = '') {
  const id = modelId(model).toLowerCase();
  const pid = providerId(provider);
  return /^gpt-5/.test(id) || /^o[134](?:-|$)/.test(id) || pid === 'openai-responses';
}

function modelRejectsIsErrorField(model, provider = '') {
  const id = modelId(model).toLowerCase();
  const pid = providerId(provider);
  return /kimi|moonshot/.test(id) || pid === 'moonshot';
}

function providerBodyLimitBytes(provider) {
  return PROVIDER_BODY_LIMITS[providerId(provider)] || DEFAULT_BODY_LIMIT;
}

function requestBodySize(request) {
  return Buffer.byteLength(JSON.stringify(request || {}), 'utf8');
}

function stripIsErrorFields(messages = []) {
  let count = 0;
  walk(messages, (obj) => {
    if (obj && typeof obj === 'object' && Object.prototype.hasOwnProperty.call(obj, 'is_error')) {
      delete obj.is_error;
      count += 1;
    }
  });
  return count;
}

function walk(value, visit) {
  if (!value || typeof value !== 'object') return;
  visit(value);
  if (Array.isArray(value)) {
    for (const item of value) walk(item, visit);
    return;
  }
  for (const child of Object.values(value)) walk(child, visit);
}

function providerId(provider) {
  if (!provider) return '';
  if (typeof provider === 'string') return provider.toLowerCase();
  return String(provider.type || provider.provider || provider.providerId || provider.id || '').toLowerCase();
}

function modelId(model) {
  if (!model) return '';
  if (typeof model === 'string') return model;
  return String(model.id || model.model || model.name || '');
}

function cloneRequest(request = {}) {
  const next = { ...request };
  if (Array.isArray(request.messages)) next.messages = deepClone(request.messages);
  if (Array.isArray(request.tools)) {
    next.tools = request.tools.map((tool) => {
      if (!tool || typeof tool !== 'object') return tool;
      const copy = { ...tool };
      if (tool.input_schema) copy.input_schema = deepClone(tool.input_schema);
      if (tool.parameters) copy.parameters = deepClone(tool.parameters);
      return copy;
    });
  }
  return next;
}

function deepClone(value) {
  if (value === undefined) return value;
  return JSON.parse(JSON.stringify(value));
}

module.exports = {
  STRIPPED_REASONING_PARAMS,
  applyProviderCompatibility,
  assertProviderCompatible,
  codingCapability,
  isReasoningModel,
  modelRejectsIsErrorField,
  providerBodyLimitBytes,
  requestBodySize,
  stripIsErrorFields,
  usesMaxCompletionTokens,
};
