@inject DbService DbService
@inject NavigationManager NavigationManager
@inject KeyboardShortcutService KeyboardShortcutService
@inject JsInteropService JsInteropService
@inject IStringLocalizerFactory LocalizerFactory
@inject LanguageService LanguageService
@implements IAsyncDisposable
@using Microsoft.Extensions.Localization
@using System.Timers

<div class="relative" id="searchWidgetContainer">
    <input
        id="searchWidget"
        type="text"
        placeholder="@Localizer["SearchVaultPlaceholder"]"
        autocomplete="off"
        class="w-full px-4 py-2 text-gray-700 bg-white border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:focus:ring-primary-500"
        @bind-value="SearchTerm"
        @oninput="SearchTermChanged"
        @onfocus="OnFocusClick"
        @onclick="OnFocusClick"
        @onkeydown="HandleKeyDown"/>

    @if (ShowHelpText || ShowResults)
    {
        <ClickOutsideHandler OnClose="OnClose" ContentId="searchWidgetContainer">
            @if (ShowHelpText)
            {
                <div class="absolute z-10 w-full mt-1 bg-white rounded-md shadow-lg dark:bg-gray-800 p-2 text-sm text-gray-600 dark:text-gray-400">
                    @if (string.IsNullOrEmpty(SearchTerm))
                    {
                        <p>@Localizer["SearchHelpText"]</p>
                    }
                    else if (SearchTerm.Length == 1)
                    {
                        <p>@Localizer["SearchTooShortMessage"]</p>
                    }
                    else
                    {
                        <p>@string.Format(Localizer["SearchingForMessage"], SearchTerm)</p>
                    }
                </div>
            }

            @if (ShowResults && SearchTerm.Length >= 2)
            {
                @if (_isLoading)
                {
                    <div class="absolute z-10 w-screen left-0 sm:left-auto sm:w-full mt-1 bg-white rounded-md shadow-lg dark:bg-gray-800 text-sm text-gray-600 dark:text-gray-400">
                        <div class="px-4 py-2 flex items-center">
                            <svg class="animate-spin -ml-1 mr-3 h-4 w-4 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
                                <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
                                <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
                            </svg>
                            @Localizer["SearchingMessage"]
                        </div>
                    </div>
                }
                else if (SearchResults.Any())
                {
                    <div class="absolute z-10 w-screen left-0 sm:left-auto sm:w-full mt-1 bg-white rounded-md shadow-lg dark:bg-gray-800 text-sm text-gray-600 dark:text-gray-400">
                        @for (int i = 0; i < SearchResults.Count; i++)
                        {
                            var result = SearchResults[i];
                            <div
                                class="search-result @(i == SelectedIndex ? "bg-gray-100 dark:bg-gray-700" : "") px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center"
                                @onclick="() => SelectResult(result)">
                                <DisplayFavicon FaviconBytes="@result.Service.Logo" Width="24" />
                                <div class="ml-2">
                                    <div>@result.Service.Name</div>
                                    @if (!string.IsNullOrEmpty(result.Alias.Email))
                                    {
                                        <span class="text-gray-500">(@result.Alias.Email)</span>
                                    }
                                    else if (!string.IsNullOrEmpty(result.Username))
                                    {
                                        <span class="text-gray-500">(@result.Username)</span>
                                    }
                                </div>
                            </div>
                        }
                    </div>
                }
                else
                {
                    <div class="absolute z-10 w-screen left-0 sm:left-auto sm:w-full mt-1 bg-white rounded-md shadow-lg dark:bg-gray-800 text-sm text-gray-600 dark:text-gray-400">
                        <div class="px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700">
                            @Localizer["NoResultsFoundMessage"]
                        </div>
                    </div>
                }
            }
        </ClickOutsideHandler>
    }
</div>

