using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Olive.Helpers;
using Olive.Klipy;
using Windows.Foundation;

namespace Olive.Pages;

internal sealed partial class GifPickerPage : IDynamicListPage
{
  private const int KlipyMaxPageSize = 50;
  private static readonly TimeSpan SearchDebounceDelay = TimeSpan.FromMilliseconds(350);

  private readonly Lock _stateLock = new();
  private readonly SettingsManager _settingsManager;
  private readonly GifListItemFactory _itemFactory;
  private readonly GifPickerPageState _state;
  private CancellationTokenSource? _searchCancellation;
  private string _searchText = string.Empty;
  private string _activeSearch = string.Empty;
  private int _searchVersion;
  private ICommandItem _emptyContent;

  public GifPickerPage(SettingsManager settingsManager)
  {
    _settingsManager = settingsManager;
    _itemFactory = new GifListItemFactory(Icon, _settingsManager);
    _state = new GifPickerPageState();
    _emptyContent = _itemFactory.InitialContent();
  }

  public event TypedEventHandler<object, IItemsChangedEventArgs>? ItemsChanged;

  public event TypedEventHandler<object, IPropChangedEventArgs>? PropChanged;

  public IIconInfo Icon { get; } = IconHelpers.FromRelativePath("Assets\\StoreLogo.png");

  public string Id => string.Empty;

  public string Name => "Olive GIF Picker";

  public string Title { get; private set; } = "Olive";

  public OptionalColor AccentColor => default;

  public bool IsLoading
  {
    get;
    private set
    {
      if (field == value) return;

      field = value;
      RaisePropChanged(nameof(IsLoading));
    }
  }

  public string PlaceholderText => "Search for GIFs...";

  public bool ShowDetails => false;

  public bool HasMoreItems => false;

  public IFilters? Filters => null;

  public IGridProperties GridProperties { get; } = new GalleryGridLayout { ShowTitle = false, ShowSubtitle = false };

  public ICommandItem EmptyContent
  {
    get => _emptyContent;
    private set
    {
      _emptyContent = value;
      RaisePropChanged(nameof(EmptyContent));
    }
  }

  public string? SearchText
  {
    get => _searchText;
    set
    {
      value ??= string.Empty;
      if (StringComparer.Ordinal.Equals(_searchText, value)) return;

      _searchText = value;
      UpdateSearchText(value);
    }
  }

  public IListItem[] GetItems()
  {
    lock (_stateLock)
    {
      return _state.Snapshot();
    }
  }

  public void LoadMore()
  {
  }

  private void UpdateSearchText(string newSearch)
  {
    CancelSearch();

    var trimmedSearch = newSearch.Trim();
    if (string.IsNullOrWhiteSpace(trimmedSearch))
    {
      ResetSearchState();
      EmptyContent = _settingsManager.HasKlipyApiKey
        ? _itemFactory.InitialContent()
        : _itemFactory.MissingApiKeyContent();
      return;
    }

    var version = Interlocked.Increment(ref _searchVersion);
    var cancellation = new CancellationTokenSource();
    _searchCancellation = cancellation;

    _ = SearchAfterDebounceAsync(trimmedSearch, version, cancellation.Token);
  }

  private void ResetSearchState()
  {
    Interlocked.Increment(ref _searchVersion);
    IsLoading = false;
    _activeSearch = string.Empty;
    ReplaceState([]);
  }

  private void CancelSearch()
  {
    var cancellation = _searchCancellation;
    _searchCancellation = null;
    if (cancellation is null) return;

    try
    {
      cancellation.Cancel();
    }
    catch (ObjectDisposedException)
    {
    }

    cancellation.Dispose();
  }

  private async Task SearchAfterDebounceAsync(string search, int version, CancellationToken cancellationToken)
  {
    try
    {
      await Task.Delay(SearchDebounceDelay, cancellationToken).ConfigureAwait(false);

      var apiKey = _settingsManager.KlipyApiKey;
      if (string.IsNullOrWhiteSpace(apiKey))
      {
        if (IsStale(version, cancellationToken)) return;

        IsLoading = false;
        EmptyContent = _itemFactory.MissingApiKeyContent();
        ReplaceState([]);
        return;
      }

      if (IsStale(version, cancellationToken)) return;

      _activeSearch = search;
      IsLoading = true;
      EmptyContent = _itemFactory.LoadingContent(search);
      UpdateTitle();

      var gifs = await SearchResultsAsync(apiKey, search, _settingsManager.ResultCount, cancellationToken)
        .ConfigureAwait(false);
      if (IsStale(version, cancellationToken)) return;

      if (gifs.Count == 0)
      {
        EmptyContent = _itemFactory.NoResultsContent(search);
        ReplaceState([]);
        return;
      }

      ReplaceState(gifs.Select(_itemFactory.CreateGifItem).ToArray());
    }
    catch (OperationCanceledException)
    {
    }
    catch (Exception ex)
    {
      if (version == _searchVersion) ShowSearchError(ex);
    }
    finally
    {
      if (version == _searchVersion) IsLoading = false;
    }
  }

  private static async Task<List<KlipyGif>> SearchResultsAsync(
    string apiKey,
    string search,
    int resultCount,
    CancellationToken cancellationToken)
  {
    var gifs = new List<KlipyGif>(resultCount);
    var page = 1;

    while (gifs.Count < resultCount)
    {
      var perPage = Math.Min(KlipyMaxPageSize, resultCount - gifs.Count);
      var result = await KlipyClient.SearchAsync(apiKey, search, page, perPage, cancellationToken)
        .ConfigureAwait(false);

      gifs.AddRange(result.Gifs.Take(resultCount - gifs.Count));
      if (!result.HasMore || result.Gifs.Count == 0) break;

      page++;
    }

    return gifs;
  }

  private void ShowSearchError(Exception ex)
  {
    IsLoading = false;
    EmptyContent = _itemFactory.SearchErrorContent(ex);
    ReplaceState([]);
  }

  private void ReplaceState(IListItem[] items)
  {
    int count;
    lock (_stateLock)
    {
      _state.ReplaceItems(items);
      count = _state.DisplayItemCount;
    }

    RaiseItemsChanged(count);
    UpdateTitle();
  }

  private int DisplayItemCount()
  {
    lock (_stateLock)
    {
      return _state.DisplayItemCount;
    }
  }

  private bool IsStale(int version, CancellationToken cancellationToken)
  {
    return version != _searchVersion || cancellationToken.IsCancellationRequested;
  }

  private void UpdateTitle()
  {
    var loadedCount = DisplayItemCount();
    var title = string.IsNullOrWhiteSpace(_activeSearch) || loadedCount == 0
      ? "Olive"
      : $"Olive - {loadedCount} GIFs loaded";

    if (StringComparer.Ordinal.Equals(Title, title)) return;

    Title = title;
    RaisePropChanged(nameof(Title));
  }

  private void RaiseItemsChanged(int count)
  {
    ItemsChanged?.Invoke(this, new ItemsChangedEventArgs(count));
  }

  private void RaisePropChanged(string propertyName)
  {
    PropChanged?.Invoke(this, new PropChangedEventArgs(propertyName));
  }
}
