using InABox.Clients;
using InABox.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace InABox.DynamicGrid;

/// <summary>
/// Defines a common interface for dealing with grids like <see cref="DynamicOneToManyGrid{TOne, TMany}"/>
/// or <see cref="DynamicManyToManyGrid{TManyToMany, TThis}"/>, which display <see cref="Entity"/>s, but do not load them necessarily from the database,
/// instead keeping them in memory.
/// <br/>
/// This interface then allows other functions, like
/// <see cref="DynamicMemoryEntityGridExtensions.EnsureColumns{T}(InABox.DynamicGrid.IDynamicMemoryEntityGrid{T}, Columns{T})"/>, to work based on <see cref="IDynamicMemoryEntityGrid{T}.LoadedColumns"/> and manage our column handling better.
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IDynamicMemoryEntityGrid<T>
    where T : Entity, IRemotable, IPersistent, new()
{
    /// <summary>
    /// A set of columns representing which columns have been loaded from the database.
    /// </summary>
    /// <remarks>
    /// This is used to refresh the data when the columns change.<br/>
    /// 
    /// It is <see langword="null"/> if no data has been loaded from the database (that is, the data was gotten from
    /// a page data handler instead.)
    /// </remarks>
    public HashSet<string>? LoadedColumns { get; }

    public IEnumerable<T> Items { get; }
}

public static class DynamicMemoryEntityGridExtensions
{
    public static void EnsureColumns<T>(this IDynamicMemoryEntityGrid<T> grid, Columns<T> columns)
        where T : Entity, IRemotable, IPersistent, new()
    {
        RequireColumns(grid, columns);
        LoadForeignProperties(grid, columns);
    }

    /// <summary>
    /// Load the properties of any <see cref="EntityLink{T}"/>s on this <see cref="TMany"/> where the <see cref="IEntityLink.ID"/> is not <see cref="Guid.Empty"/>.
    /// This allows us to populate columns of transient objects, as long as they are linked by the ID. What this actually then does is query each
    /// linked table with the required columns.
    /// </summary>
    /// <param name="columns"></param>
    public static void LoadForeignProperties<T>(this IDynamicMemoryEntityGrid<T> grid, Columns<T> columns)
        where T : Entity, IRemotable, IPersistent, new()
    {
        grid.Items.LoadForeignProperties(columns);
    }

    public static void RequireColumns<T>(this IDynamicMemoryEntityGrid<T> grid, Columns<T> columns)
        where T : Entity, IRemotable, IPersistent, new()
    {
        if (grid.LoadedColumns is null) return;

        // Figure out which columns we still need.
        var newColumns = columns.Where(x => !grid.LoadedColumns.Contains(x.Property)).ToColumns(ColumnTypeFlags.None);
        if (newColumns.Count > 0 && typeof(T).GetCustomAttribute<AutoEntity>() is null)
        {
            var data = Client.Query(
                new Filter<T>(x => x.ID).InList(grid.Items.Select(x => x.ID).Where(x => x != Guid.Empty).ToArray()),
                // We also need to add ID, so we know which item to fill.
                newColumns.Add(x => x.ID));
            foreach (var row in data.Rows)
            {
                var item = grid.Items.FirstOrDefault(x => x.ID == row.Get<T, Guid>(y => y.ID));
                if (item is not null)
                {
                    row.FillObject(item, overrideExisting: false);
                }
            }
            // Remember that we have now loaded this data.
            foreach (var column in newColumns)
            {
                grid.LoadedColumns.Add(column.Property);
            }
        }
    }
}
