@using System.Diagnostics.CodeAnalysis
@using Iceshrimp.Assets.PhosphorIcons
@* ReSharper disable once RedundantUsingDirective *@
@using Iceshrimp.Frontend.Components.Note
@using Iceshrimp.Frontend.Core.Miscellaneous
@using Iceshrimp.Frontend.Core.Services
@using Iceshrimp.Frontend.Localization
@using Iceshrimp.Shared.Schemas.Web
@using Microsoft.Extensions.Localization
@using Iceshrimp.Frontend.Core.Services.StateServicePatterns
@inject IStringLocalizer<Localization> Loc;
@inject ApiService                     Api;
@inject NavigationManager              Navigation;
@inject StateService                   StateService;
@inject IJSRuntime                     Js;

<form class="search" @onsubmit="Search" role="search">
    <input @bind="SearchString" class="input" aria-label=""/>
    <InputSelect @bind-Value="@SearchType">
        @foreach (var type in Enum.GetValues(typeof(SearchTypeEnum)))
        {
            <option value="@type">@type</option>
        }
    </InputSelect>
    <button class="button"><Icon Name="Icons.MagnifyingGlass"/>@Loc["Search"]</button>
</form>

@if (_resultType == ResultType.Notes)
{
    <div class="note-results" @ref="Scroller">
        @if (NoteSearchInstance?.NoteResponses != null)
        {
            foreach (var note in NoteSearchInstance?.NoteResponses!)
            {
                <div class="wrapper">
                    <div @onclick="() => OpenNote(note.Id)" class="note-container">
                        <Note NoteResponse="note" OpenNote="false"/>
                    </div>
                </div>
            }

            <div class="end">
                <ScrollEnd ManualLoad="NoteSearchInstance.FetchOlder" IntersectionChange="async () => { if (!NoteSearchInstance.SearchComplete) { await NoteSearchInstance.FetchOlder(); } }"/>
            </div>
        }
    </div>
}
@if (_resultType == ResultType.Users)
{
    <div class="user-results">
        @if (UserSearchInstance?.UserResponses != null)
        {
            foreach (var user in UserSearchInstance?.UserResponses!)
            {
                <div @onclick="() => OpenProfile(user)" class="wrapper">
                    <UserProfileCard User="user"/>
                </div>
            }

            <div class="end">
                <ScrollEnd ManualLoad="UserSearchInstance.FetchOlder" IntersectionChange="async () => { if (!UserSearchInstance.SearchComplete) { await UserSearchInstance.FetchOlder(); } }"/>
            </div>
        }
    </div>
}

