#pragma warning disable CA1044, CA1819, CA2227, CS1591

using System;
using System.Collections.Generic;
using System.Linq;
using Diacritics.Extensions;
using Jellyfin.Data;
using Jellyfin.Data.Enums;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Database.Implementations.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;

namespace MediaBrowser.Controller.Entities
{
    public class InternalItemsQuery
    {
        public InternalItemsQuery()
        {
            AlbumArtistIds = [];
            AlbumIds = [];
            AncestorIds = [];
            LinkedChildAncestorIds = [];
            ArtistIds = [];
            BlockUnratedItems = [];
            BoxSetLibraryFolders = [];
            ChannelIds = [];
            ContributingArtistIds = [];
            DtoOptions = new DtoOptions();
            EnableTotalRecordCount = true;
            ExcludeArtistIds = [];
            ExcludeInheritedTags = [];
            IncludeInheritedTags = [];
            ExcludeItemIds = [];
            ExcludeItemTypes = [];
            ExcludeTags = [];
            GenreIds = [];
            Genres = [];
            GroupByPresentationUniqueKey = true;
            ImageTypes = [];
            IncludeItemTypes = [];
            ItemIds = [];
            OwnerIds = [];
            ExtraTypes = [];
            MediaTypes = [];
            OfficialRatings = [];
            OrderBy = [];
            OwnerIds = [];
            PersonIds = [];
            PersonTypes = [];
            PresetViews = [];
            SeriesStatuses = [];
            SourceTypes = [];
            StudioIds = [];
            Tags = [];
            TopParentIds = [];
            TrailerTypes = [];
            VideoTypes = [];
            Years = [];
            SkipDeserialization = false;
            AudioLanguages = [];
            SubtitleLanguages = [];
        }

        public InternalItemsQuery(User? user)
            : this()
        {
            if (user is not null)
            {
                SetUser(user);
            }
        }

        /// <summary>
        /// Gets a value indicating whether the query carries any criteria that narrows the
        /// result set, as opposed to user context, pagination, sorting or DTO options.
        /// </summary>
        public bool HasFilters =>
            IncludeItemTypes.Length > 0
            || ExcludeItemTypes.Length > 0
            || Genres.Count > 0
            || GenreIds.Count > 0
            || Years.Length > 0
            || Tags.Length > 0
            || ExcludeTags.Length > 0
            || OfficialRatings.Length > 0
            || StudioIds.Length > 0
            || ArtistIds.Length > 0
            || AlbumArtistIds.Length > 0
            || ContributingArtistIds.Length > 0
            || ExcludeArtistIds.Length > 0
            || AlbumIds.Length > 0
            || PersonIds.Length > 0
            || PersonTypes.Length > 0
            || MediaTypes.Length > 0
            || VideoTypes.Length > 0
            || ImageTypes.Length > 0
            || SeriesStatuses.Length > 0
            || ItemIds.Length > 0
            || ExcludeItemIds.Length > 0
            || AudioLanguages.Count > 0
            || SubtitleLanguages.Count > 0
            || LinkedChildAncestorIds.Length > 0
            || AncestorIds.Length > 0
            || IsFavorite.HasValue
            || IsFavoriteOrLiked.HasValue
            || IsLiked.HasValue
            || IsPlayed.HasValue
            || IsResumable.HasValue
            || IsFolder.HasValue
            || IsMissing.HasValue
            || IsUnaired.HasValue
            || IsSpecialSeason.HasValue
            || Is3D.HasValue
            || IsHD.HasValue
            || Is4K.HasValue
            || IsLocked.HasValue
            || IsPlaceHolder.HasValue
            || IsMovie.HasValue
            || IsSports.HasValue
            || IsKids.HasValue
            || IsNews.HasValue
            || IsSeries.HasValue
            || IsAiring.HasValue
            || IsVirtualItem.HasValue
            || HasImdbId.HasValue
            || HasTmdbId.HasValue
            || HasTvdbId.HasValue
            || HasOverview.HasValue
            || HasOfficialRating.HasValue
            || HasParentalRating.HasValue
            || HasThemeSong.HasValue
            || HasThemeVideo.HasValue
            || HasSubtitles.HasValue
            || HasSpecialFeature.HasValue
            || HasTrailer.HasValue
            || HasChapterImages.HasValue
            || MinCriticRating.HasValue
            || MinCommunityRating.HasValue
            || MinParentalRating is not null
            || MinIndexNumber.HasValue
            || MinParentAndIndexNumber.HasValue
            || IndexNumber.HasValue
            || ParentIndexNumber.HasValue
            || AiredDuringSeason.HasValue
            || MinWidth.HasValue
            || MinHeight.HasValue
            || MaxWidth.HasValue
            || MaxHeight.HasValue
            || MinPremiereDate.HasValue
            || MaxPremiereDate.HasValue
            || MinStartDate.HasValue
            || MaxStartDate.HasValue
            || MinEndDate.HasValue
            || MaxEndDate.HasValue
            || MinDateCreated.HasValue
            || MinDateLastSaved.HasValue
            || MinDateLastSavedForUser.HasValue
            || AdjacentTo.HasValue
            || !string.IsNullOrEmpty(NameStartsWith)
            || !string.IsNullOrEmpty(NameStartsWithOrGreater)
            || !string.IsNullOrEmpty(NameLessThan)
            || !string.IsNullOrEmpty(NameContains)
            || !string.IsNullOrEmpty(MinSortName)
            || !string.IsNullOrEmpty(Name)
            || !string.IsNullOrEmpty(Person)
            || !string.IsNullOrEmpty(SearchTerm)
            || !string.IsNullOrEmpty(Path);

