using Microsoft.Data.Sqlite;
using ZB.MOM.WW.LocalDb.Registration;

namespace ZB.MOM.WW.LocalDb;

/// <summary>
/// Consumer SQL API over the embedded SQLite local database. Every connection this
/// hands out has the <c>zb_hlc_next()</c> UDF registered, so capture triggers on
/// registered tables can stamp writes fail-closed.
/// SQL references parameters with the <c>@name</c> marker; supported parameter shapes are
/// an anonymous object / POCO (public instance properties bind as <c>@PropertyName</c>) or an
/// <c>IDictionary&lt;string, object?&gt;</c> (each entry binds as <c>@Key</c>). Null values bind as SQL NULL.
/// </summary>
public interface ILocalDb
{
    /// <summary>
    /// Executes a non-query statement; returns the affected row count.
    /// <paramref name="parameters"/> may be an anonymous object / POCO (properties → <c>@name</c>)
    /// or an <c>IDictionary&lt;string, object?&gt;</c> (entries → <c>@key</c>).
    /// </summary>
    Task<int> ExecuteAsync(string sql, object? parameters = null, CancellationToken ct = default);

    /// <summary>
    /// Executes a query and projects each row through <paramref name="map"/>.
    /// <paramref name="parameters"/> may be an anonymous object / POCO (properties → <c>@name</c>)
    /// or an <c>IDictionary&lt;string, object?&gt;</c> (entries → <c>@key</c>).
    /// </summary>
    Task<IReadOnlyList<T>> QueryAsync<T>(string sql, Func<SqliteDataReader, T> map, object? parameters = null, CancellationToken ct = default);

    /// <summary>Begins a transaction pinned to a single UDF-registered connection.</summary>
    Task<ILocalDbTransaction> BeginTransactionAsync(CancellationToken ct = default);

    /// <summary>Escape hatch: an open, UDF-registered connection the caller owns and disposes.</summary>
    SqliteConnection CreateConnection();

    /// <summary>
    /// Opts a table into replication: validates it has an explicit primary key, computes its
    /// schema digest, and installs the three AFTER triggers that capture every change into the
    /// oplog + row-version tables within the same transaction as the consumer's write. Idempotent
    /// (re-registering regenerates the triggers). Throws <see cref="LocalDbRegistrationException"/>
    /// if the name is unsafe, the table is missing, or it has no primary key.
    /// </summary>
    ReplicatedTable RegisterReplicated(string tableName);

    /// <summary>The tables currently opted into replication, keyed by table name.</summary>
    IReadOnlyDictionary<string, ReplicatedTable> ReplicatedTables { get; }
}

/// <summary>A transaction scope over the local database. Dispose without commit rolls back.</summary>
public interface ILocalDbTransaction : IAsyncDisposable
{
    /// <summary>Executes a non-query statement inside the transaction.</summary>
    Task<int> ExecuteAsync(string sql, object? parameters = null, CancellationToken ct = default);

    /// <summary>Executes a query inside the transaction.</summary>
    Task<IReadOnlyList<T>> QueryAsync<T>(string sql, Func<SqliteDataReader, T> map, object? parameters = null, CancellationToken ct = default);

    /// <summary>Commits the transaction.</summary>
    Task CommitAsync(CancellationToken ct = default);

    /// <summary>Rolls the transaction back.</summary>
    Task RollbackAsync(CancellationToken ct = default);
}