@code {
    private string                      SearchString       { get; set; } = "";
    private SearchTypeEnum              SearchType         { get; set; } = SearchTypeEnum.Note;
    private NoteSearch?                 NoteSearchInstance { get; set; }
    private UserSearch?                 UserSearchInstance { get; set; }
    private IJSInProcessObjectReference Module             { get; set; } = null!;
    private ElementReference            Scroller           { get; set; }
    private ResultType                  _resultType;
    private bool                        _setScroll;
    private float                       _scrollTop;

    protected override async Task OnInitializedAsync()
    {
        Module = (IJSInProcessObjectReference)await Js.InvokeAsync<IJSObjectReference>("import", "/Components/SearchComponent.razor.js");
        var state = StateService.Search.GetState();
        if (state != null)
        {
            NoteSearchInstance = new NoteSearch(state.SearchString, Api, state.SearchResults);
            _resultType        = ResultType.Notes;
            _scrollTop         = state.ScrollTop;
            _setScroll         = true;
        }
    }

    protected override void OnAfterRender(bool firstRender)
    {
        if (_setScroll)
        {
            SetScrollY(_scrollTop);
            _setScroll = false;
        }
    }

    private async Task Search()
    {
        NoteSearchInstance = null;
        _resultType        = ResultType.None;
        StateHasChanged();
        try
        {
            var lookup = await Api.Search.LookupAsync(SearchString);
            if (lookup is not null && lookup.TargetUrl.Contains("user"))
            {
                var user = await Api.Users.GetUserAsync(lookup.TargetUrl.Split("users/")[1]);
                if (user is not null)
                {
                    var username                    = $"@{user.Username}";
                    if (user.Host != null) username += $"@{user.Host}";
                    Navigation.NavigateTo($"/{username}");
                }
            }
            else if (lookup is not null && lookup.TargetUrl.Contains("notes"))
            {
                Navigation.NavigateTo(lookup.TargetUrl);
            }
        }
        catch (ApiException)
        {
            if (SearchType == SearchTypeEnum.Note)
            {
                var searchRes = await Api.Search.SearchNotesAsync(SearchString, new PaginationQuery { Limit = 20 });
                if (searchRes.Count > 0)
                {
                    NoteSearchInstance = new NoteSearch(searchString: SearchString, api: Api, noteResponses: searchRes);
                    _resultType        = ResultType.Notes;
                    StateHasChanged();
                }
            }

            if (SearchType == SearchTypeEnum.User)
            {
                var searchRes = await Api.Search.SearchUsersAsync(SearchString, new PaginationQuery() { Limit = 20 });
                if (searchRes.Count > 0)
                {
                    UserSearchInstance = new UserSearch(searchString: SearchString, api: Api, userResponses: searchRes);
                    _resultType        = ResultType.Users;
                    StateHasChanged();
                }
            }
        }
    }

    private void OpenNote(string id)
    {
        StateService.Search.SetState(new SearchState { SearchResults = NoteSearchInstance!.NoteResponses, ScrollTop = GetScrollY(), SearchString = NoteSearchInstance!.SearchString });
        Navigation.NavigateTo($"/notes/{id}");
    }

    private void OpenProfile(UserResponse user)
    {
        var username                    = $"@{user.Username}";
        if (user.Host != null) username += $"@{user.Host}";
        Navigation.NavigateTo($"/{username}");
    }

    private float GetScrollY()
    {
        return Module.Invoke<float>("GetScrollY");
    }

    private void SetScrollY(float scrollTop)
    {
        Module.InvokeVoid("SetScrollY", scrollTop);
    }

    private enum ResultType
    {
        Default,
        Notes,
        Users,
        None
    }

    private enum SearchTypeEnum
    {
        Note,
        User
    }

    [method: SetsRequiredMembers]
    private class UserSearch(string searchString, ApiService api, List<UserResponse> userResponses)
    {
        internal required string             MinId        = userResponses.Last().Id;
        internal required string             MaxId        = userResponses.First().Id;
        internal required string             SearchString = searchString;
        internal required ApiService         Api          = api;
        public required   List<UserResponse> UserResponses { get; init; } = userResponses;
        public            bool               SearchComplete = false;

        public async Task FetchOlder()
        {
            var pq  = new PaginationQuery { Limit = 15, MaxId = MinId };
            var res = await Api.Search.SearchUsersAsync(SearchString, pq);
            switch (res.Count)
            {
                case > 0:
                    MinId = res.Last().Id;
                    UserResponses.AddRange(res);
                    break;
                case 0:
                    SearchComplete = true;
                    break;
            }
        }
    }

    [method: SetsRequiredMembers]
    private class NoteSearch(string searchString, ApiService api, List<NoteResponse> noteResponses)
    {
        internal required string             MinId        = noteResponses.Last().Id;
        internal required string             MaxId        = noteResponses.First().Id;
        public required   string             SearchString = searchString;
        internal required ApiService         Api          = api;
        public required   List<NoteResponse> NoteResponses { get; init; } = noteResponses;
        public            bool               SearchComplete = false;

        public async Task FetchOlder()
        {
            var pq  = new PaginationQuery { Limit = 15, MaxId = MinId };
            var res = await Api.Search.SearchNotesAsync(SearchString, pq);
            switch (res.Count)
            {
                case > 0:
                    MinId = res.Last().Id;
                    NoteResponses.AddRange(res);
                    break;
                case 0:
                    SearchComplete = true;
                    break;
            }
        }
    }
}
