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

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.SemanticKernel.ChatCompletion;

/// <summary>Provides extension methods for <see cref="IChatClient"/>.</summary>
public static class ChatClientExtensions
{
    /// <summary>
    /// Get chat response which may contain multiple choices for the prompt and settings.
    /// </summary>
    /// <param name="chatClient">Target chat client service.</param>
    /// <param name="prompt">The standardized prompt input.</param>
    /// <param name="executionSettings">The AI execution settings (optional).</param>
    /// <param name="kernel">The <see cref="Kernel"/> containing services, plugins, and other state for use throughout the operation.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>Get chat response with choices generated by the remote model</returns>
    internal static Task<ChatResponse> GetResponseAsync(
        this IChatClient chatClient,
        string prompt,
        PromptExecutionSettings? executionSettings = null,
        Kernel? kernel = null,
        CancellationToken cancellationToken = default)
    {
        var chatOptions = executionSettings.ToChatOptions(kernel);

        // Try to parse the text as a chat history
        if (!ChatPromptParser.TryParse(prompt, out ChatHistory? chatHistory))
        {
            chatHistory = [new ChatMessageContent(AuthorRole.User, prompt)];
        }

        // Check if the execution settings is present and attempt to prepare the chat history for the request
        if (executionSettings is not null)
        {
            chatHistory = executionSettings.ChatClientPrepareChatHistoryForRequest(chatHistory);
        }

        var messageList = chatHistory.ToChatMessageList();
        return chatClient.GetResponseAsync(messageList, chatOptions, cancellationToken);
    }

    /// <summary>Get ChatClient streaming response for the prompt, settings and kernel.</summary>
    /// <param name="chatClient">Target chat client service.</param>
    /// <param name="prompt">The standardized prompt input.</param>
    /// <param name="executionSettings">The AI execution settings (optional).</param>
    /// <param name="kernel">The <see cref="Kernel"/> containing services, plugins, and other state for use throughout the operation.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>Streaming list of different completion streaming string updates generated by the remote model</returns>
    internal static IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
        this IChatClient chatClient,
        string prompt,
        PromptExecutionSettings? executionSettings = null,
        Kernel? kernel = null,
        CancellationToken cancellationToken = default)
    {
        var chatOptions = executionSettings.ToChatOptions(kernel);

        // Try to parse the text as a chat history
        if (!ChatPromptParser.TryParse(prompt, out ChatHistory? chatHistory))
        {
            chatHistory = [new ChatMessageContent(AuthorRole.User, prompt)];
        }

        // Check if the execution settings is present and attempt to prepare the chat history for the request
        if (executionSettings is not null)
        {
            chatHistory = executionSettings.ChatClientPrepareChatHistoryForRequest(chatHistory);
        }

        var messageList = chatHistory.ToChatMessageList();

        // Otherwise, use the prompt as the chat user message
        return chatClient.GetStreamingResponseAsync(messageList, chatOptions, cancellationToken);
    }

    /// <summary>Creates an <see cref="IChatCompletionService"/> for the specified <see cref="IChatClient"/>.</summary>
    /// <param name="client">The chat client to be represented as a chat completion service.</param>
    /// <param name="serviceProvider">An optional <see cref="IServiceProvider"/> that can be used to resolve services to use in the instance.</param>
    /// <returns>
    /// The <see cref="IChatCompletionService"/>. If <paramref name="client"/> is an <see cref="IChatCompletionService"/>, <paramref name="client"/> will
    /// be returned. Otherwise, a new <see cref="IChatCompletionService"/> will be created that wraps <paramref name="client"/>.
    /// </returns>
    public static IChatCompletionService AsChatCompletionService(this IChatClient client, IServiceProvider? serviceProvider = null)
    {
        Verify.NotNull(client);

        return client is IChatCompletionService chatCompletionService ?
            chatCompletionService :
            new ChatClientChatCompletionService(client, serviceProvider);
    }

    /// <summary>
    /// Get the model identifier for the specified <see cref="IChatClient"/>.
    /// </summary>
    public static string? GetModelId(this IChatClient client)
    {
        Verify.NotNull(client);

        return client.GetService<ChatClientMetadata>()?.DefaultModelId;
    }
}
