using Beam.Abstractions;
using HtmlAgilityPack;

namespace Beam.Dynamic;

/// <summary>
/// Allows composition of different data providers to adapt to different types of data.
/// </summary>
/// <typeparam name="T"></typeparam>
public class ComposeDataProviders<T> : IComposableDataProvider<T> {
    public required IComposableDataProvider<object>[] SelectWith { get; init; }
    public required IComposableDataProvider<T> GetWith { get; init; }
    
    private ComposeDataProviders() {}

    public static ComposeDataProviders<T> Create(IComposableDataProvider<object> selectWith, IComposableDataProvider<T> getWith) {
        return new ComposeDataProviders<T>() {
            GetWith = getWith,
            SelectWith = [selectWith]
        };
    }

    public static ComposeDataProviders<T> Create(IComposableDataProvider<object>[] selectWiths, IComposableDataProvider<T> getWith) {
        return new ComposeDataProviders<T>() {
            GetWith = getWith,
            SelectWith = selectWiths
        };
    }

    /// <summary>
    /// Composes the data providers, first selecting a node with <see cref="SelectWith"/>, then getting the data with <see cref="GetWith"/>.
    /// </summary>
    /// <exception cref="Exception">Throws when <see cref="SelectWith"/> returns a null value.</exception>
    /// <param name="document"></param>
    /// <returns></returns>
    public T Get(HtmlDocument document) {
        var selected = Select(document);
        if (selected is null)
            throw new Exception("Selection operation failed.");
        return GetWith.Get(selected);
    }
    
    /// <summary>
    /// Uses the <see cref="GetWith"/> data provider to get the data from the supplied node.
    /// </summary>
    /// <param name="node"></param>
    /// <returns></returns>
    public T Get(HtmlNode node) {
        return GetWith.Get(node);
    }
    
    /// <summary>
    /// Uses the <see cref="SelectWith"/> data provider to select a node from the supplied document.
    /// </summary>
    /// <param name="doc"></param>
    /// <returns></returns>
    public HtmlNode? Select(HtmlDocument doc) {
        var selected = SelectWith[0].Select(doc);
        foreach(var provider in SelectWith.Skip(1)) {
            if (selected is null)
                return null;
            selected = provider.Select(selected);
        }

        return selected;
    }

    /// <summary>
    /// Uses the <see cref="SelectWith"/> data provider to select a node from the supplied document.
    /// </summary>
    /// <param name="doc"></param>
    /// <returns></returns>
    public HtmlNode? Select(HtmlNode node) {
        var selected = SelectWith[0].Select(node);
        foreach(var provider in SelectWith.Skip(1)) {
            if (selected is null)
                return null;
            selected = provider.Select(selected);
        }

        return selected;
    }
}