#nullable enable

namespace MBN_STOCK_WEBVIEW.Core.Playout;

public enum PlayoutMode
{
    Disabled,
    DryRun,
    Test,
    Live
}

public enum PlayoutConnectionState
{
    Disabled,
    DryRunReady,
    Disconnected,
    Connecting,
    Connected,
    Reconnecting,
    Disconnecting,
    Faulted,
    OutcomeUnknown,
    Disposed
}

public enum PlayoutOperation
{
    Connect,
    Disconnect,
    Prepare,
    TakeIn,
    Next,
    TakeOut,
    UpdateOnAir
}

public enum PlayoutResultCode
{
    Success,
    Rejected,
    Cancelled,
    TimedOut,
    Unavailable,
    Failed,
    OutcomeUnknown
}

public enum PlayoutKtapConnectState
{
    NotAttempted,
    Attempted,
    AcceptedUnconfirmed,
    Failed
}

public enum PlayoutTakeOutScope
{
    Layout,
    All
}

/// <summary>
/// Describes a COM-neutral scene object update performed before QueryVariables.
/// </summary>
public sealed record PlayoutField(
    string ObjectName,
    string? Value = null,
    bool? IsVisible = null);

[Flags]
public enum PlayoutVectorComponents
{
    X = 1,
    Y = 2,
    XY = X | Y,
    Z = 4,
    ZX = Z | X,
    YZ = Y | Z,
    XYZ = X | Y | Z
}

[Flags]
public enum PlayoutCropEdges
{
    Left = 1,
    Top = 2,
    Right = 4,
    Horizontal = Left | Right,
    Bottom = 8,
    Vertical = Top | Bottom,
    All = Left | Top | Right | Bottom
}

[Flags]
public enum PlayoutAngleComponents
{
    Start = 1,
    End = 2,
    All = Start | End
}

public readonly record struct PlayoutPoint(float X, float Y, float Z = 0);

/// <summary>
/// COM-neutral, ordered K3D mutation applied inside the scene transaction.
/// Concrete mutation types form the allowlist; arbitrary method names are never accepted.
/// </summary>
public abstract record PlayoutMutation;

public enum PlayoutMutationTiming
{
    BeforeTransaction,
    InTransaction
}

public sealed record PlayoutSetValue(string ObjectName, string Value) : PlayoutMutation;

public sealed record PlayoutSetAssetValue(string ObjectName, string AssetPath) : PlayoutMutation;

public sealed record PlayoutSetVisible(string ObjectName, bool IsVisible) : PlayoutMutation;

public sealed record PlayoutSetFaceColor(
    string ObjectName,
    int Red,
    int Green,
    int Blue,
    int Alpha = 255) : PlayoutMutation;

public sealed record PlayoutSetPosition(
    string ObjectName,
    float X,
    float Y,
    float Z,
    PlayoutVectorComponents Components) : PlayoutMutation;

public sealed record PlayoutSetPositionKey(
    string ObjectName,
    int KeyIndex,
    float X,
    float Y,
    float Z,
    PlayoutVectorComponents Components) : PlayoutMutation;

public sealed record PlayoutSetScale(
    string ObjectName,
    float X,
    float Y,
    float Z,
    PlayoutVectorComponents Components) : PlayoutMutation;

public sealed record PlayoutSetCropKey(
    string ObjectName,
    int KeyIndex,
    float Left,
    float Top,
    float Right,
    float Bottom,
    PlayoutCropEdges Edges) : PlayoutMutation;

public sealed record PlayoutSetCircleAngleKey(
    string ObjectName,
    int KeyIndex,
    float Start,
    float End,
    PlayoutAngleComponents Components) : PlayoutMutation;

public sealed record PlayoutSetPathPoints(
    string ObjectName,
    IReadOnlyList<PlayoutPoint> Points) : PlayoutMutation;

public sealed record PlayoutSetPathShapePoints(
    string ObjectName,
    IReadOnlyList<PlayoutPoint> Points) : PlayoutMutation;

public sealed record PlayoutUseBackground(
    bool IsEnabled,
    PlayoutMutationTiming Timing = PlayoutMutationTiming.BeforeTransaction) : PlayoutMutation;

/// <summary>
/// Selects the closed native root used to resolve a playout asset. Web content never
/// supplies this value. Scene builders keep using <see cref="SceneDirectory"/>, while
/// the MainForm-compatible F2/F3 background uses its own sibling background root.
/// </summary>
public enum PlayoutAssetRoot
{
    SceneDirectory,
    OperatorBackgroundDirectory
}

public sealed record PlayoutSetBackgroundTexture(
    string AssetPath,
    PlayoutMutationTiming Timing = PlayoutMutationTiming.BeforeTransaction,
    PlayoutAssetRoot AssetRoot = PlayoutAssetRoot.SceneDirectory) : PlayoutMutation;

/// <param name="LoopInfinite">
/// Raw K3D Int32 argument. MainForm uses 10 for its common background and s5006
/// uses 1; this value must not be normalized as a Boolean.
/// </param>
public sealed record PlayoutSetBackgroundVideo(
    string AssetPath,
    int LoopCount,
    int LoopInfinite,
    PlayoutMutationTiming Timing = PlayoutMutationTiming.BeforeTransaction,
    PlayoutAssetRoot AssetRoot = PlayoutAssetRoot.SceneDirectory) : PlayoutMutation;

