#nullable enable
using Baya.Application.Models.Search;
using Baya.Domain.Entities.Catalog;
using Baya.Domain.Entities.Identity;
using Baya.Domain.Entities.Verification;

namespace Baya.Application.Contracts.Search;

/// <summary>
/// The index-maintenance seam (the "<c>ISearchIndexWriter</c>" shape). It keeps <c>nurse_search_index</c>
/// consistent with its source tables. Each method is invoked by the handler that owns the source write and
/// <b>stages</b> its index changes on the same unit of work — the handler's single <c>CommitAsync</c> then
/// persists the source change and its projection atomically. A source write that rolls back rolls back its
/// index change too; the projection can never diverge on a successful commit.
/// <para>
/// The projection is written <b>only by the code path that owns the source row</b>: a variant write
/// reindexes that variant, a profile/verification write reindexes that nurse, a service-area write fans
/// out / removes that nurse's rows for the area. The inline SQL path applies these today; the same change
/// events can later be routed to an outbox/queue for an Elasticsearch feeder without touching callers.
/// </para>
/// <para>
/// The maintainer intentionally reads the facts a given trigger does <i>not</i> change from the database and
/// takes the facts it <i>does</i> change as tracked arguments — so it never reads a stale, pre-commit value.
/// </para>
/// </summary>
public interface ISearchIndexMaintainer
{
    /// <summary>Variant create / edit / activate / deactivate. Reprojects <b>this variant across all the
    /// nurse's active service areas</b> (upsert one row per area) and reconciles away rows for areas no
    /// longer covered. A deactivated variant keeps its rows with <c>is_searchable = 0</c>. Pass the tracked
    /// variant entity — a freshly-created one (id still 0) is inserted in the same graph.</summary>
    Task ReindexVariantAsync(NurseServiceVariant variant, CancellationToken cancellationToken);

    /// <summary>A change to the nurse's bookability or copied aggregates: the <c>is_verified</c> flip,
    /// suspend / un-suspend, the <c>is_accepting_bookings</c> toggle, or a rating recompute. Re-derives
    /// <b>every row for the nurse</b> (each variant × each active area), recomputing <c>is_searchable</c> and
    /// refreshing the copied gender/rating fields. Pass the tracked profile (its just-changed flags/aggregates
    /// are read from it); pass <paramref name="verificationStatus"/> when this same unit of work also changed
    /// verification state, else the committed status is read.</summary>
    Task ReindexNurseAsync(NurseProfile profile, VerificationStatus? verificationStatus, CancellationToken cancellationToken);

    /// <summary>Service-area <b>add</b>. Inserts one row per non-deleted variant for the newly-covered area
    /// (the area itself may not be committed yet — the city/district are taken from the write, not read
    /// back), with <c>is_searchable</c> per the visibility predicate.</summary>
    Task FanOutServiceAreaAsync(long nurseId, long cityId, long? districtId, CancellationToken cancellationToken);

    /// <summary>Service-area <b>remove</b>. Soft-deletes exactly the nurse's rows for that area across all
    /// variants — never collapses other areas.</summary>
    Task RemoveServiceAreaRowsAsync(long nurseId, long cityId, long? districtId, CancellationToken cancellationToken);

    /// <summary>Idempotent full rebuild from source (<c>nurse_profiles × variants × active areas</c>) — the
    /// convergence/reconciliation path. Owns its own batched commits; the incrementally-maintained index and
    /// a fresh rebuild must produce the same live rows.</summary>
    Task<SearchIndexRebuildResult> RebuildAsync(CancellationToken cancellationToken);
}
