@page "/notes/{NoteId}"
@attribute [Authorize]
@inject NoteStore                      NoteStore;
@inject RelatedStore                   RelatedStore;
@inject IJSRuntime                     Js
@inject StateService                   StateSvc
@inject NavigationManager              Navigation
@inject IStringLocalizer<Localization> Loc
@inject ILogger<SingleNote>            Logger;
@inject StateSynchronizer              StateSynchronizer;
@inherits ChildNoteController
@using Iceshrimp.Assets.PhosphorIcons
@using Iceshrimp.Frontend.Components
@using Iceshrimp.Frontend.Components.Note
@using Iceshrimp.Frontend.Core.Miscellaneous
@using Iceshrimp.Frontend.Core.Services
@using Iceshrimp.Frontend.Core.Services.NoteStore
@using Iceshrimp.Frontend.Core.Services.StateServicePatterns
@using Iceshrimp.Frontend.Localization
@using Iceshrimp.Shared.Schemas.Web
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Sections
@using Microsoft.Extensions.Localization
@implements IAsyncDisposable

@if (_componentState == State.Loaded)
{
    <HeadTitle Text="@Loc["Note by {0}", RootNote?.User.DisplayName ?? RootNote?.User.Username ?? ""]"/>

    <SectionContent SectionName="top-bar">
        <Icon Name="Icons.Signpost"></Icon>
        @if (RootNote is { User.DisplayName: not null } && RootNote.User.Emojis.Count != 0)
        {
            <MfmText Text="@Loc["Note by {0}", RootNote.User.DisplayName]" Emoji="@RootNote.User.Emojis"
                     Simple="@true"/>
        }
        else
        {
            @Loc["Note by {0}", (RootNote?.User.DisplayName ?? RootNote?.User.Username) ?? string.Empty]
        }
        <button class="cw-button button" @onclick="ToggleCw">@if (_cwState)
            {
                <Icon Name="Icons.EyeClosed"/>
            }
            else
            {
                <Icon Name="Icons.Eye"/>
            }
        </button>
    </SectionContent>
    @if (RootNote?.User.Host != null)
    {
        <RemoteUserBanner OriginalPageUrl="@RootNote?.Url"/>
    }
    <div class="scroller">
        <div class="wrapper">
            <div class="container">
                @if (Ascendants != null)
                {
                    <div class="ascendants">
                        @foreach (var note in Ascendants)
                        {
                            <CascadingValue Value="this" Name="Controller">
                                <CascadingValue Value="RelatedStore" TValue="NoteMessageProvider" Name="Provider">
                                    <AscendedNote Note="note.Value"/>
                                </CascadingValue>
                            </CascadingValue>
                        }
                    </div>
                }
                <div @ref="RootNoteRef" class="root-note">
                    <CascadingValue Value="this" Name="Controller">
                        <CascadingValue Value="NoteStore" TValue="NoteMessageProvider" Name="Provider">
                            <Note NoteResponse="RootNote" OpenNote="false" RootNote="true"></Note>
                        </CascadingValue>
                    </CascadingValue>
                </div>
                <div class="details">
                    <TabbedMenu>
                        <TabPage>
                            <Title>
                                @Loc["Replies ({0})", RootNote!.Replies]
                            </Title>
                            <TabContent>
                                @if (Descendants != null)
                                {
                                    <div class="descendants">
                                        @foreach (var element in Descendants)
                                        {
                                            <CascadingValue Value="this" Name="Controller">
                                                <CascadingValue Value="RelatedStore" TValue="NoteMessageProvider"
                                                                Name="Provider">
                                                    <RecursiveNote Note="element.Value" Depth="0" MaxDepth="_depth"/>
                                                </CascadingValue>
                                            </CascadingValue>
                                        }
                                    </div>
                                }
                            </TabContent>
                        </TabPage>
                        @if (RootNote?.Likes > 0)
                        {
                            <TabPage>
                                <Title>@Loc["Likes ({0})", RootNote!.Likes]</Title>
                                <TabContent>
                                    <NoteLikeDetails NoteId="@RootNote?.Id"/>
                                </TabContent>
                            </TabPage>
                        }
                        @if (RootNote?.Renotes > 0)
                        {
                            <TabPage>
                                <Title>@Loc["Renotes ({0})", RootNote!.Renotes]</Title>
                                <TabContent>
                                    <NoteRenoteDetails NoteId="@RootNote?.Id"/>
                                </TabContent>
                            </TabPage>
                        }
                        @if (RootNote?.Reactions.Count > 0)
                        {
                            <TabPage>
                                <Title>@Loc["Reactions ({0})", RootNote!.Reactions.Count]</Title>
                                <TabContent>
                                    <NoteReactionDetails Reactions="RootNote?.Reactions" NoteId="@RootNote?.Id"/>
                                </TabContent>
                            </TabPage>
                        }
                        <TabPage>
                            <Title>@Loc["Quotes"]</Title>
                            <TabContent>
                                <NoteQuoteDetails NoteId="@RootNote?.Id"/>
                            </TabContent>
                        </TabPage>
                    </TabbedMenu>
                </div>

            </div>
        </div>
    </div>
}
@if (_componentState == State.Loading)
{
    <HeadTitle Text="@Loc["Loading"]"/>

    <div class="loading">
        <LoadingSpinner Scale="2"/>
    </div>
}
@if (_componentState == State.NotFound)
{
    <HeadTitle Text="@Loc["Note not found"]"/>

    <div>This note does not exist!</div>
}
@if (_componentState == State.Empty)
{
    <HeadTitle Text="@Loc["Deleted"]"/>

    <div>@Loc["This post has been deleted"]</div>
}
@if (_componentState == State.Error)
{
    <HeadTitle Text="@Loc["Error"]"/>

    <div>An error occured loading the notes. Please inspect logs.</div>
}

