/*
 * Copyright 2025 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Firebase.AI.Internal;

namespace Firebase.AI
{
  /// <summary>
  /// An object that represents a back-and-forth chat with a model, capturing the history and saving
  /// the context in memory between each message sent.
  /// </summary>
  public class Chat
  {
    private readonly GenerativeModel generativeModel;
    private readonly List<ModelContent> chatHistory;

    private readonly Dictionary<string, BaseAutoFunctionDeclaration> autoFunctionDeclarations;
    private readonly int autoFunctionTurnLimit;

    /// <summary>
    /// The previous content from the chat that has been successfully sent and received from the
    /// model. This will be provided to the model for each message sent as context for the discussion.
    /// </summary>
    public IReadOnlyList<ModelContent> History => chatHistory;

    // Note: No public constructor, get one through GenerativeModel.StartChat
    private Chat(GenerativeModel model, IEnumerable<ModelContent> initialHistory,
        IEnumerable<BaseAutoFunctionDeclaration> autoFunctions, int autoFunctionTurnLimit)
    {
      generativeModel = model;

      if (initialHistory != null)
      {
        chatHistory = new List<ModelContent>(initialHistory);
      }
      else
      {
        chatHistory = new List<ModelContent>();
      }

      if (autoFunctions != null && autoFunctions.Any())
      {
        autoFunctionDeclarations = autoFunctions.ToDictionary(afd => afd.Name);
      }
      else
      {
        autoFunctionDeclarations = null;
      }
      this.autoFunctionTurnLimit = autoFunctionTurnLimit;
    }

    /// <summary>
    /// Intended for internal use only.
    /// Use `GenerativeModel.StartChat` instead to ensure proper initialization and configuration of the `Chat`.
    /// </summary>
    internal static Chat InternalCreateChat(GenerativeModel model, IEnumerable<ModelContent> initialHistory,
        IEnumerable<BaseAutoFunctionDeclaration> autoFunctionDeclarations, int autoFunctionTurnLimit)
    {
      return new Chat(model, initialHistory, autoFunctionDeclarations, autoFunctionTurnLimit);
    }

    /// <summary>
    /// Sends a message using the existing history of this chat as context. If successful, the message
    /// and response will be added to the history. If unsuccessful, history will remain unchanged.
    /// </summary>
    /// <param name="content">The input given to the model as a prompt.</param>
    /// <param name="cancellationToken">An optional token to cancel the operation.</param>
    /// <returns>The model's response if no error occurred.</returns>
    /// <exception cref="HttpRequestException">Thrown when an error occurs during content generation.</exception>
    public Task<GenerateContentResponse> SendMessageAsync(
        ModelContent content, CancellationToken cancellationToken = default)
    {
      return SendMessageAsync(new[] { content }, cancellationToken);
    }
    /// <summary>
    /// Sends a message using the existing history of this chat as context. If successful, the message
    /// and response will be added to the history. If unsuccessful, history will remain unchanged.
    /// </summary>
    /// <param name="text">The text given to the model as a prompt.</param>
    /// <param name="cancellationToken">An optional token to cancel the operation.</param>
    /// <returns>The model's response if no error occurred.</returns>
    /// <exception cref="HttpRequestException">Thrown when an error occurs during content generation.</exception>
    public Task<GenerateContentResponse> SendMessageAsync(
        string text, CancellationToken cancellationToken = default)
    {
      return SendMessageAsync(new ModelContent[] { ModelContent.Text(text) }, cancellationToken);
    }
    /// <summary>
    /// Sends a message using the existing history of this chat as context. If successful, the message
    /// and response will be added to the history. If unsuccessful, history will remain unchanged.
    /// </summary>
    /// <param name="content">The input given to the model as a prompt.</param>
    /// <param name="cancellationToken">An optional token to cancel the operation.</param>
    /// <returns>The model's response if no error occurred.</returns>
    /// <exception cref="HttpRequestException">Thrown when an error occurs during content generation.</exception>
    public Task<GenerateContentResponse> SendMessageAsync(
        IEnumerable<ModelContent> content, CancellationToken cancellationToken = default)
    {
      return SendMessageAsyncInternal(content, cancellationToken);
    }

    /// <summary>
    /// Sends a message using the existing history of this chat as context. If successful, the message
    /// and response will be added to the history. If unsuccessful, history will remain unchanged.
    /// </summary>
    /// <param name="content">The input given to the model as a prompt.</param>
    /// <param name="cancellationToken">An optional token to cancel the operation.</param>
    /// <returns>A stream of generated content responses from the model.</returns>
    /// <exception cref="HttpRequestException">Thrown when an error occurs during content generation.</exception>
    public IAsyncEnumerable<GenerateContentResponse> SendMessageStreamAsync(
        ModelContent content, CancellationToken cancellationToken = default)
    {
      return SendMessageStreamAsync(new[] { content }, cancellationToken);
    }
    /// <summary>
    /// Sends a message using the existing history of this chat as context. If successful, the message
    /// and response will be added to the history. If unsuccessful, history will remain unchanged.
    /// </summary>
    /// <param name="text">The text given to the model as a prompt.</param>
    /// <param name="cancellationToken">An optional token to cancel the operation.</param>
    /// <returns>A stream of generated content responses from the model.</returns>
    /// <exception cref="HttpRequestException">Thrown when an error occurs during content generation.</exception>
    public IAsyncEnumerable<GenerateContentResponse> SendMessageStreamAsync(
        string text, CancellationToken cancellationToken = default)
    {
      return SendMessageStreamAsync(new ModelContent[] { ModelContent.Text(text) }, cancellationToken);
    }
    /// <summary>
    /// Sends a message using the existing history of this chat as context. If successful, the message
    /// and response will be added to the history. If unsuccessful, history will remain unchanged.
    /// </summary>
    /// <param name="content">The input given to the model as a prompt.</param>
    /// <param name="cancellationToken">An optional token to cancel the operation.</param>
    /// <returns>A stream of generated content responses from the model.</returns>
    /// <exception cref="HttpRequestException">Thrown when an error occurs during content generation.</exception>
    public IAsyncEnumerable<GenerateContentResponse> SendMessageStreamAsync(
        IEnumerable<ModelContent> content, CancellationToken cancellationToken = default)
    {
      return SendMessageStreamAsyncInternal(content, cancellationToken);
    }

    private Task<GenerateContentResponse> SendMessageAsyncInternal(
        IEnumerable<ModelContent> requestContent, CancellationToken cancellationToken = default)
    {
      Task<GenerateContentResponse> generateContentFunc(List<ModelContent> fullRequest)
      {
        return generativeModel.GenerateContentAsync(fullRequest, cancellationToken);
      }

      return ChatSessionHelpers.SendMessageAsync(chatHistory,
          autoFunctionDeclarations, autoFunctionTurnLimit,
          requestContent, generateContentFunc);
    }

    private IAsyncEnumerable<GenerateContentResponse> SendMessageStreamAsyncInternal(
        IEnumerable<ModelContent> requestContent,
        CancellationToken cancellationToken = default)
    {
      IAsyncEnumerable<GenerateContentResponse> generateContentStreamFunc(List<ModelContent> fullRequest)
      {
        return generativeModel.GenerateContentStreamAsync(fullRequest, cancellationToken);
      }

      return ChatSessionHelpers.SendMessageStreamAsync(chatHistory,
          autoFunctionDeclarations, autoFunctionTurnLimit,
          requestContent, generateContentStreamFunc);
    }
  }

}
