using System.Text.Json.Serialization;
using Beam.Abstractions;
using HtmlAgilityPack;

namespace Beam.Dynamic;

public record class DataBindings : IDataBindings {
    #region ---------------------- Common Bindings ----------------------
    [JsonIgnore]
    public IDataProvider<string>? Title {
        get => Get<string>(nameof(Title));
        set => Providers[nameof(Title)] = value;
    }
    [JsonIgnore]
    public IDataProvider<string[]>? Authors {
        get => Get<string[]>(nameof(Authors));
        set => Providers[nameof(Authors)] = value;
    }
    [JsonIgnore]
    public IDataProvider<string>? Description {
        get => Get<string>(nameof(Description));
        set => Providers[nameof(Description)] = value;
    }
    [JsonIgnore]
    public IDataProvider<string>? Content {
        get => Get<string>(nameof(Content));
        set => Providers[nameof(Content)] = value;
    }
    [JsonIgnore]
    public IDataProvider<string[]>? Language {
        get => Get<string[]>(nameof(Language));
        set => Providers[nameof(Language)] = value;
    }
    [JsonIgnore]
    public IDataProvider<string[]>? Tags {
        get => Get<string[]>(nameof(Tags));
        set => Providers[nameof(Tags)] = value;
    }
    [JsonIgnore]
    public IDataProvider<string>? Publisher {
        get => Get<string>(nameof(Publisher));
        set => Providers[nameof(Publisher)] = value;
    }
    [JsonIgnore]
    public IDataProvider<DateTimeOffset>? PublicationDate {
        get => Get<DateTimeOffset>(nameof(PublicationDate));
        set => Providers[nameof(PublicationDate)] = value;
    }
    [JsonIgnore]
    public IDataProvider<string>? ISBN {
        get => Get<string>(nameof(ISBN));
        set => Providers[nameof(ISBN)] = value;
    }
    [JsonIgnore]
    public IDataProvider<int>? PageCount {
        get => Get<int>(nameof(PageCount));
        set => Providers[nameof(PageCount)] = value;
    }
    [JsonIgnore]
    public IDataProvider<string>? CoverImage {
        get => Get<string>(nameof(CoverImage));
        set => Providers[nameof(CoverImage)] = value;
    }
    [JsonIgnore]
    public IDataProvider<string[]>? Series {
        get => Get<string[]>(nameof(Series));
        set => Providers[nameof(Series)] = value;
    }
    [JsonIgnore]
    public IDataProvider<int>? Edition {
        get => Get<int>(nameof(Edition));
        set => Providers[nameof(Edition)] = value;
    }
    [JsonIgnore]
    public IDataProvider<string[]>? Contributors {
        get => Get<string[]>(nameof(Contributors));
        set => Providers[nameof(Contributors)] = value;
    }
    [JsonIgnore]
    public IDataProvider<string[]>? Subjects {
        get => Get<string[]>(nameof(Subjects));
        set => Providers[nameof(Subjects)] = value;
    }
    [JsonIgnore]
    public IDataProvider<string>? Rights {
        get => Get<string>(nameof(Rights));
        set => Providers[nameof(Rights)] = value;
    }
    [JsonIgnore]
    public IDataProvider<string[]>? TableOfContents {
        get => Get<string[]>(nameof(TableOfContents));
        set => Providers[nameof(TableOfContents)] = value;
    }
    [JsonIgnore]
    public IDataProvider<string[]>? PagesDropDown {
        get => Get<string[]>(nameof(PagesDropDown));
        set => Providers[nameof(PagesDropDown)] = value;
    }
    [JsonIgnore]
    public IDataProvider<string>? NextPageButton {
        get => Get<string>(nameof(NextPageButton));
        set => Providers[nameof(NextPageButton)] = value;
    }
    [JsonIgnore]
    public IDataProvider<string>? PreviousPageButton {
        get => Get<string>(nameof(PreviousPageButton));
        set => Providers[nameof(PreviousPageButton)] = value;
    }
    #endregion

    public Dictionary<string, IDataProvider?> Providers { get; set; } = [];

    private IDataProvider<T>? Get<T>(string key) {
        if (Providers.TryGetValue(key, out var k) && k is IDataProvider<T> ks)
            return ks;
        return default;
    }

    public virtual ResolvedBindings Resolve(HtmlDocument doc) {
        // explicit fields already handled below
        var mappedKeys = new HashSet<string> {
            nameof(Title), nameof(Authors), nameof(Description), nameof(Content),
            nameof(Language), nameof(Tags), nameof(Publisher), nameof(PublicationDate),
            nameof(ISBN), nameof(PageCount), nameof(CoverImage), nameof(Series),
            nameof(Edition), nameof(Contributors), nameof(Subjects), nameof(Rights),
            nameof(TableOfContents), nameof(PagesDropDown), nameof(NextPageButton),
            nameof(PreviousPageButton)
        };

        var additional = new Dictionary<string, object?>();

        foreach (var (key, provider) in Providers) {
            if (!mappedKeys.Contains(key) && provider is not null) {
                // dynamic call so any IDataProvider<T> works
                additional[key] = ((dynamic)provider).Get(doc);
            }
        }

        return new ResolvedBindings {
            Title = Title?.Get(doc),
            Authors = Authors?.Get(doc) ?? [],
            Description = Description?.Get(doc),
            Content = Content?.Get(doc),
            Language = Language?.Get(doc),
            Tags = Tags?.Get(doc) ?? [],
            Publisher = Publisher?.Get(doc),
            PublicationDate = PublicationDate?.Get(doc),
            ISBN = ISBN?.Get(doc),
            PageCount = PageCount?.Get(doc),
            CoverImage = CoverImage?.Get(doc),
            Series = Series?.Get(doc) ?? [],
            Edition = Edition?.Get(doc),
            Contributors = Contributors?.Get(doc) ?? [],
            Subjects = Subjects?.Get(doc) ?? [],
            Rights = Rights?.Get(doc),
            TableOfContents = TableOfContents?.Get(doc) ?? [],
            PagesDropDown = PagesDropDown?.Get(doc),
            NextPageButton = NextPageButton?.Get(doc),
            PreviousPageButton = PreviousPageButton?.Get(doc),
            Additional = additional
        };
    }
}

public record class ResolvedBindings {
    public string? Title { get; set; }
    public string[]? Authors { get; set; }
    public string? Description { get; set; }
    public string? Content { get; set; }
    public string[]? Language { get; set; }
    public string[]? Tags { get; set; }
    public string? Publisher { get; set; }
    public DateTimeOffset? PublicationDate { get; set; }
    public string? ISBN { get; set; }
    public int? PageCount { get; set; }
    public string? CoverImage { get; set; }
    public string[]? Series { get; set; }
    public int? Edition { get; set; }
    public string[]? Contributors { get; set; }
    public string[]? Subjects { get; set; }
    public string? Rights { get; set; }
    public string[]? TableOfContents { get; set; }
    public string[]? PagesDropDown { get; set; }
    public string? NextPageButton { get; set; }
    public string? PreviousPageButton { get; set; }

    /// <summary>
    /// Values resolved from any providers whose keys aren’t represented
    /// by the named properties above.
    /// </summary>
    public Dictionary<string, object?> Additional { get; set; } = [];
}