/// <summary>
/// Identifies a scene and the neutral object changes needed to prepare it.
/// SceneFile is an input only and is never copied into user-facing status/error text.
/// </summary>
public sealed record PlayoutCue(
    string SceneFile,
    string SceneName,
    IReadOnlyList<PlayoutField>? Fields = null,
    int FadeDuration = 0,
    IReadOnlyList<PlayoutMutation>? Mutations = null);

public sealed record PlayoutStatus(
    PlayoutMode Mode,
    PlayoutConnectionState State,
    bool IsConnected,
    bool IsProcessRunning,
    bool IsCommandAvailable,
    bool LiveTakeInAllowed,
    string? PreparedSceneName,
    string? OnAirSceneName,
    string Message,
    DateTimeOffset ChangedAtUtc,
    long Sequence)
{
    public int OperationTimeoutMilliseconds { get; init; } = 5_000;

    /// <summary>
    /// True while Tornado has accepted an SDK Play call but has not yet delivered the
    /// corresponding scene-played completion callback. This is observation-only state;
    /// callers must not retry or infer completion by issuing another scene mutation.
    /// </summary>
    public bool IsPlayCompletionPending { get; init; }

    /// <summary>
    /// True after CutOut/StopAll was accepted and before the matching Tornado
    /// completion callback. No further scene command may be dispatched in this state.
    /// </summary>
    public bool IsTakeOutCompletionPending { get; init; }

    /// <summary>
    /// True only after the adapter entered the KAEngine.KTAPConnect dispatch path.
    /// Process discovery, registration probes, DryRun, and configuration preflight do not set it.
    /// Local reflection or COM marshalling can still fail before a server receives anything.
    /// </summary>
    public PlayoutKtapConnectState LastKtapConnectState { get; init; } =
        PlayoutKtapConnectState.NotAttempted;

    public bool KtapConnectAttempted =>
        LastKtapConnectState != PlayoutKtapConnectState.NotAttempted;

    /// <summary>
    /// True only when KTAPConnect returned the SDK success value (1). This does not prove that
    /// OnHello was observed or that Tornado Network Monitoring contains matching [R]/[S] rows.
    /// </summary>
    public bool? KtapConnectAccepted => LastKtapConnectState switch
    {
        PlayoutKtapConnectState.AcceptedUnconfirmed => true,
        PlayoutKtapConnectState.NotAttempted => false,
        _ => null
    };

    /// <summary>
    /// True after the dynamically generated, vendor-compatible IKAEventHandler receives OnHello;
    /// false when a completed connection lifecycle explicitly proves it was not observed. Null
    /// means that callback delivery is still pending or could not be established reliably.
    /// </summary>
    public bool? KtapHelloObserved { get; init; }

    /// <summary>
    /// True only when the SDK returned its success value, false when no dispatch was attempted,
    /// and null when a local/COM failure or timeout prevents a reliable prediction.
    /// </summary>
    public bool? NetworkMonitoringRecordExpected => LastKtapConnectState switch
    {
        PlayoutKtapConnectState.AcceptedUnconfirmed => true,
        PlayoutKtapConnectState.NotAttempted => false,
        _ => null
    };

    /// <summary>
    /// Any KTAP dispatch attempt requires an operator to inspect the isolated Test instance,
    /// even when the adapter cannot predict whether a matching record should exist.
    /// </summary>
    public bool NetworkMonitoringCheckRequired => KtapConnectAttempted;
}

public sealed class PlayoutStatusChangedEventArgs : EventArgs
{
    public PlayoutStatusChangedEventArgs(PlayoutStatus previous, PlayoutStatus current)
    {
        Previous = previous;
        Current = current;
    }

    public PlayoutStatus Previous { get; }

    public PlayoutStatus Current { get; }
}

public sealed record PlayoutResult(
    PlayoutOperation Operation,
    PlayoutResultCode Code,
    bool IsDryRun,
    string Message,
    DateTimeOffset CompletedAtUtc)
{
    public bool IsSuccess => Code == PlayoutResultCode.Success;
}

/// <summary>
/// COM-free application boundary for all Tornado/K3D playout operations.
/// </summary>
public interface IPlayoutEngine : IAsyncDisposable
{
    PlayoutStatus Status { get; }

    event EventHandler<PlayoutStatusChangedEventArgs>? StatusChanged;

    Task<PlayoutResult> ConnectAsync(CancellationToken cancellationToken = default);

    Task<PlayoutResult> DisconnectAsync(CancellationToken cancellationToken = default);

    Task<PlayoutResult> PrepareAsync(
        PlayoutCue cue,
        CancellationToken cancellationToken = default);

    Task<PlayoutResult> TakeInAsync(CancellationToken cancellationToken = default);

    Task<PlayoutResult> NextAsync(
        PlayoutCue cue,
        CancellationToken cancellationToken = default);

    /// <summary>
    /// Updates and replays the scene already on air without loading a replacement scene.
    /// Used by MainForm-compatible timer refresh. The concrete adapter decides how it
    /// safely retains or reacquires the on-air scene reference.
    /// </summary>
    Task<PlayoutResult> UpdateOnAirAsync(
        PlayoutCue cue,
        CancellationToken cancellationToken = default);

    Task<PlayoutResult> TakeOutAsync(
        PlayoutTakeOutScope scope,
        CancellationToken cancellationToken = default);

    /// <summary>
    /// Latches OutcomeUnknown and releases local adapter/COM references without issuing
    /// an SDK Disconnect. Used only when another control call could make output safety worse.
    /// </summary>
    ValueTask QuarantineAsync();
}
