namespace Domain.Ports.Repositories;

using Domain.Entities;
using System.Linq.Expressions;

/// <summary>
/// Repository interface for student persistence operations.
/// </summary>
public interface IStudentRepository
{
    /// <summary>
    /// Gets a student by ID without related entities.
    /// </summary>
    Task<Student?> GetByIdAsync(int id, CancellationToken ct = default);

    /// <summary>
    /// Gets a student by ID with enrollments and related subjects.
    /// </summary>
    Task<Student?> GetByIdWithEnrollmentsAsync(int id, CancellationToken ct = default);

    /// <summary>
    /// Gets all students without related entities.
    /// </summary>
    Task<IReadOnlyList<Student>> GetAllAsync(CancellationToken ct = default);

    /// <summary>
    /// Gets all students with enrollments and related subjects.
    /// </summary>
    Task<IReadOnlyList<Student>> GetAllWithEnrollmentsAsync(CancellationToken ct = default);

    /// <summary>
    /// Checks if a student exists by ID.
    /// </summary>
    Task<bool> ExistsAsync(int id, CancellationToken ct = default);

    /// <summary>
    /// Checks if an email is already in use, optionally excluding a student.
    /// </summary>
    /// <param name="email">The email to check.</param>
    /// <param name="excludeId">Optional student ID to exclude from the check.</param>
    /// <param name="ct">Cancellation token.</param>
    Task<bool> EmailExistsAsync(string email, int? excludeId = null, CancellationToken ct = default);

    /// <summary>
    /// Gets all students with custom projection to avoid over-fetching.
    /// </summary>
    Task<IReadOnlyList<TResult>> GetAllProjectedAsync<TResult>(
        Expression<Func<Student, TResult>> selector,
        CancellationToken ct = default);

    /// <summary>
    /// Gets a student by ID with custom projection.
    /// </summary>
    Task<TResult?> GetByIdProjectedAsync<TResult>(
        int id,
        Expression<Func<Student, TResult>> selector,
        CancellationToken ct = default);

    /// <summary>
    /// Gets students with keyset pagination (more efficient than OFFSET).
    /// </summary>
    /// <param name="selector">Projection selector.</param>
    /// <param name="afterId">Cursor: ID after which to start.</param>
    /// <param name="pageSize">Number of items per page.</param>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>Items, next cursor (if any), and total count.</returns>
    Task<(IReadOnlyList<TResult> Items, int? NextCursor, int TotalCount)> GetPagedProjectedAsync<TResult>(
        Expression<Func<Student, TResult>> selector,
        int? afterId = null,
        int pageSize = 10,
        CancellationToken ct = default);

    /// <summary>
    /// Gets all students with pending activation (not expired).
    /// </summary>
    Task<IReadOnlyList<Student>> GetPendingActivationAsync(CancellationToken ct = default);

    /// <summary>
    /// Adds a new student to the context.
    /// </summary>
    void Add(Student student);

    /// <summary>
    /// Marks a student as modified in the context.
    /// </summary>
    void Update(Student student);

    /// <summary>
    /// Removes a student from the context.
    /// </summary>
    void Delete(Student student);
}
