using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Layout;
using Avalonia.Media;

// Avalonia's Button, not Android.Widget's. .NET for Android puts Android.Widget in this project's
// implicit usings, so the bare name is ambiguous — the same collision the namespace itself causes
// for Android.App. See the note at the top of MainActivity.
using Application = Avalonia.Application;
using Button = Avalonia.Controls.Button;

namespace DodoSSH.Client.Android.Views;

/// <summary>
/// One destination in the bottom bar.
/// </summary>
/// <remarks>
/// <para>
/// Written in C# rather than as a XAML control because it is a button with a glyph, a label and a rule about
/// the accent bar, and a templated control for that would be more machinery than the thing it draws.
/// </para>
/// <para>
/// <b>The accent bar is on top, not underneath.</b> The v2 design distinguishes the current destination with
/// colour and weight alone and draws no bar at all; the bar is kept, and kept at the top, for a reason the
/// design does not have to think about. The bottom of a phone screen is under the system's own gesture bar,
/// so an indicator drawn there is one the user cannot see — and colour alone at 9pt on a dark background is
/// the single signal a user with a colour deficiency would be left with. Weight, colour and the bar are
/// three, which is the right number for the control that says where you are.
/// </para>
/// <para>
/// Every colour here is resolved from <c>Theme/Palette.axaml</c> rather than written out. A nav bar that
/// named its own blues is exactly how the fourth place a colour appears ends up one shade off — which is the
/// argument the desktop head's <c>Ellipse.dot</c> style makes for being a style rather than a converter.
/// </para>
/// </remarks>
internal sealed class NavButton : Button
{
    public static readonly StyledProperty<string> LabelProperty =
        AvaloniaProperty.Register<NavButton, string>(nameof(Label), string.Empty);

    /// <summary>The glyph drawn above the label.</summary>
    /// <remarks>
    /// A character rather than an icon asset. The four the design uses — ▤ ⌗ ⚿ ≣ — are all in the fonts
    /// Android already has, so shipping four vectors to draw them would buy nothing but four more files to
    /// keep in step with the palette.
    /// </remarks>
    public static readonly StyledProperty<string> GlyphProperty =
        AvaloniaProperty.Register<NavButton, string>(nameof(Glyph), string.Empty);

    public static readonly StyledProperty<bool> IsCurrentProperty =
        AvaloniaProperty.Register<NavButton, bool>(nameof(IsCurrent));

    private readonly Border indicator;
    private readonly TextBlock glyph;
    private readonly TextBlock text;

    // Resolved from the palette once the control is in the tree; see Repaint.
    private IBrush? currentBrush;
    private IBrush? idleBrush;

    public NavButton()
    {
        Background = Brushes.Transparent;
        BorderThickness = new Thickness(0);
        CornerRadius = new CornerRadius(0);
        Padding = new Thickness(0);
        HorizontalAlignment = HorizontalAlignment.Stretch;
        VerticalAlignment = VerticalAlignment.Stretch;
        HorizontalContentAlignment = HorizontalAlignment.Stretch;
        VerticalContentAlignment = VerticalAlignment.Stretch;

        indicator = new Border
        {
            Height = 2,
            VerticalAlignment = VerticalAlignment.Top,
            Background = Brushes.Transparent,
        };

        glyph = new TextBlock
        {
            FontSize = 15,
            HorizontalAlignment = HorizontalAlignment.Center,
        };

        text = new TextBlock
        {
            FontSize = 9,
            FontWeight = FontWeight.Medium,
            HorizontalAlignment = HorizontalAlignment.Center,
        };

        var stack = new StackPanel
        {
            Spacing = 3,
            HorizontalAlignment = HorizontalAlignment.Center,
            VerticalAlignment = VerticalAlignment.Center,
            Children = { glyph, text },
        };

        Content = new Panel { Children = { indicator, stack } };
    }

    public string Label
    {
        get => GetValue(LabelProperty);
        set => SetValue(LabelProperty, value);
    }

    /// <inheritdoc cref="GlyphProperty" />
    public string Glyph
    {
        get => GetValue(GlyphProperty);
        set => SetValue(GlyphProperty, value);
    }

    /// <summary>Whether this is the destination currently showing.</summary>
    public bool IsCurrent
    {
        get => GetValue(IsCurrentProperty);
        set => SetValue(IsCurrentProperty, value);
    }

    /// <inheritdoc />
    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
    {
        base.OnPropertyChanged(change);

        if (change.Property == LabelProperty)
        {
            text.Text = Label;
        }
        else if (change.Property == GlyphProperty)
        {
            glyph.Text = Glyph;
        }
        else if (change.Property == IsCurrentProperty)
        {
            Repaint();
        }
        else if (change.Property == FontFamilyProperty)
        {
            text.FontFamily = FontFamily;
        }
    }

    /// <inheritdoc />
    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
    {
        base.OnApplyTemplate(e);

        // Resolved here rather than in the constructor: application resources are not reachable while the
        // control is still being constructed, and a hard-coded stack would drift from Palette.axaml.
        if (Application.Current?.FindResource("MonoFont") is FontFamily mono)
        {
            text.FontFamily = mono;
        }

        currentBrush = Application.Current?.FindResource("AccentText") as IBrush;
        idleBrush = Application.Current?.FindResource("TextFaint") as IBrush;

        // IsCurrent is normally bound and so has usually already been set by the time the template applies,
        // which is why this runs again here rather than only on the property change.
        Repaint();
    }

    private void Repaint()
    {
        // Both the label and the bar, not just the bar — and the weight as well. See the remark on the type.
        var paint = IsCurrent ? currentBrush : idleBrush;

        if (paint is not null)
        {
            glyph.Foreground = paint;
            text.Foreground = paint;
        }

        text.FontWeight = IsCurrent ? FontWeight.SemiBold : FontWeight.Medium;
        indicator.Background = IsCurrent && currentBrush is not null ? currentBrush : Brushes.Transparent;
    }
}
