@using AliasVault.Client.Main.Components.Layout
@inject DbService DbService
@inject GlobalLoadingService GlobalLoadingService
@inject GlobalNotificationService GlobalNotificationService
@inject CredentialService CredentialService
@inject IStringLocalizerFactory LocalizerFactory
@using Microsoft.Extensions.Localization

<ClickOutsideHandler OnClose="OnClose" ContentId="passwordSettingsModal">
    <ModalWrapper OnEnter="HandleEnterKey">
        <div id="passwordSettingsModal" class="relative top-20 mx-auto p-5 shadow-lg rounded-md bg-white dark:bg-gray-800 border-2 border-gray-300 dark:border-gray-400">
            <div class="m-2">
                <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white">@Localizer["Title"]</h3>
                <div class="mt-4 space-y-4">
                    <div>
                        <label for="password-length" class="block text-sm font-medium text-gray-700 dark:text-gray-300">@string.Format(Localizer["PasswordLengthLabel"], _workingSettings.Length)</label>
                        <input type="range" id="password-length" min="8" max="64"
                            class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
                            @bind="_workingSettings.Length" @oninput="HandleLengthInput">
                    </div>

                    <div class="flex items-center">
                        <input id="use-lowercase" type="checkbox" class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded dark:bg-gray-700 dark:border-gray-600"
                            @bind="_workingSettings.UseLowercase" @bind:after="OnPasswordSettingsChanged">
                        <label for="use-lowercase" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">@Localizer["IncludeLowercaseLabel"]</label>
                    </div>

                    <div class="flex items-center">
                        <input id="use-uppercase" type="checkbox" class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded dark:bg-gray-700 dark:border-gray-600"
                            @bind="_workingSettings.UseUppercase" @bind:after="OnPasswordSettingsChanged">
                        <label for="use-uppercase" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">@Localizer["IncludeUppercaseLabel"]</label>
                    </div>

                    <div class="flex items-center">
                        <input id="use-numbers" type="checkbox" class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded dark:bg-gray-700 dark:border-gray-600"
                            @bind="_workingSettings.UseNumbers" @bind:after="OnPasswordSettingsChanged">
                        <label for="use-numbers" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">@Localizer["IncludeNumbersLabel"]</label>
                    </div>

                    <div class="flex items-center">
                        <input id="use-special-chars" type="checkbox" class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded dark:bg-gray-700 dark:border-gray-600"
                            @bind="_workingSettings.UseSpecialChars" @bind:after="OnPasswordSettingsChanged">
                        <label for="use-special-chars" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">@Localizer["IncludeSpecialCharsLabel"]</label>
                    </div>

                    <div class="flex items-center">
                        <input id="use-non-ambiguous" type="checkbox" class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded dark:bg-gray-700 dark:border-gray-600"
                            @bind="_workingSettings.UseNonAmbiguousChars" @bind:after="OnPasswordSettingsChanged">
                        <label for="use-non-ambiguous" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">@Localizer["AvoidAmbiguousCharsLabel"]</label>
                    </div>

                    <div class="mt-4">
                        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">@Localizer["PreviewLabel"]</label>
                        <div class="mt-1 flex">
                            <CopyPasteFormRow Id="preview-password" Value="@_previewPassword" />
                            <button type="button" class="ml-2 px-3 py-2 text-sm text-gray-500 dark:text-white bg-gray-200 hover:bg-gray-300 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-md dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800" @onclick="RefreshPreview">
                                <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
                                </svg>
                            </button>
                        </div>
                    </div>

                    <div class="flex @(IsTemporary ? "justify-between" : "justify-end") pt-4 gap-2">
                        <button type="button" class="px-4 py-2 bg-gray-200 text-gray-800 rounded-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-400 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600" @onclick="OnClose">
                            @Localizer["CancelButton"]
                        </button>
                        @if (IsTemporary)
                        {
                            <button type="button" class="px-4 py-2 bg-gray-200 text-gray-800 dark:bg-gray-700 dark:text-white rounded-md hover:bg-gray-300 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-500" @onclick="OnSaveTemporary">
                                @Localizer["UseJustOnceButton"]
                            </button>
                        }
                        <button type="button" id="save-button" class="px-4 py-2 bg-primary-600 text-white rounded-md hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-primary-700 dark:hover:bg-primary-600" @onclick="OnSaveGlobal">
                            @Localizer["SaveGloballyButton"]
                        </button>
                    </div>
                </div>
            </div>
        </div>
    </ModalWrapper>
