using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Markup;
using Microsoft.UI.Xaml.Media;
using Sonex.Client.Dialogs;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Threading.Tasks;

namespace Sonex.Client.Controls;

[ContentProperty(Name = nameof(Items))]
public sealed class TabView : UserControl
{
    public static readonly DependencyProperty SelectedItemProperty =
        DependencyProperty.Register(
            nameof(SelectedItem),
            typeof(TabViewItem),
            typeof(TabView),
            new PropertyMetadata(null, OnSelectedItemChanged));

    public static readonly DependencyProperty TabHeightProperty =
        DependencyProperty.Register(
            nameof(TabHeight),
            typeof(double),
            typeof(TabView),
            new PropertyMetadata(double.NaN, OnTabAppearanceChanged));

    public static readonly DependencyProperty HeaderCommandBarProperty =
        DependencyProperty.Register(
            nameof(HeaderCommandBar),
            typeof(CommandBar),
            typeof(TabView),
            new PropertyMetadata(null, OnHeaderCommandBarChanged));

    public static readonly DependencyProperty ItemTypeProperty =
        DependencyProperty.Register(
            nameof(ItemType),
            typeof(Type),
            typeof(TabView),
            new PropertyMetadata(null, OnItemTypeChanged));

    private readonly TabViewItemCollection _items = [];
    private readonly List<TabViewItem> _trackedItems = [];
    private readonly SelectorBar _selectorBar;
    private readonly ContentPresenter _selectedContentPresenter;
    private readonly ContentPresenter _headerCommandBarPresenter;
    private bool _isUpdatingSelection;
    private bool _isSelectionChangeRunning;

