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

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;

namespace Microsoft.Agents.AI;

/// <summary>
/// Represents a single streaming response chunk from an <see cref="AIAgent"/>.
/// </summary>
/// <remarks>
/// <para>
/// <see cref="AgentResponseUpdate"/> is so named because it represents updates
/// that layer on each other to form a single agent response. Conceptually, this combines the roles of
/// <see cref="AgentResponse"/> and <see cref="ChatMessage"/> in streaming output.
/// </para>
/// <para>
/// To get the text result of this response chunk, use the <see cref="Text"/> property or simply call <see cref="ToString()"/> on the <see cref="AgentResponseUpdate"/>.
/// </para>
/// <para>
/// The relationship between <see cref="AgentResponse"/> and <see cref="AgentResponseUpdate"/> is
/// codified in the <see cref="AgentResponseExtensions.ToAgentResponseAsync"/> and
/// <see cref="AgentResponse.ToAgentResponseUpdates"/>, which enable bidirectional conversions
/// between the two. Note, however, that the provided conversions may be lossy, for example if multiple
/// updates all have different <see cref="RawRepresentation"/> objects whereas there's only one slot for
/// such an object available in <see cref="AgentResponse.RawRepresentation"/>.
/// </para>
/// </remarks>
[DebuggerDisplay("[{Role}] {ContentForDebuggerDisplay}{EllipsesForDebuggerDisplay,nq}")]
public class AgentResponseUpdate
{
    /// <summary>The response update content items.</summary>
    private IList<AIContent>? _contents;

    /// <summary>Initializes a new instance of the <see cref="AgentResponseUpdate"/> class.</summary>
    [JsonConstructor]
    public AgentResponseUpdate()
    {
    }

    /// <summary>Initializes a new instance of the <see cref="AgentResponseUpdate"/> class.</summary>
    /// <param name="role">The role of the author of the update.</param>
    /// <param name="content">The text content of the update.</param>
    public AgentResponseUpdate(ChatRole? role, string? content)
        : this(role, content is null ? null : [new TextContent(content)])
    {
    }

    /// <summary>Initializes a new instance of the <see cref="AgentResponseUpdate"/> class.</summary>
    /// <param name="role">The role of the author of the update.</param>
    /// <param name="contents">The contents of the update.</param>
    public AgentResponseUpdate(ChatRole? role, IList<AIContent>? contents)
    {
        this.Role = role;
        this._contents = contents;
    }

    /// <summary>Initializes a new instance of the <see cref="AgentResponseUpdate"/> class.</summary>
    /// <param name="chatResponseUpdate">The <see cref="ChatResponseUpdate"/> from which to seed this <see cref="AgentResponseUpdate"/>.</param>
    public AgentResponseUpdate(ChatResponseUpdate chatResponseUpdate)
    {
        _ = Throw.IfNull(chatResponseUpdate);

        this.AdditionalProperties = chatResponseUpdate.AdditionalProperties;
        this.AuthorName = chatResponseUpdate.AuthorName;
        this.Contents = chatResponseUpdate.Contents;
        this.CreatedAt = chatResponseUpdate.CreatedAt;
        this.FinishReason = chatResponseUpdate.FinishReason;
        this.MessageId = chatResponseUpdate.MessageId;
        this.RawRepresentation = chatResponseUpdate;
        this.ResponseId = chatResponseUpdate.ResponseId;
        this.Role = chatResponseUpdate.Role;
        this.ContinuationToken = chatResponseUpdate.ContinuationToken;
    }

    /// <summary>Gets or sets the name of the author of the response update.</summary>
    public string? AuthorName
    {
        get => field;
        set => field = string.IsNullOrWhiteSpace(value) ? null : value;
    }

    /// <summary>Gets or sets the role of the author of the response update.</summary>
    public ChatRole? Role { get; set; }

    /// <summary>Gets the text of this update.</summary>
    /// <remarks>
    /// This property concatenates the text of all <see cref="TextContent"/> objects in <see cref="Contents"/>.
    /// </remarks>
    [JsonIgnore]
    public string Text => this._contents is not null ? this._contents.ConcatText() : string.Empty;