        public bool Recursive { get; set; }

        public int? StartIndex { get; set; }

        public int? Limit { get; set; }

        public User? User { get; set; }

        public bool? IsFolder { get; set; }

        public bool? IsFavorite { get; set; }

        public bool? IsFavoriteOrLiked { get; set; }

        public bool? IsLiked { get; set; }

        public bool? IsPlayed { get; set; }

        public bool? IsResumable { get; set; }

        public bool? IncludeItemsByName { get; set; }

        public MediaType[] MediaTypes { get; set; }

        public BaseItemKind[] IncludeItemTypes { get; set; }

        public BaseItemKind[] ExcludeItemTypes { get; set; }

        public string[] ExcludeTags { get; set; }

        public string[] ExcludeInheritedTags { get; set; }

        public string[] IncludeInheritedTags { get; set; }

        public IReadOnlyList<string> Genres { get; set; }

        public bool? IsSpecialSeason { get; set; }

        public bool? IsMissing { get; set; }

        public bool? IsUnaired { get; set; }

        public bool? CollapseBoxSetItems { get; set; }

        /// <summary>
        /// Gets or sets the item types that should be collapsed into box sets.
        /// When empty, all types are collapsed. When set, only items of these types are replaced by their parent box set.
        /// </summary>
        public BaseItemKind[] CollapseBoxSetItemTypes { get; set; } = [];

        public string? NameStartsWithOrGreater { get; set; }

        public string? NameStartsWith { get; set; }

        public string? NameLessThan { get; set; }

        public string? NameContains { get; set; }

        public string? MinSortName { get; set; }

        public string? PresentationUniqueKey { get; set; }

        public string? Path { get; set; }

        public string? Name { get; set; }

        public bool? UseRawName { get; set; }

        public string? Person { get; set; }

        public Guid[] PersonIds { get; set; }

        public Guid[] ItemIds { get; set; }

        public Guid[] OwnerIds { get; set; }

        public ExtraType[] ExtraTypes { get; set; }

        public Guid[] ExcludeItemIds { get; set; }

        public Guid? AdjacentTo { get; set; }

        public string[] PersonTypes { get; set; }

        public bool? Is3D { get; set; }

        public bool? IsHD { get; set; }

        public bool? IsLocked { get; set; }

        public bool? IsPlaceHolder { get; set; }

        public bool? HasImdbId { get; set; }

        public bool? HasOverview { get; set; }

        public bool? HasTmdbId { get; set; }

        public bool? HasOfficialRating { get; set; }

        public bool? HasTvdbId { get; set; }

        public bool? HasThemeSong { get; set; }

        public bool? HasThemeVideo { get; set; }

        public bool? HasSubtitles { get; set; }

        public bool? HasSpecialFeature { get; set; }

        public bool? HasTrailer { get; set; }

        public bool? HasParentalRating { get; set; }

        public Guid[] StudioIds { get; set; }