@code {
    private IStringLocalizer? _localizer;
    private IStringLocalizer Localizer => _localizer ??= LocalizerFactory.Create("Components.Main.Widgets.SearchWidget", "AliasVault.Client");

    private string SearchTerm { get; set; } = string.Empty;
    private List<Credential> SearchResults { get; set; } = new();
    private bool ShowResults { get; set; }
    private bool ShowHelpText { get; set; }
    private int SelectedIndex { get; set; } = -1;
    private Timer? _searchTimer;
    private bool _isSearching = false;
    private bool _isLoading = false;

    /// <inheritdoc />
    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        await KeyboardShortcutService.UnregisterShortcutAsync("gs");
        await KeyboardShortcutService.UnregisterShortcutAsync("gf");
        NavigationManager.LocationChanged -= ResetSearchField;
        LanguageService.LanguageChanged -= OnLanguageChanged;
        _searchTimer?.Dispose();
    }

    /// <inheritdoc />
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await base.OnAfterRenderAsync(firstRender);

        if (firstRender)
        {
            await KeyboardShortcutService.RegisterShortcutAsync("gs", FocusSearchField);
            await KeyboardShortcutService.RegisterShortcutAsync("gf", FocusSearchField);
            NavigationManager.LocationChanged += ResetSearchField;
            LanguageService.LanguageChanged += OnLanguageChanged;

            // Initialize search timer debounce.
            _searchTimer = new Timer(100);
            _searchTimer.Elapsed += async (sender, e) => await PerformSearchAsync();
            _searchTimer.AutoReset = false;
        }
    }

    /// <summary>
    /// Handles language change events and triggers component refresh.
    /// </summary>
    /// <param name="languageCode">The new language code.</param>
    private void OnLanguageChanged(string languageCode)
    {
        // Reset the localizer to force it to use the new language
        _localizer = null;
        InvokeAsync(StateHasChanged);
    }

    private void OnFocusClick()
    {
        // Ensure popup stays open when clicking inside the input field
        ShowHelpText = true;
        ShowResults = true;
        StateHasChanged();
    }

    private void OnClose()
    {
        ShowHelpText = false;
        ShowResults = false;
        StateHasChanged();
    }

    private void SearchTermChanged(ChangeEventArgs e)
    {
        SearchTerm = e.Value?.ToString() ?? string.Empty;

        // Reset timer for debounced search
        _searchTimer?.Stop();
        _searchTimer?.Start();

        // Update UI immediately for short search terms
        if (SearchTerm.Length < 2)
        {
            SearchResults.Clear();
            SelectedIndex = -1;
            _isLoading = false;
            StateHasChanged();
        }
        else
        {
            // For longer search terms, set loading state but don't clear results yet
            // This prevents the "no results found" message from showing briefly
            _isLoading = true;
            StateHasChanged();
        }
    }

    private async Task PerformSearchAsync()
    {
        if (_isSearching) return;

        _isSearching = true;
        try
        {
            var context = await DbService.GetDbContextAsync();
            if (SearchTerm.Length >= 2)
            {
                var searchTerms = SearchTerm.Trim().ToLowerInvariant().Split(' ', StringSplitOptions.RemoveEmptyEntries);

                var query = context.Credentials
                    .Include(x => x.Service)
                    .Include(x => x.Alias)
                    .Where(x => !x.IsDeleted)
                    .AsQueryable();

                foreach (var term in searchTerms)
                {
                    // We filter credentials by searching in the following fields:
                    // - Service name
                    // - Username
                    // - Alias email
                    // - Service URL
                    // - Notes
                    query = query.Where(x =>
                        (x.Service.Name != null && EF.Functions.Like(x.Service.Name.ToLower(), $"%{term}%")) ||
                        (x.Alias.Email != null && EF.Functions.Like(x.Alias.Email.ToLower(), $"%{term}%")) ||
                        (x.Username != null && EF.Functions.Like(x.Username.ToLower(), $"%{term}%")) ||
                        (x.Service.Url != null && EF.Functions.Like(x.Service.Url.ToLower(), $"%{term}%")) ||
                        (x.Notes != null && EF.Functions.Like(x.Notes.ToLower(), $"%{term}%"))
                    );
                }

                SearchResults = await query.Take(10).ToListAsync();

                // Select first entry by default so when pressing enter, the first result is immediately selected.
                SelectedIndex = SearchResults.Count > 0 ? 0 : -1;
            }
            else
            {
                SearchResults.Clear();
                SelectedIndex = -1;
            }

            _isLoading = false;
            await InvokeAsync(StateHasChanged);
        }
        finally
        {
            _isSearching = false;
        }
    }

    private async Task HandleKeyDown(KeyboardEventArgs e)
    {
        switch (e.Key)
        {
            case "ArrowDown":
                SelectedIndex = Math.Min(SelectedIndex + 1, SearchResults.Count - 1);
                StateHasChanged();
                break;
            case "ArrowUp":
                SelectedIndex = Math.Max(SelectedIndex - 1, -1);
                StateHasChanged();
                break;
            case "Enter":
                if (SelectedIndex >= 0 && SelectedIndex < SearchResults.Count)
                {
                    await SelectResult(SearchResults[SelectedIndex]);
                }
                break;
        }
    }

    private async Task SelectResult(Credential credential)
    {
        await JsInteropService.BlurElementById("searchWidget");
        NavigationManager.NavigateTo($"/credentials/{credential.Id}");
    }

    private void ResetSearchField(object? sender, LocationChangedEventArgs e)
    {
        SearchTerm = string.Empty;
        SearchResults.Clear();
        SelectedIndex = -1;
        ShowHelpText = false;
        ShowResults = false;
        _isLoading = false;
        _searchTimer?.Stop();
        StateHasChanged();
    }

    private async Task FocusSearchField()
    {
        await JsInteropService.FocusElementById("searchWidget");
    }
}