    /// <summary>Gets or sets the agent run response update content items.</summary>
    [AllowNull]
    public IList<AIContent> Contents
    {
        get => this._contents ??= [];
        set => this._contents = value;
    }

    /// <summary>Gets or sets the raw representation of the response update from an underlying implementation.</summary>
    /// <remarks>
    /// If a <see cref="AgentResponseUpdate"/> is created to represent some underlying object from another object
    /// model, this property can be used to store that original object. This can be useful for debugging or
    /// for enabling a consumer to access the underlying object model if needed.
    /// </remarks>
    [JsonIgnore]
    public object? RawRepresentation { get; set; }

    /// <summary>Gets or sets additional properties for the update.</summary>
    public AdditionalPropertiesDictionary? AdditionalProperties { get; set; }

    /// <summary>Gets or sets the ID of the agent that produced the response.</summary>
    public string? AgentId { get; set; }

    /// <summary>Gets or sets the ID of the response of which this update is a part.</summary>
    public string? ResponseId { get; set; }

    /// <summary>Gets or sets the ID of the message of which this update is a part.</summary>
    /// <remarks>
    /// A single streaming response may be composed of multiple messages, each of which may be represented
    /// by multiple updates. This property is used to group those updates together into messages.
    ///
    /// Some providers may consider streaming responses to be a single message, and in that case
    /// the value of this property may be the same as the response ID.
    ///
    /// This value is used when <see cref="AgentResponseExtensions.ToAgentResponseAsync(IAsyncEnumerable{AgentResponseUpdate}, System.Threading.CancellationToken)"/>
    /// groups <see cref="AgentResponseUpdate"/> instances into <see cref="AgentResponse"/> instances.
    /// The value must be unique to each call to the underlying provider, and must be shared by
    /// all updates that are part of the same logical message within a streaming response.
    /// </remarks>
    public string? MessageId { get; set; }

    /// <summary>Gets or sets a timestamp for the response update.</summary>
    public DateTimeOffset? CreatedAt { get; set; }

    /// <summary>
    /// Gets or sets the continuation token for resuming the streamed agent response of which this update is a part.
    /// </summary>
    /// <remarks>
    /// <see cref="AIAgent"/> implementations that support background responses will return
    /// a continuation token on each update if background responses are allowed in <see cref="AgentRunOptions.AllowBackgroundResponses"/>
    /// except for the last update, for which the token will be <see langword="null"/>.
    /// <para>
    /// This property should be used for stream resumption, where the continuation token of the latest received update should be
    /// passed to <see cref="AgentRunOptions.ContinuationToken"/> on subsequent calls to <see cref="AIAgent.RunStreamingAsync(AgentSession?, AgentRunOptions?, System.Threading.CancellationToken)"/>
    /// to resume streaming from the point of interruption.
    /// </para>
    /// </remarks>
    public ResponseContinuationToken? ContinuationToken { get; set; }

    /// <summary>
    /// Gets or sets the reason for the agent response finishing.
    /// </summary>
    /// <value>
    /// A <see cref="ChatFinishReason"/> value indicating why the response finished (e.g., stop, length, content filter, tool calls),
    /// or <see langword="null"/> if the finish reason is not available or not yet determined (mid-stream).
    /// </value>
    public ChatFinishReason? FinishReason { get; set; }

    /// <inheritdoc/>
    public override string ToString() => this.Text;

    /// <summary>Gets a <see cref="AIContent"/> object to display in the debugger display.</summary>
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    [ExcludeFromCodeCoverage]
    private AIContent? ContentForDebuggerDisplay => this._contents is { Count: > 0 } ? this._contents[0] : null;

    /// <summary>Gets an indication for the debugger display of whether there's more content.</summary>
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    [ExcludeFromCodeCoverage]
    private string EllipsesForDebuggerDisplay => this._contents is { Count: > 1 } ? ", ..." : string.Empty;
}
