// Copyright (c) Microsoft. All rights reserved.

// Compaction Pipeline — Progressive context management strategies
//
// This sample demonstrates how to use a CompactionProvider with a compaction
// pipeline as an AIContextProvider for in-run context management. The pipeline
// chains multiple compaction strategies from gentle to aggressive:
//   1. ToolResultCompactionStrategy — Collapses old tool-call groups into summaries
//   2. SummarizationCompactionStrategy — LLM-compresses older conversation spans
//   3. SlidingWindowCompactionStrategy — Keeps only the most recent N user turns
//   4. TruncationCompactionStrategy — Emergency token-budget backstop

using System.ComponentModel;
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Compaction;
using Microsoft.Extensions.AI;

var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";

// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());

// Create a chat client for the agent and a separate one for the summarization strategy.
// Using the same model for simplicity; in production, use a smaller/cheaper model for summarization.
IChatClient agentChatClient = aiProjectClient.GetProjectOpenAIClient().GetResponsesClient().AsIChatClient(deploymentName);
IChatClient summarizerChatClient = aiProjectClient.GetProjectOpenAIClient().GetResponsesClient().AsIChatClient(deploymentName);

// Define a tool the agent can use, so we can see tool-result compaction in action.
[Description("Look up the current price of a product by name.")]
static string LookupPrice([Description("The product name to look up.")] string productName) =>
    productName.ToUpperInvariant() switch
    {
        "LAPTOP" => "The laptop costs $999.99.",
        "KEYBOARD" => "The keyboard costs $79.99.",
        "MOUSE" => "The mouse costs $29.99.",
        _ => $"Sorry, I don't have pricing for '{productName}'."
    };

// Configure the compaction pipeline with one of each strategy, ordered least to most aggressive.
PipelineCompactionStrategy compactionPipeline =
    new(// 1. Gentle: collapse old tool-call groups into short summaries
        new ToolResultCompactionStrategy(CompactionTriggers.MessagesExceed(7)),

        // 2. Moderate: use an LLM to summarize older conversation spans into a concise message
        new SummarizationCompactionStrategy(summarizerChatClient, CompactionTriggers.TokensExceed(0x500)),

        // 3. Aggressive: keep only the last N user turns and their responses
        new SlidingWindowCompactionStrategy(CompactionTriggers.TurnsExceed(4)),

        // 4. Emergency: drop oldest groups until under the token budget
        new TruncationCompactionStrategy(CompactionTriggers.TokensExceed(0x8000)));

// Create the agent with a CompactionProvider that uses the compaction pipeline.
AIAgent agent =
    agentChatClient
        .AsBuilder()
        // Note: Adding the CompactionProvider at the builder level means it will be applied to all agents
        // built from this builder and will manage context for both agent messages and tool calls.
        .UseAIContextProviders(new CompactionProvider(compactionPipeline))
        .BuildAIAgent(
            new ChatClientAgentOptions
            {
                Name = "ShoppingAssistant",
                ChatOptions = new()
                {
                    Instructions =
                        """
                        You are a helpful, but long winded, shopping assistant.
                        Help the user look up prices and compare products.
                        You MUST use the LookupPrice tool for every price question — never answer price questions from memory.
                        When responding, Be sure to be extra descriptive and use as
                        many words as possible without sounding ridiculous.
                        """,
                    Tools = [AIFunctionFactory.Create(LookupPrice)]
                },
                // Note: AIContextProviders may be specified here instead of ChatClientBuilder.UseAIContextProviders.
                // Specifying compaction at the agent level skips compaction in the function calling loop.
                //AIContextProviders = [new CompactionProvider(compactionPipeline)]
            });

AgentSession session = await agent.CreateSessionAsync();

// Helper to print chat history size
void PrintChatHistory()
{
    if (session.TryGetInMemoryChatHistory(out var history))
    {
        Console.ForegroundColor = ConsoleColor.Cyan;
        Console.WriteLine($"\n[Messages: #{history.Count}]\n");
        Console.ResetColor();
    }
}

// Run a multi-turn conversation with tool calls to exercise the pipeline.
string[] prompts =
[
    "What's the price of a laptop?",
    "How about a keyboard?",
    "And a mouse?",
    "Which product is the cheapest?",
    "Can you compare the laptop and the keyboard for me?",
    "What was the first product I asked about?",
    "Thank you!",
];

foreach (string prompt in prompts)
{
    Console.ForegroundColor = ConsoleColor.Cyan;
    Console.Write("\n[User] ");
    Console.ResetColor();
    Console.WriteLine(prompt);
    Console.ForegroundColor = ConsoleColor.Cyan;
    Console.Write("\n[Agent] ");
    Console.ResetColor();
    Console.WriteLine(await agent.RunAsync(prompt, session));

    PrintChatHistory();
}