        public IReadOnlyList<Guid> GenreIds { get; set; }

        public ImageType[] ImageTypes { get; set; }

        public VideoType[] VideoTypes { get; set; }

        public UnratedItem[] BlockUnratedItems { get; set; }

        public int[] Years { get; set; }

        public string[] Tags { get; set; }

        public string[] OfficialRatings { get; set; }

        public DateTime? MinPremiereDate { get; set; }

        public DateTime? MaxPremiereDate { get; set; }

        public DateTime? MinStartDate { get; set; }

        public DateTime? MaxStartDate { get; set; }

        public DateTime? MinEndDate { get; set; }

        public DateTime? MaxEndDate { get; set; }

        public bool? IsAiring { get; set; }

        public bool? IsMovie { get; set; }

        public bool? IsSports { get; set; }

        public bool? IsKids { get; set; }

        public bool? IsNews { get; set; }

        public bool? IsSeries { get; set; }

        public int? MinIndexNumber { get; set; }

        /// <summary>
        /// Gets or sets the minimum ParentIndexNumber and IndexNumber.
        /// </summary>
        /// <remarks>
        /// It produces this where clause:
        /// <para>(ParentIndexNumber = X and IndexNumber >= Y) or ParentIndexNumber > X.
        /// </para>
        /// </remarks>
        public (int ParentIndexNumber, int IndexNumber)? MinParentAndIndexNumber { get; set; }

        public int? AiredDuringSeason { get; set; }

        public double? MinCriticRating { get; set; }

        public double? MinCommunityRating { get; set; }

        public IReadOnlyList<Guid> ChannelIds { get; set; }

        public int? ParentIndexNumber { get; set; }

        public int? ParentIndexNumberNotEquals { get; set; }

        public int? IndexNumber { get; set; }

        public ParentalRatingScore? MinParentalRating { get; set; }

        public ParentalRatingScore? MaxParentalRating { get; set; }

        public bool? HasDeadParentId { get; set; }

        public bool? IsVirtualItem { get; set; }

        public Guid ParentId { get; set; }

        public BaseItemKind? ParentType { get; set; }

        public Guid[] AncestorIds { get; set; }

        /// <summary>
        /// Gets or sets a list of ancestor ids that the item's linked children must descend from.
        /// Useful for filtering BoxSets/Playlists to only those that contain items from a specific library.
        /// </summary>
        public Guid[] LinkedChildAncestorIds { get; set; }

        public Guid[] TopParentIds { get; set; }

        public CollectionType?[] PresetViews { get; set; }

        public TrailerType[] TrailerTypes { get; set; }

        public SourceType[] SourceTypes { get; set; }

        public SeriesStatus[] SeriesStatuses { get; set; }

        public string? ExternalSeriesId { get; set; }

        public string? ExternalId { get; set; }

        public Guid[] AlbumIds { get; set; }

        public Guid[] ArtistIds { get; set; }

        public Guid[] ExcludeArtistIds { get; set; }

        public string? AncestorWithPresentationUniqueKey { get; set; }

        public string? SeriesPresentationUniqueKey { get; set; }

        public bool GroupByPresentationUniqueKey { get; set; }

        public bool GroupBySeriesPresentationUniqueKey { get; set; }

        public bool EnableTotalRecordCount { get; set; }

        public bool ForceDirect { get; set; }

        public Dictionary<string, string>? ExcludeProviderIds { get; set; }

        public bool EnableGroupByMetadataKey { get; set; }

        public bool? HasChapterImages { get; set; }

        public IReadOnlyList<(ItemSortBy OrderBy, SortOrder SortOrder)> OrderBy { get; set; }

        public DateTime? MinDateCreated { get; set; }

        public DateTime? MinDateLastSaved { get; set; }

        public DateTime? MinDateLastSavedForUser { get; set; }

        public DtoOptions DtoOptions { get; set; }

        public string? HasNoAudioTrackWithLanguage { get; set; }

        public string? HasNoInternalSubtitleTrackWithLanguage { get; set; }

        public string? HasNoExternalSubtitleTrackWithLanguage { get; set; }

        public string? HasNoSubtitleTrackWithLanguage { get; set; }

        public bool? IsDeadArtist { get; set; }

        public bool? IsDeadStudio { get; set; }

        public bool? IsDeadGenre { get; set; }

