@page "/mod/emojis"
@using Iceshrimp.Frontend.Core.Services
@using Iceshrimp.Frontend.Localization
@using Microsoft.AspNetCore.Authorization
@using Microsoft.Extensions.Localization
@using Microsoft.AspNetCore.Components.Sections
@using Iceshrimp.Assets.PhosphorIcons
@using Iceshrimp.Frontend.Core.Miscellaneous
@using Iceshrimp.Shared.Schemas.Web
@using Iceshrimp.Frontend.Components
@attribute [Authorize(Roles = "moderator")]
@layout ModerationLayout
@inject ApiService                     Api;
@inject GlobalComponentSvc             Global;
@inject IJSRuntime                     Js;
@inject IStringLocalizer<Localization> Loc;
@inject ILogger<CustomEmojis>          Logger;

<HeadTitle Text="@Loc["Custom Emojis"]"/>

<SectionContent SectionName="top-bar">
    <Icon Name="Icons.Smiley"></Icon>
    @Loc["Custom Emojis"]
    @if (State is State.Empty or State.Loaded && Source == "local")
    {
        <span class="action btn" @onclick="OpenUpload" title="@Loc["Upload emoji"]">
            <Icon Name="Icons.Upload"/>
        </span>
        <span class="action btn" @onclick="OpenImport" title="@Loc["Import emoji pack"]">
            <Icon Name="Icons.FileArrowUp"/>
        </span>
    }
</SectionContent>

<div class="body">
    @if (State is State.Empty or State.Error or State.Loaded)
    {
        <div class="emoji-search">
            <input @bind="EmojiFilter" @bind:event="oninput" @bind:after="FilterEmojis" class="search" type="text"
                   placeholder="Search" aria-label="search"/>
            <select class="search-from" @bind="Source" @bind:after="GetEmojis">
                <option value="local">@Loc["Local"]</option>
                <option value="remote">@Loc["Remote"]</option>
            </select>
                <select class="display-type" @bind=_displayType @bind:after="GetEmojis">
                    <option value="@DisplayType.All">@Loc["List"]</option>
                    <option value="@DisplayType.Categories">@Loc["Categories"]</option>
                </select>
        </div>
    }
    @if (State is State.Loaded)
    {
        @if (_displayType is DisplayType.All )
        {
            <div class="emoji-list">
                @foreach (var emoji in DisplayedEmojis)
                {
                    <EmojiManagementEntry Emoji="@emoji" Source="@Source" GetEmojis="GetEmojis"/>
                }
            </div>
            @if (PaginationData is { Next: not null } && Source == "remote")
            {
                <ScrollEnd ManualLoad="FetchMore" IntersectionChange="FetchMore"></ScrollEnd>
            }
        }
        
        @if (_displayType is DisplayType.Categories && Source == "local")
        {
            foreach (var el in LocalEmojiCategories)
            {
                <span class="category-name">@el.Key</span>
                <div class="emoji-list">
                    @foreach (var emoji in el.Value)
                    {
                        <EmojiManagementEntry Emoji="emoji" Source="remote"/>
                    }
                </div>
            }
           
        }

        @if (_displayType is DisplayType.Categories && Source == "remote")
        {
            @foreach (var category in DisplayedCategories)
            {
                <HostEmojiEntry Host="@category"/>
            }

            <ScrollEnd IntersectionChange="FetchHosts" ManualLoad="FetchHosts"/>
        }
    }
    @if (State is State.Empty)
    {
        <i>This instance has no emojis</i>
    }
    @if (State is State.Loading)
    {
        <div class="loading">
            <LoadingSpinner Scale="2"/>
        </div>
    }

    <div class="file-input">
        <InputFile @ref="UploadInput" OnChange="Upload" accept="image/*">Upload</InputFile>
        <InputFile @ref="ImportInput" OnChange="Import" accept=".zip">Import</InputFile>
    </div>
</div>

