# frozen_string_literal: true

module RubyLLM
  # A single message in a chat conversation.
  class Message
    ROLES = %i[system user assistant tool].freeze

    attr_reader :role, :model_id, :tool_calls, :tool_call_id, :raw, :thinking, :tokens
    attr_writer :content

    def initialize(options = {})
      @role = options.fetch(:role).to_sym
      @tool_calls = options[:tool_calls]
      @content = normalize_content(options.fetch(:content), role: @role, tool_calls: @tool_calls)
      @model_id = options[:model_id]
      @tool_call_id = options[:tool_call_id]
      @tokens = options[:tokens] || Tokens.build(
        input: options[:input_tokens],
        output: options[:output_tokens],
        cached: options[:cached_tokens],
        cache_creation: options[:cache_creation_tokens],
        thinking: options[:thinking_tokens],
        reasoning: options[:reasoning_tokens]
      )
      @raw = options[:raw]
      @thinking = options[:thinking]

      ensure_valid_role
    end

    def content
      if @content.is_a?(Content) && @content.text && @content.attachments.empty?
        @content.text
      else
        @content
      end
    end

    def tool_call?
      !tool_calls.nil? && !tool_calls.empty?
    end

    def tool_result?
      !tool_call_id.nil? && !tool_call_id.empty?
    end

    def tool_results
      content if tool_result?
    end

    def input_tokens
      tokens&.input
    end

    def output_tokens
      tokens&.output
    end

    def cached_tokens
      tokens&.cached
    end

    def cache_creation_tokens
      tokens&.cache_creation
    end

    def cache_read_tokens
      tokens&.cache_read
    end

    def cache_write_tokens
      tokens&.cache_write
    end

    def thinking_tokens
      tokens&.thinking
    end

    def reasoning_tokens
      tokens&.thinking
    end

    def cost(model: nil)
      Cost.new(tokens:, model: model || model_info)
    end

    def to_h
      {
        role: role,
        content: content,
        model_id: model_id,
        tool_calls: tool_calls,
        tool_call_id: tool_call_id,
        thinking: thinking&.text,
        thinking_signature: thinking&.signature
      }.merge(tokens ? tokens.to_h : {}).compact
    end

    def instance_variables
      super - [:@raw]
    end

    def model_info
      return unless model_id

      @model_info ||= RubyLLM.models.find(model_id)
    rescue ModelNotFoundError
      nil
    end

    private

    def normalize_content(content, role:, tool_calls:)
      return '' if role == :assistant && content.nil? && tool_calls && !tool_calls.empty?

      case content
      when String then Content.new(content)
      when Hash then Content.new(content[:text], content)
      else content
      end
    end

    def ensure_valid_role
      raise InvalidRoleError, "Expected role to be one of: #{ROLES.join(', ')}" unless ROLES.include?(role)
    end
  end
end