@code {
    [Parameter] public string?                           NoteId      { get; set; }
    public             NoteResponse?                     RootNote    { get; set; }
    private            SortedList<string, NoteResponse>? Descendants { get; set; }
    private            SortedList<string, NoteResponse>? Ascendants  { get; set; }
    private            IJSInProcessObjectReference?      Module      { get; set; }
    private readonly   Lazy<Task<IJSObjectReference>>    _moduleTask;
    private            ElementReference                  RootNoteRef { get; set; }
    private            int                               _depth = 20;
    private            IDisposable?                      _locationChangingHandlerDisposable;
    private            State                             _componentState;
    private            bool                              _firstLoad = true;
    private            bool                              _cwState;

    private async Task Load()
    {
        Logger.LogTrace($"Opening NoteID: {NoteId}");
        Module          = (IJSInProcessObjectReference?)await _moduleTask.Value;
        Logger.LogInformation("Loaded Module");
        _componentState = State.Loading;
        if (NoteId == null)
        {
            _componentState = State.NotFound;
            return;
        }

        try
        {
            var rootNoteTask    = NoteStore.GetNoteAsync(NoteId);
            var descendantsTask = RelatedStore.GetDescendantsAsync(NoteId, _depth);
            var ascendantsTask  = RelatedStore.GetAscendantsAsync(NoteId, default);
            RootNote    = await rootNoteTask;
            Descendants = await descendantsTask;
            Ascendants  = await ascendantsTask;
        }
        catch (ApiException e)
        {
            Logger.LogWarning($"Failed to load ID '{NoteId}' due to API Exception: {e.Message}");
            _componentState = State.Error;
            return;
        }

        if (RootNote == null)
        {
            _componentState = State.NotFound;
            return;
        }

        _componentState = State.Loaded;
    }

    protected override async Task OnInitializedAsync()
    {
        await Load();
    }

    protected override async Task OnParametersSetAsync()
    {
        if (_firstLoad)
        {
            _firstLoad = false;
            return;
        }

        await Load();
        StateHasChanged();
    }

    protected override void OnInitialized()
    {
        _locationChangingHandlerDisposable =  Navigation.RegisterLocationChangingHandler(LocationChangeHandler);
        StateSynchronizer.NoteDeleted      += OnNoteDeleted;
        NoteStore.AnyNoteChanged           += OnNoteChanged;
    }

    private void OnNoteDeleted(object? _, NoteBase note)
    {
        if (NoteId == note.Id)
        {
            _componentState = State.Empty;
        }
        else
        {
            Ascendants?.Remove(note.Id);
            Descendants?.Remove(note.Id);
        }

        StateHasChanged();
    }

    private void OnNoteChanged(object? _, NoteResponse note)
    {
        if (note.Id == NoteId)
        {
            var __ = Refresh();
        }
    }

    private async Task Refresh()
    {
        if (NoteId != null) Descendants = await RelatedStore.GetDescendantsAsync(NoteId, _depth, true);
        StateHasChanged();
    }

    // ReSharper disable once InconsistentNaming
    private async ValueTask LocationChangeHandler(LocationChangingContext arg)
    {
        await SaveState();
    }

    public SingleNote()
    {
        _moduleTask = new Lazy<Task<IJSObjectReference>>(() =>
                                                             Js.InvokeAsync<IJSObjectReference>(
                                                                                                "import",
                                                                                                "./Pages/SingleNote.razor.js")
                                                               .AsTask());
    }

    protected override void OnAfterRender(bool firstRender)
    {
        if (_componentState == State.Loaded)
        {
            var state = StateSvc.SingleNote.GetState(NoteId!);
            if (Module is null)
            {
                Logger.LogError("JS Interop used before initialization");
                return;
            }

            if (state != null)
            {
                Module.InvokeVoid("SetScrollY", state.ScrollTop);
            }
            else
            {
                Module.InvokeVoid("ScrollIntoView", RootNoteRef);
            }
        }

        if (_cwState)
        {
            foreach (var note in NoteChildren)
            {
                note.OpenCw();
            }
        }
        else
        {
            foreach (var note in NoteChildren)
            {
                note.CloseCw();
            }
        }
    }

    private void ToggleCw()
    {
        if (_cwState)
        {
            foreach (var note in NoteChildren)
            {
                note.CloseCw();
            }

            _cwState = false;
        }
        else
        {
            foreach (var note in NoteChildren)
            {
                note.OpenCw();
            }

            _cwState = true;
        }
    }

    private async Task SaveState()
    {
        if (NoteId == null || _componentState != State.Loaded) return;
        var module    = await _moduleTask.Value;
        var scrollTop = await module.InvokeAsync<float>("GetScrollY");
        var state     = new SingleNoteState { ScrollTop = scrollTop };
        StateSvc.SingleNote.SetState(NoteId, state);
    }
    
    public async ValueTask DisposeAsync()
    {
        await SaveState();
        _locationChangingHandlerDisposable?.Dispose();
        StateSynchronizer.NoteDeleted -= OnNoteDeleted;
        NoteStore.AnyNoteChanged      -= OnNoteChanged;
    }
}