@code {
    private List<EmojiResponse>                     DisplayedEmojis      { get; set; } = [];
    private List<EmojiResponse>                     StoredLocalEmojis    { get; set; } = [];
    private List<EmojiResponse>                     StoredRemoteEmojis   { get; set; } = [];
    private Dictionary<string, List<EmojiResponse>> LocalEmojiCategories { get; set; } = new();
    private HashSet<string>                         Categories           { get; set; } = [];
    private HashSet<string>                         DisplayedCategories  { get; set; } = [];
    private string                                  EmojiFilter          { get; set; } = "";
    private string                                  Source               { get; set; } = "local";
    private State                                   State                { get; set; }
    private InputFile                               UploadInput          { get; set; } = null!;
    private IBrowserFile                            UploadFile           { get; set; } = null!;
    private InputFile                               ImportInput          { get; set; } = null!;
    private IBrowserFile                            ImportFile           { get; set; } = null!;
    private PaginationData?                         PaginationData       { get; set; }
    private IJSObjectReference                      _module      = null!;
    private DisplayType                             _displayType = DisplayType.All;

    private void FilterEmojis()
    {
        if (Source == "remote" && _displayType == DisplayType.All || Source == "local")
        {
            DisplayedEmojis = StoredRemoteEmojis.Where(p => p.Name.Contains(EmojiFilter.Trim()) || p.Aliases.Count(a => a.Contains(EmojiFilter.Trim())) > 0).ToList();
        }

        if (Source == "remote" && _displayType == DisplayType.Categories)
        {
            DisplayedCategories = Categories.Where(p => p.Contains(EmojiFilter.Trim())).ToHashSet();
        }

        if (Source == "local" && _displayType == DisplayType.Categories)
        {
            LocalEmojiCategories = StoredLocalEmojis
            .Where(p => p.Name.Contains(EmojiFilter.Trim()) || p.Aliases.Count(a => a.Contains(EmojiFilter.Trim())) != 0)
            .OrderBy(p => p.Name)
            .ThenBy(p => p.Id)
            .GroupBy(p => p.Category)
            .OrderBy(p => string.IsNullOrEmpty(p.Key))
            .ThenBy(p => p.Key)
            .ToDictionary(p => p.Key ?? "Other", p => p.ToList());
        }
    }

    private enum DisplayType
    {
        Categories,
        All
    }

    private async Task GetEmojis()
    {
        if (Source == "local")
        {
            State = State.Loading;

            StoredLocalEmojis = await Api.Emoji.GetAllEmojiAsync();
            FilterEmojis();

            State = StoredLocalEmojis.Count == 0 ? State.Empty : State.Loaded;
        }

        if (Source == "remote")
        {
            if (_displayType is DisplayType.Categories)
            {
                await FetchHosts();
            }
            else
            {
                var res = await Api.Emoji.GetRemoteEmojiAsync(new PaginationQuery());
                PaginationData     = res.Links;
                StoredRemoteEmojis = res.Data;
            }
        }

        if (EmojiFilter.Length == 0)
        {
            switch (Source)
            {
                case "remote":
                    DisplayedEmojis = StoredRemoteEmojis;
                    break;
                case "local":
                    DisplayedEmojis = StoredLocalEmojis;
                    break;
            }
        }
    }

    private async Task FetchHosts()
    {
        // Due to query weirdness, MinID and MaxID are reversed here
        var pq  = new PaginationQuery { MinId = Categories.LastOrDefault(), Limit = 250 };
        var res = await Api.Emoji.GetRemoteEmojiHostsAsync(pq);
        foreach (var el in res)
        {
           var op = Categories.Add(el.Entity);
           if (!op) Logger.LogWarning($"Duplicate host entry: {el.Entity}");
        }

        if (EmojiFilter.Length == 0) DisplayedCategories = Categories;
    }

    private async Task FetchMore()
    {
        if (PaginationData?.Next == null) return;
        var pq  = new PaginationQuery { MaxId = PaginationData.Next?.Split('=')[1], Limit = PaginationData.Limit };
        var res = await Api.Emoji.GetRemoteEmojiAsync(pq);
        PaginationData = res.Links;
        StoredRemoteEmojis.AddRange(res.Data);
        if (EmojiFilter.Length == 0)
        {
            DisplayedEmojis = StoredRemoteEmojis;
        }

        StateHasChanged();
    }

    // The <InputFile> Component is hidden, and triggered by a sepperate button.
    // That way we get it's functionality, without the styling limitations of the InputFile component
    private async Task OpenUpload()
    {
        await _module.InvokeVoidAsync("openUpload", UploadInput.Element);
    }

    private async Task OpenImport()
    {
        await _module.InvokeVoidAsync("openUpload", ImportInput.Element);
    }

    private async Task Upload(InputFileChangeEventArgs e)
    {
        if (!e.File.ContentType.StartsWith("image/")) return;
        UploadFile = e.File;
        await Global.PromptDialog?.Prompt(new EventCallback<string?>(this, UploadCallback), Loc["Set emoji name"], "", e.File.Name.Split(".")[0], buttonText: Loc["Upload"])!;
    }

    private async Task UploadCallback(string? name)
    {
        if (name == null) return;

        try
        {
            await Api.Emoji.UploadEmojiAsync(UploadFile, name);
            await Global.NoticeDialog?.Display(Loc["Successfully uploaded {0}", name])!;
            await GetEmojis();
        }
        catch (ApiException e)
        {
            await Global.NoticeDialog?.Display(e.Response.Message ?? Loc["An unknown error occurred"], NoticeDialog.NoticeType.Error)!;
        }
    }

    private async Task Import(InputFileChangeEventArgs e)
    {
        if (e.File.ContentType != "application/zip") return;
        ImportFile = e.File;
        await Global.ConfirmDialog?.Confirm(new EventCallback<bool>(this, ImportCallback), Loc["Import {0}?", e.File.Name], Icons.FileArrowUp, Loc["Import"])!;
    }

    private async Task ImportCallback(bool import)
    {
        if (!import) return;

        try
        {
            await Api.Emoji.ImportEmojiAsync(ImportFile);
            await Global.NoticeDialog?.Display(Loc["Successfully imported emoji pack"])!;
            await GetEmojis();
        }
        catch (ApiException e)
        {
            await Global.NoticeDialog?.Display(e.Response.Message ?? Loc["An unknown error occurred"], NoticeDialog.NoticeType.Error)!;
        }
    }

    protected override async Task OnInitializedAsync()
    {
        _module = await Js.InvokeAsync<IJSObjectReference>("import",
                                                           "./Pages/Moderation/CustomEmojis.razor.js");
        await GetEmojis();
    }
}
