# frozen_string_literal: true

module Langchain
  module LLM
    # OpenRouter is OpenAI-compatible, but uses OpenRouter's endpoint and model ids.
    class OpenRouter < OpenAI
      RETRYABLE_ERROR_PATTERN = /429|503|529|rate limit|resource exhausted|overloaded|temporarily unavailable/i

      DEFAULTS = {
        n: 1,
        chat_model: "google/gemini-3.1-pro-preview-customtools",
        embedding_model: "google/gemini-embedding-001",
        dimensions: 3072
      }.freeze

      def initialize(api_key:, llm_options: {}, default_options: {})
        default_options = DEFAULTS.merge(default_options)
        llm_options = {
          uri_base: "https://openrouter.ai/api/v1",
          extra_headers: {
            "HTTP-Referer" => "https://sprintstudio.ai",
            "X-Title" => "Sprint Analysis"
          },
          request_timeout: 600
        }.deep_merge(llm_options)

        super(api_key:, llm_options:, default_options:)
        @defaults = default_options
        chat_parameters.update(
          model: { default: @defaults[:chat_model] },
          n: { default: @defaults[:n] },
          temperature: { default: @defaults[:temperature] },
          response_format: { default: @defaults[:response_format] }
        )
      end

      def default_dimensions
        @defaults[:dimensions]
      end

      def embed_texts(
        texts:,
        model: defaults[:embedding_model],
        encoding_format: nil,
        user: nil,
        dimensions: @defaults[:dimensions]
      )
        texts = Array(texts)
        raise ArgumentError, "texts argument is required" if texts.empty? || texts.any?(&:empty?)
        raise ArgumentError, "model argument is required" if model.empty?
        raise ArgumentError, "encoding_format must be either float or base64" if encoding_format && !%w[float base64].include?(encoding_format)

        parameters = {
          input: texts,
          model:
        }
        parameters[:encoding_format] = encoding_format if encoding_format
        parameters[:user] = user if user
        parameters[:dimensions] = dimensions if dimensions

        response = with_openrouter_retries do
          result = client.embeddings(parameters:)
          raise Langchain::LLM::ApiError, "OpenRouter API error: #{result.dig('error', 'message')}" if result&.dig("error")

          result
        end

        response.fetch("data").sort_by { |datum| datum.fetch("index") }.map { |datum| datum.fetch("embedding") }
      end

      private

      def with_openrouter_retries(max_attempts: 5)
        attempts = 0

        begin
          attempts += 1
          yield
        rescue StandardError => e
          raise unless retryable_error?(e) && attempts < max_attempts

          delay = [2**(attempts - 1), 30].min
          Langchain.logger.warn("OpenRouter embedding request failed; retrying in #{delay}s (attempt #{attempts}/#{max_attempts}): #{e.class}: #{e.message}")
          sleep(delay)
          retry
        end
      end

      def retryable_error?(error)
        return true if defined?(Faraday::TooManyRequestsError) && error.is_a?(Faraday::TooManyRequestsError)
        return true if defined?(Faraday::ServerError) && error.is_a?(Faraday::ServerError)
        return true if defined?(Faraday::TimeoutError) && error.is_a?(Faraday::TimeoutError)

        error.message.match?(RETRYABLE_ERROR_PATTERN)
      end
    end
  end
end
