defmodule AI.Agent.Compactor do
  @behaviour AI.Agent

  @model AI.Model.large_context(:balanced)
  @max_attempts 3

  @system_prompt """
  You are an AI Agent in a larger system.
  You will be presented with a transcript of a conversation between a user and an AI assistant, along with any research the assistant has done.
  Your task: Reformat into compact meeting minutes while preserving all content needed for context.
  Do not use smart quotes, smart apostrophes, emojis, or other special characters.
  Use the following output template:

  # Original User Prompt
  [full text of the original user prompt]

  # Research and Responses
  [outline of facts, findings, and conclusions from the research portions of the conversation]

  # Conversation Timeline
  [
    Present a timeline of the conversation, listing each message with its role and a brief summary of its content.
    Messages at the end of the conversation should include WAY more detail than those at the beginning.
    Don't waste space with formatting or JSON; just use plain text.
  ]

  # Continuation Context
  [
    What was the assistant doing before the conversation grew too long?
    Your response will form the complete prompt for the LLM's next response.
    This section MUST guarantee that the LLM continues exactly where it left off.
  ]
  """

  @impl AI.Agent
  def get_response(%{messages: [%{role: "developer", content: @system_prompt} | _]}) do
    raise "Refusing to compact a compaction prompt"
  end

  def get_response(%{messages: messages} = opts) do
    attempts = Map.get(opts, :attempts, 0)

    transcript =
      transcript(messages, [])
      |> Jason.encode!(pretty: true)

    original_length = byte_size(transcript)

    AI.Completion.get(
      model: @model,
      prompt: @system_prompt,
      messages: [
        AI.Util.system_msg(@system_prompt),
        AI.Util.system_msg("""
        The complete message transcript in JSON format:
        ```json
        #{transcript}
        ```

        Your output MUST use the specified template and include ALL sections.
        """)
      ]
    )
    |> case do
      {:ok, %{response: response}} ->
        summary =
          """
          Summary of conversation and research thus far:
          #{response}
          """

        new_length = byte_size(summary)
        difference = original_length - new_length
        percent = difference / original_length * 100.0

        UI.info("""
        Compaction results:
          Original: #{Util.format_number(original_length)} bytes
         Compacted: #{Util.format_number(new_length)} bytes
           Savings: #{percent}% (#{Util.format_number(difference)} bytes)
        """)

        if new_length >= original_length * 0.65 and attempts < @max_attempts do
          UI.warn(
            "Compaction insufficient",
            "Attempting another pass (#{attempts + 1}/#{@max_attempts})"
          )

          get_response(%{messages: messages, attempts: attempts + 1})
        else
          UI.debug("Compacted conversation", summary)

          summary
          |> AI.Util.system_msg()
          |> then(&{:ok, [&1]})
        end

      {:error, reason} ->
        {:error, reason}
    end
  end

  defp transcript([], acc), do: Enum.reverse(acc)

  defp transcript([%{role: "system"} | rest], acc), do: transcript(rest, acc)
  defp transcript([%{role: "developer"} | rest], acc), do: transcript(rest, acc)
  defp transcript([%{role: "tool", name: "notify_tool"} | rest], acc), do: transcript(rest, acc)

  defp transcript([%{role: "tool", name: name, content: content} | rest], acc) do
    transcript(rest, [%{role: "tool", name: name, content: content} | acc])
  end

  defp transcript([%{role: "assistant", content: nil} | rest], acc), do: transcript(rest, acc)

  defp transcript([%{role: "assistant", content: content} = msg | rest], acc)
       when is_binary(content) do
    if String.starts_with?(content, "<think>") do
      # skip internal reasoning
      transcript(rest, acc)
    else
      # include assistant message
      transcript(rest, [msg | acc])
    end
  end

  defp transcript([msg | rest], acc), do: transcript(rest, [msg | acc])
end
