using System.Linq.Expressions;
using ClaudeDo.Data.Models;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;

namespace ClaudeDo.Data.Filtering;

/// <summary>
/// Strategy that defines which tasks belong to a single list. One subclass per list kind;
/// consumers (counters, list loader) ask the registry for the right strategy and never branch
/// on the list id themselves.
///
/// Subclasses express their primary-match condition once as an expression tree
/// (<see cref="MatchExpression"/>), which doubles as a SQL-translatable predicate for EF Core
/// and, compiled on first use, as the in-memory <see cref="Matches"/> predicate.
/// </summary>
public abstract class TaskListFilter
{
    private Func<TaskEntity, bool>? _compiled;

    /// <summary>The list id this filter applies to (e.g. "virtual:queued", "user:abc").</summary>
    public abstract string Id { get; }

    /// <summary>The primary-match predicate as an expression tree, so EF Core can push it into SQL.</summary>
    public abstract Expression<Func<TaskEntity, bool>> MatchExpression { get; }

    /// <summary>True if <paramref name="t"/> is a primary citizen of this list — appears as a row.</summary>
    public bool Matches(TaskEntity t) => (_compiled ??= MatchExpression.Compile())(t);

    /// <summary>True if <paramref name="t"/> should be counted in this list's badge.</summary>
    public abstract bool ShouldCount(TaskEntity t);

    /// <summary>
    /// True if <paramref name="t"/> is shown as a contextual row (not a primary citizen,
    /// but appears to host children that match). Default: nothing extra.
    /// </summary>
    public virtual bool MatchesAsContext(TaskEntity t, IReadOnlyList<TaskEntity> all) => false;
}

/// <summary>
/// Filter for a smart list keyed off a boolean/nullable task flag
/// (My Day, Important, Planned). Counts only non-done matches.
/// </summary>
public sealed class SmartFlagFilter(string id, Expression<Func<TaskEntity, bool>> flag) : TaskListFilter
{
    public override string Id => id;
    public override Expression<Func<TaskEntity, bool>> MatchExpression => flag;
    public override bool ShouldCount(TaskEntity t) => Matches(t) && t.Status != TaskStatus.Done;
}

/// <summary>
/// Virtual list filter matching tasks by a single status (Queued, Running).
/// Planning parents appear contextually when they host a matching child.
/// </summary>
public sealed class StatusFilter(string id, TaskStatus status) : TaskListFilter
{
    public override string Id => id;
    public override Expression<Func<TaskEntity, bool>> MatchExpression => t => t.Status == status;
    public override bool ShouldCount(TaskEntity t) => t.Status == status;
    public override bool MatchesAsContext(TaskEntity t, IReadOnlyList<TaskEntity> all) =>
        PlanningRules.IsPlanningParent(t) &&
        PlanningRules.HasMatchingChild(t, all, c => c.Status == status);
}

public sealed class ReviewFilter : TaskListFilter
{
    public override string Id => "virtual:review";
    public override Expression<Func<TaskEntity, bool>> MatchExpression => t => t.Status == TaskStatus.WaitingForReview;
    public override bool ShouldCount(TaskEntity t) => Matches(t);
}

/// <summary>
/// Filter for any user-defined list. Constructed on demand from the list id —
/// one instance per list.
/// </summary>
public sealed class UserListFilter(string listId) : TaskListFilter
{
    public override string Id { get; } = $"user:{listId}";
    public override Expression<Func<TaskEntity, bool>> MatchExpression => t => t.ListId == listId;
    public override bool ShouldCount(TaskEntity t) => t.ListId == listId && t.Status != TaskStatus.Done;
}

/// <summary>
/// Resolves a list id (e.g. "virtual:queued", "user:abc") to the filter that
/// owns its semantics. Smart and virtual filters are singletons; user-list
/// filters are constructed on demand from the id.
/// </summary>
public sealed class TaskListFilterRegistry
{
    public const string UserListPrefix = "user:";

    private static readonly IReadOnlyDictionary<string, TaskListFilter> BuiltIn =
        new Dictionary<string, TaskListFilter>(StringComparer.Ordinal)
        {
            ["smart:my-day"]    = new SmartFlagFilter("smart:my-day", t => t.IsMyDay),
            ["smart:important"] = new SmartFlagFilter("smart:important", t => t.IsStarred),
            ["smart:planned"]   = new SmartFlagFilter("smart:planned", t => t.ScheduledFor != null),
            ["virtual:queued"]  = new StatusFilter("virtual:queued", TaskStatus.Queued),
            ["virtual:running"] = new StatusFilter("virtual:running", TaskStatus.Running),
            ["virtual:review"]  = new ReviewFilter(),
        };

    /// <summary>
    /// Resolve a filter for a list id, or null if the id is unknown.
    /// </summary>
    public TaskListFilter? Resolve(string listId)
    {
        if (BuiltIn.TryGetValue(listId, out var f)) return f;
        if (listId.StartsWith(UserListPrefix, StringComparison.Ordinal))
        {
            var inner = listId[UserListPrefix.Length..];
            return string.IsNullOrEmpty(inner) ? null : new UserListFilter(inner);
        }
        return null;
    }
}