</ClickOutsideHandler>

@code {
    private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Settings.PasswordSettingsPopup", "AliasVault.Client");

    /// <summary>
    /// The PasswordSettings to mutate.
    /// </summary>
    [Parameter]
    public PasswordSettings PasswordSettings { get; set; } = new();

    /// <summary>
    /// Whether temporary change is allowed. If true, component will show both global and temporary options.
    /// If false, only global settings are available.
    /// </summary>
    [Parameter]
    public bool IsTemporary { get; set; }

    /// <summary>
    /// Callback invoked when settings have been changed, with a boolean indicating if it's a global save.
    /// </summary>
    [Parameter]
    public EventCallback<(PasswordSettings Settings, string GeneratedPassword)> OnSaveSettings { get; set; }

    /// <summary>
    /// Callback invoked when popup is closed.
    /// </summary>
    [Parameter]
    public EventCallback OnClose { get; set; }

    /// <summary>
    /// Local copy of password settings that are currently being edited.
    /// </summary>
    private PasswordSettings _workingSettings = new();

    /// <summary>
    /// The preview password.
    /// </summary>
    private string _previewPassword = string.Empty;

    /// <inheritdoc />
    protected override async Task OnInitializedAsync()
    {
        // Clone the settings to avoid reference issues
        _workingSettings = new PasswordSettings
        {
            Length = PasswordSettings.Length,
            UseLowercase = PasswordSettings.UseLowercase,
            UseUppercase = PasswordSettings.UseUppercase,
            UseNumbers = PasswordSettings.UseNumbers,
            UseSpecialChars = PasswordSettings.UseSpecialChars,
            UseNonAmbiguousChars = PasswordSettings.UseNonAmbiguousChars
        };

        await RefreshPreview();
    }

    /// <summary>
    /// Refresh the preview password.
    /// </summary>
    private async Task RefreshPreview()
    {
        try
        {
            _previewPassword = await CredentialService.GenerateRandomPasswordAsync(_workingSettings);
        }
        catch
        {
            // If password generation fails, ignore it. This can happen if the settings are invalid.
        }
    }

    /// <summary>
    /// Handle input from the password length input.
    /// </summary>
    private async Task HandleLengthInput(ChangeEventArgs e)
    {
        int newLength;
        if (int.TryParse(e.Value?.ToString(), out newLength))
        {
            _workingSettings.Length = newLength;
            await RefreshPreview();
        }
    }

    /// <summary>
    /// Handle changes to the password settings.
    /// </summary>
    private async Task OnPasswordSettingsChanged()
    {
        await RefreshPreview();
    }

    /// <summary>
    /// Persist changed password settings globally in vault.
    /// </summary>
    private async Task OnSaveGlobal()
    {
        // Save globally to DB.
        GlobalLoadingService.Show();
        var settingsJson = System.Text.Json.JsonSerializer.Serialize(_workingSettings);
        await DbService.Settings.SetSettingAsync("PasswordGenerationSettings", settingsJson);
        GlobalLoadingService.Hide();
        GlobalNotificationService.AddSuccessMessage(Localizer["SettingsUpdatedMessage"], true);

        // Notify parent with both settings and the generated password.
        await OnSaveSettings.InvokeAsync((_workingSettings, _previewPassword));
        await OnClose.InvokeAsync();
    }

    /// <summary>
    /// Do not persist changes in vault but just return the new settings to the parent component.
    /// </summary>
    private async Task OnSaveTemporary()
    {
        await OnSaveSettings.InvokeAsync((_workingSettings, _previewPassword));
        await OnClose.InvokeAsync();
    }

    /// <summary>
    /// Handle Enter key press, submits the form based on context.
    /// </summary>
    private async Task HandleEnterKey()
    {
        if (IsTemporary)
        {
            await OnSaveTemporary();
        }
        else
        {
            await OnSaveGlobal();
        }
    }
}