        public bool? IsDeadPerson { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether album sub-folders should be returned if they exist.
        /// </summary>
        public bool? DisplayAlbumFolders { get; set; }

        public BaseItem? Parent
        {
            set
            {
                if (value is null)
                {
                    ParentId = Guid.Empty;
                    ParentType = null;
                }
                else
                {
                    ParentId = value.Id;
                    ParentType = value.GetBaseItemKind();
                }
            }
        }

        public Dictionary<string, string>? HasAnyProviderId { get; set; }

        public Dictionary<string, string[]>? HasAnyProviderIds { get; set; }

        public Guid[] AlbumArtistIds { get; set; }

        public Guid[] BoxSetLibraryFolders { get; set; }

        public Guid[] ContributingArtistIds { get; set; }

        public bool? HasAired { get; set; }

        public bool? HasOwnerId { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether to include items with an OwnerId
        /// (additional parts, alternate versions) that are normally excluded from general queries.
        /// </summary>
        public bool IncludeOwnedItems { get; set; }

        public bool? Is4K { get; set; }

        public int? MaxHeight { get; set; }

        public int? MaxWidth { get; set; }

        public int? MinHeight { get; set; }

        public int? MinWidth { get; set; }

        public string? SearchTerm { get; set; }

        public string? SeriesTimerId { get; set; }

        public bool SkipDeserialization { get; set; }

        public bool IncludeExtras { get; set; }

        public IReadOnlyList<string> AudioLanguages { get; set; }

        public IReadOnlyList<string> SubtitleLanguages { get; set; }

        public void SetUser(User user)
        {
            var maxRating = user.MaxParentalRatingScore;
            if (maxRating.HasValue)
            {
                MaxParentalRating = new(maxRating.Value, user.MaxParentalRatingSubScore);
            }

            var other = UnratedItem.Other.ToString();
            BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems)
                .Where(i => i != other)
                .Select(e => Enum.Parse<UnratedItem>(e, true)).ToArray();

            ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags)
                .Where(tag => !string.IsNullOrWhiteSpace(tag))
                .Select(tag => tag.RemoveDiacritics().ToLowerInvariant())
                .ToArray();

            IncludeInheritedTags = user.GetPreference(PreferenceKind.AllowedTags)
                .Where(tag => !string.IsNullOrWhiteSpace(tag))
                .Select(tag => tag.RemoveDiacritics().ToLowerInvariant())
                .ToArray();

            User = user;
        }

        public void ApplyFilters(ItemFilter[] filters)
        {
            static void ThrowConflictingFilters()
                => throw new ArgumentException("Conflicting filters", nameof(filters));

            foreach (var filter in filters)
            {
                switch (filter)
                {
                    case ItemFilter.IsFolder:
                        if (filters.Contains(ItemFilter.IsNotFolder))
                        {
                            ThrowConflictingFilters();
                        }

                        IsFolder = true;
                        break;
                    case ItemFilter.IsNotFolder:
                        if (filters.Contains(ItemFilter.IsFolder))
                        {
                            ThrowConflictingFilters();
                        }

                        IsFolder = false;
                        break;
                    case ItemFilter.IsUnplayed:
                        if (filters.Contains(ItemFilter.IsPlayed))
                        {
                            ThrowConflictingFilters();
                        }

                        IsPlayed = false;
                        break;
                    case ItemFilter.IsPlayed:
                        if (filters.Contains(ItemFilter.IsUnplayed))
                        {
                            ThrowConflictingFilters();
                        }

                        IsPlayed = true;
                        break;
                    case ItemFilter.IsFavorite:
                        IsFavorite = true;
                        break;
                    case ItemFilter.IsResumable:
                        IsResumable = true;
                        break;
                    case ItemFilter.Likes:
                        if (filters.Contains(ItemFilter.Dislikes))
                        {
                            ThrowConflictingFilters();
                        }

                        IsLiked = true;
                        break;
                    case ItemFilter.Dislikes:
                        if (filters.Contains(ItemFilter.Likes))
                        {
                            ThrowConflictingFilters();
                        }

                        IsLiked = false;
                        break;
                    case ItemFilter.IsFavoriteOrLikes:
                        IsFavoriteOrLiked = true;
                        break;
                }
            }
        }
    }
}