    public TabView()
    {
        Grid layoutRoot = new()
        {
            RowSpacing = 0
        };
        layoutRoot.CornerRadius = new CornerRadius(10);
        layoutRoot.Padding = new Thickness(0);
        layoutRoot.BorderThickness = new Thickness(1);
        layoutRoot.Background = Application.Current.Resources["CardBackgroundFillColorDefaultBrush"] as Brush;
        layoutRoot.BorderBrush = Application.Current.Resources["CardStrokeColorDefaultBrush"] as Brush;

        layoutRoot.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
        layoutRoot.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });

        Grid headerGrid = new()
        {
            ColumnSpacing = 8,
            Padding = new Thickness(8, 0, 0, 0),
            Height = 48d,
            BorderThickness = new Thickness(0, 0, 0, 1),
            BorderBrush = Application.Current.Resources["CardStrokeColorDefaultBrush"] as Brush
        };
        headerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
        headerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });

        _selectorBar = new SelectorBar();
        _selectorBar.Resources["SelectorBarItemPadding"] = new Thickness(0);
        _selectorBar.Resources["SelectorBarPadding"] = new Thickness(0);
        _selectorBar.Resources["SelectorBarItemPillWidth"] = 36d;

        _selectorBar.VerticalAlignment = VerticalAlignment.Center;

        _headerCommandBarPresenter = new ContentPresenter
        {
            HorizontalAlignment = HorizontalAlignment.Right,
            VerticalAlignment = VerticalAlignment.Center
        };

        Grid.SetColumn(_selectorBar, 0);
        Grid.SetColumn(_headerCommandBarPresenter, 1);

        headerGrid.Children.Add(_selectorBar);
        headerGrid.Children.Add(_headerCommandBarPresenter);

        _selectedContentPresenter = new ContentPresenter
        {
            Background = Application.Current.Resources["CardBackgroundFillColorDefaultBrush"] as Brush
        };

        Grid.SetRow(headerGrid, 0);
        Grid.SetRow(_selectedContentPresenter, 1);

        layoutRoot.Children.Add(headerGrid);
        layoutRoot.Children.Add(_selectedContentPresenter);

        base.Content = layoutRoot;

        _selectorBar.SelectionChanged += SelectorBar_SelectionChanged;
        _items.CollectionChanged += Items_CollectionChanged;

        UpdateHeaderCommandBar();
        EnsureSelection();
        UpdateSelectedContent();
    }

    public TabViewItemCollection Items => _items;

    public TabViewItem? SelectedItem
    {
        get => (TabViewItem?)GetValue(SelectedItemProperty);
        set => SetValue(SelectedItemProperty, value);
    }

    public double TabHeight
    {
        get => (double)GetValue(TabHeightProperty);
        set => SetValue(TabHeightProperty, value);
    }

    public CommandBar? HeaderCommandBar
    {
        get => (CommandBar?)GetValue(HeaderCommandBarProperty);
        set => SetValue(HeaderCommandBarProperty, value);
    }

    public Type? ItemType
    {
        get => (Type?)GetValue(ItemTypeProperty);
        set => SetValue(ItemTypeProperty, value);
    }

    public object? CurrentItem { get; private set; }

    public int SelectedIndex
    {
        get => SelectedItem is null ? -1 : Items.IndexOf(SelectedItem);
        set => SelectedItem = value >= 0 && value < Items.Count ? Items[value] : null;
    }

    public event EventHandler? SelectionChanged;

    public void Load(object? item)
    {
        CurrentItem = item;
        Load();
    }

    public void Load()
    {
        SelectedItem?.Load(CurrentItem);
    }

    private static void OnSelectedItemChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
    {
        if (dependencyObject is TabView tabView)
        {
            _ = tabView.HandleSelectedItemChangedAsync((TabViewItem?)args.OldValue, (TabViewItem?)args.NewValue);
        }
    }

    private static void OnTabAppearanceChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs _)
    {
        if (dependencyObject is TabView tabView)
        {
            tabView.ApplyTabAppearance();
        }
    }

    private static void OnHeaderCommandBarChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs _)
    {
        if (dependencyObject is TabView tabView)
        {
            tabView.UpdateHeaderCommandBar();
        }
    }

    private static void OnItemTypeChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs _)
    {
        if (dependencyObject is TabView tabView)
        {
            tabView.ApplyItemType();
        }
    }

    private async Task HandleSelectedItemChangedAsync(TabViewItem? oldItem, TabViewItem? newItem)
    {
        if (_isUpdatingSelection)
        {
            return;
        }

        if (_isSelectionChangeRunning)
        {
            RevertSelection(oldItem);
            return;
        }

        if (newItem is not null && !Items.Contains(newItem))
        {
            RevertSelection(oldItem);
            return;
        }

        _isSelectionChangeRunning = true;
        try
        {
            if (!await CanLeaveCurrentViewAsync(oldItem))
            {
                RevertSelection(oldItem);
                return;
            }

            UpdateSelectorBarSelection();
            UpdateSelectedContent();

            if (!ReferenceEquals(oldItem, newItem))
            {
                Load();
                RaiseItemSelected(newItem);
                SelectionChanged?.Invoke(this, EventArgs.Empty);
            }
        }
        finally
        {
            _isSelectionChangeRunning = false;
        }
    }

    private void Items_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs _)
    {
        RebuildTrackedItems();
        SyncItems();
        EnsureSelection();
        UpdateSelectedContent();
        Load();
    }

    private void RebuildTrackedItems()
    {
        foreach (TabViewItem item in _trackedItems)
        {
            item.ViewChanged -= Item_ViewChanged;
        }

        _trackedItems.Clear();

        foreach (TabViewItem item in Items)
        {
            item.ViewChanged += Item_ViewChanged;
            _trackedItems.Add(item);
        }
    }

    private void SyncItems()
    {
        _selectorBar.Items.Clear();

        foreach (TabViewItem item in Items)
        {
            ApplyTabAppearance(item);
            ApplyItemType(item);
            _selectorBar.Items.Add(item);
        }
    }

    private void ApplyTabAppearance()
    {
        foreach (TabViewItem item in Items)
        {
            ApplyTabAppearance(item);
        }
    }

    private void ApplyTabAppearance(TabViewItem item)
    {
        if (double.IsNaN(TabHeight) || TabHeight <= 0)
        {
            item.ClearValue(HeightProperty);
            return;
        }

        item.Height = TabHeight;
    }

    private void ApplyItemType()
    {
        foreach (TabViewItem item in Items)
        {
            ApplyItemType(item);
        }
    }

    private void ApplyItemType(TabViewItem item)
    {
        item.ItemType = ItemType;
    }

    private void EnsureSelection()
    {
        if (SelectedItem is not null && Items.Contains(SelectedItem))
        {
            UpdateSelectorBarSelection();
            return;
        }

        _isUpdatingSelection = true;
        SetValue(SelectedItemProperty, Items.Count > 0 ? Items[0] : null);
        _isUpdatingSelection = false;

        UpdateSelectorBarSelection();
    }

    private void RevertSelection(TabViewItem? item)
    {
        _isUpdatingSelection = true;
        SetValue(SelectedItemProperty, item);
        _selectorBar.SelectedItem = item;
        _isUpdatingSelection = false;
        UpdateSelectedContent();
    }

    private void UpdateSelectorBarSelection()
    {
        _isUpdatingSelection = true;
        _selectorBar.SelectedItem = SelectedItem;
        _isUpdatingSelection = false;
    }

    private void UpdateSelectedContent()
    {
        _selectedContentPresenter.Content = SelectedItem?.View;
    }

    private void UpdateHeaderCommandBar()
    {
        if (HeaderCommandBar != null)
        {
            HeaderCommandBar.DefaultLabelPosition = CommandBarDefaultLabelPosition.Right;
        }

        _headerCommandBarPresenter.Content = HeaderCommandBar;
        _headerCommandBarPresenter.Visibility = HeaderCommandBar is null
            ? Visibility.Collapsed
            : Visibility.Visible;
    }

    private void Item_ViewChanged(object? sender, EventArgs e)
    {
        if (ReferenceEquals(sender, SelectedItem))
        {
            UpdateSelectedContent();
        }
    }

    private async void SelectorBar_SelectionChanged(SelectorBar sender, SelectorBarSelectionChangedEventArgs args)
    {
        if (_isUpdatingSelection)
        {
            return;
        }

        var selectedItem = sender.SelectedItem as TabViewItem;
        if (ReferenceEquals(SelectedItem, selectedItem))
        {
            return;
        }

        if (_isSelectionChangeRunning)
        {
            UpdateSelectorBarSelection();
            return;
        }

        var oldItem = SelectedItem;
        if (!await CanLeaveCurrentViewAsync(oldItem))
        {
            UpdateSelectorBarSelection();
            return;
        }

        _isUpdatingSelection = true;
        SetValue(SelectedItemProperty, selectedItem);
        _isUpdatingSelection = false;

        UpdateSelectedContent();
        Load();
        RaiseItemSelected(selectedItem);
        SelectionChanged?.Invoke(this, EventArgs.Empty);
    }

    private async Task<bool> CanLeaveCurrentViewAsync(TabViewItem? item)
    {
        if (item?.View?.HasChanges != true)
        {
            return true;
        }

        var result = await this.ShowQuestionAsync(
            "Unsaved changes",
            "You have unsaved changes. Are you sure you want to leave this page?",
            "Yes",
            "No");

        return result == ContentDialogResult.Primary;
    }

    private void RaiseItemSelected(TabViewItem? item)
    {
        item?.RaiseOnSelect();
    }
}
