'use strict';

const { toOpenAI, messagesToOpenAI, responseFromOpenAI } = require('./tool-adapter');
const { filterToSupportedModels, supportedModelsForProvider } = require('./supported-models');

const DEFAULT_BASE_URL = 'https://api.deepseek.com';

// Models that support deep thinking (default thinking=enabled).
// v4-flash also accepts the param but defaults to disabled for speed/cost.
const THINKING_MODELS = /^deepseek-v4-pro\b/;

const FALLBACK_MODELS = supportedModelsForProvider('deepseek');

function hasToolLoopHistory(messages = []) {
  return (messages || []).some((msg) => {
    if (msg?.role === 'tool') return true;
    if (Array.isArray(msg?.tool_calls) && msg.tool_calls.length > 0) return true;
    if (!Array.isArray(msg?.content)) return false;
    return msg.content.some((part) => part?.type === 'tool_use' || part?.type === 'tool_result');
  });
}

function normalizeThinkingMode(value) {
  if (value === true) return 'enabled';
  if (value === false) return 'disabled';
  if (value == null) return null;
  const text = String(value).trim().toLowerCase();
  if (!text) return null;
  if (text === 'true' || text === 'on') return 'enabled';
  if (text === 'false' || text === 'off') return 'disabled';
  return text;
}

function defaultReasoningEffort(model) {
  return THINKING_MODELS.test(String(model || '')) ? 'max' : 'high';
}

function ensureAssistantReasoningContent(messages = []) {
  return (messages || []).map((msg) => {
    if (msg?.role !== 'assistant') return msg;
    if (Object.prototype.hasOwnProperty.call(msg, 'reasoning_content')) return msg;
    return { ...msg, reasoning_content: '' };
  });
}

function isPlaceholderApiKey(key) {
  return !key || /^(test|test-key|dummy|fake|example)(-|$)/i.test(key) || /^sk-test/i.test(key);
}

/**
 * Create a DeepSeek LLM provider. DeepSeek's HTTP surface is OpenAI-compatible,
 * so we drive the official `openai` SDK against api.deepseek.com.
 *
 * v4 adds `thinking` and `reasoning_effort` request params; when enabled,
 * the response splits chain-of-thought into `message.reasoning_content` next to
 * the final `message.content`. For tool-call loops, DeepSeek requires the
 * assistant `reasoning_content` to be passed back on every later sub-turn.
 *
 * @param {{ apiKey: string, baseUrl?: string, thinking?: 'enabled'|'disabled', reasoningEffort?: 'high'|'max' }} config
 */
function createDeepseekProvider(config = {}) {
  const { apiKey, baseUrl, thinking, reasoningEffort } = config;

  let _client;
  function client() {
    if (!_client) {
      const OpenAI = require('openai');
      _client = new OpenAI({
        apiKey: apiKey || 'placeholder',
        baseURL: baseUrl || DEFAULT_BASE_URL,
      });
    }
    return _client;
  }

  return {
    type: 'deepseek',

    async chat({ model, messages, tools, system, maxTokens, temperature, signal, thinking: thinkingOverride, reasoningEffort: effortOverride } = {}) {
      const t0 = Date.now();
      const targetModel = model || 'deepseek-v4-flash';

      let converted = messagesToOpenAI(messages || []);
      if (system) {
        const systemText = typeof system === 'string' ? system : JSON.stringify(system);
        converted.unshift({ role: 'system', content: systemText });
      }
      converted = ensureAssistantReasoningContent(converted);

      const params = {
        model: targetModel,
        messages: converted,
      };

      if (tools && tools.length > 0) {
        params.tools = toOpenAI(tools);
      }
      if (maxTokens != null) {
        params.max_tokens = maxTokens;
      }
      if (temperature != null) {
        params.temperature = temperature;
      }

      // Resolve thinking mode. Caller override wins; otherwise model-default
      // (v4-pro=enabled, others=disabled). Tool loops keep thinking enabled for
      // v4-pro and carry reasoning_content through the message history above.
      const hasTools = Array.isArray(tools) && tools.length > 0;
      const inToolLoop = hasTools || hasToolLoopHistory(messages || []);
      const modelDefault = THINKING_MODELS.test(targetModel) ? 'enabled' : 'disabled';
      const resolvedThinking = normalizeThinkingMode(
        thinkingOverride !== undefined ? thinkingOverride
          : (thinking !== undefined ? thinking : modelDefault),
      );
      const resolvedEffort = effortOverride || reasoningEffort || defaultReasoningEffort(targetModel);
      params.thinking = {
        type: resolvedThinking,
      };
      if (resolvedThinking === 'enabled') {
        params.reasoning_effort = resolvedEffort;
      }

      const raw = await client().chat.completions.create(params, {
        ...(signal ? { signal } : {}),
      });

      const latencyMs = Date.now() - t0;
      const normalized = responseFromOpenAI(raw);

      // Recover reasoning_content from the raw response (the OpenAI shape
      // adapter strips it; DeepSeek puts it next to `content`). Only single-shot
      // no-tool responses are folded into visible text for legacy renderers; in
      // tool loops, reasoning stays separate protocol state for the next turn.
      const reasoning = raw?.choices?.[0]?.message?.reasoning_content;
      let content = normalized.content;
      if (reasoning && typeof reasoning === 'string' && reasoning.trim() && !inToolLoop) {
        const reasoningBlock = `<think>${reasoning}</think>\n`;
        if (Array.isArray(content)) {
          // content blocks shape — prepend a text block
          content = [{ type: 'text', text: reasoningBlock }, ...content];
        } else if (typeof content === 'string') {
          content = reasoningBlock + content;
        } else {
          content = reasoningBlock;
        }
      }

      let stopReason = 'end_turn';
      if (normalized.stopReason === 'tool_calls') {
        stopReason = 'tool_use';
      } else if (normalized.stopReason === 'stop') {
        stopReason = 'end_turn';
      } else if (normalized.stopReason) {
        stopReason = normalized.stopReason;
      }

      return {
        content,
        toolCalls: normalized.toolCalls || [],
        stopReason,
        usage: normalized.usage || { input: 0, output: 0 },
        latencyMs,
        model: raw.model || targetModel,
        provider: 'deepseek',
        reasoningContent: reasoning && typeof reasoning === 'string' ? reasoning : null,
        raw,
      };
    },

    async listModels(options = {}) {
      const exact = Boolean(options.exact || options.liveOnly);
      if (isPlaceholderApiKey(apiKey)) return exact ? [] : FALLBACK_MODELS;
      try {
        const response = await client().models.list();
        const seen = new Set();
        const models = [];
        for await (const m of response) {
          const id = m.id;
          if (!id || seen.has(id)) continue;
          seen.add(id);
          const name = id
            .replace(/^deepseek-/, 'DeepSeek ')
            .replace(/v4-flash/i, 'V4 Flash')
            .replace(/v4-pro/i, 'V4 Pro')
            .replace(/-/g, ' ');
          models.push({ id, name });
        }
        const supported = filterToSupportedModels('deepseek', models);
        return supported.length > 0 ? supported : FALLBACK_MODELS;
      } catch (err) {
        if (exact) throw err;
        console.warn('[deepseek] Failed to list models from API, using fallback:', err.message);
        return FALLBACK_MODELS;
      }
    },
  };
}

module.exports = { createDeepseekProvider };
