@namespace OpenArchival.Blazor.AdminPages.Shared

@using Microsoft.AspNetCore.Components.Web
@using System.ComponentModel.DataAnnotations
@using Microsoft.Extensions.Logging
@using MudBlazor
@using global::OpenArchival.DataAccess
@using OpenArchival.Blazor.CustomComponents

@inject IDialogService DialogService
@inject NavigationManager NavigationManager;
@inject IArchiveCategoryProvider CategoryProvider;
@inject ArtifactEntrySharedHelpers Helpers;
@inject ILogger<AddArchiveGroupingComponent> _logger;

<MudPaper Class="pa-4 ma-2 rounded" Elevation="3">
    <MudText Typo="Typo.h5" Color="Color.Primary">Add an Archive Item</MudText>

    <MudDivider DividerType="DividerType.Middle"></MudDivider>

    @foreach (var result in ValidationResults)
    {
        <MudAlert Severity="Severity.Error">@result.ErrorMessage</MudAlert>
    }

    <MudGrid Justify="Justify.Center" Class="pt-4">    
        <MudItem>
            <MudAutocomplete 
                T="ArchiveCategory" 
                ToStringFunc="@(val => val?.Name)" 
                Label="Category" 
                @bind-Value="Model.Category" 
                @bind-Value:after=OnCategoryChanged 
                SearchFunc="SearchCategory"
                CoerceValue=false 
                CoerceText=false
                />
        </MudItem>

        <MudItem>
            <MudFab Color="Color.Primary" StartIcon="@Icons.Material.Filled.Add" OnClick="OnAddCategoryClicked"/>
        </MudItem>
        
    </MudGrid>
</MudPaper>

<div @ref="_formDiv" style="@_formDivStyle">

    <MudPaper Class="pa-4 ma-2 rounded" Elevation="3">
        <MudText Typo="Typo.h6" Color="Color.Primary" Class="pt-4 pb-0">Archive Item Identifier</MudText>
        <MudDivider DividerType="DividerType.Middle"></MudDivider>
        <IdentifierTextBox @ref="_identifierTextBox" IdentifierFields="@Model.IdentifierFieldValues"></IdentifierTextBox>
    </MudPaper>

    <MudPaper Class="pa-4 ma-2 rounded" Elevation="3">
        <MudText Typo="Typo.h6" Color="Color.Primary" Class="pt-4 pb-0">Grouping Title</MudText>
        <MudDivider DividerType="DividerType.Middle"></MudDivider>
        <MudTextField 
            T="string" 
            For="@(() => Model.Title)" 
            Placeholder="Grouping Title" 
            @bind-Value=Model.Title
            @bind-Value:after="OnChanged"></MudTextField>

        <MudText Typo="Typo.h6" Color="Color.Primary" Class="pt-4 pb-0">Grouping Description</MudText>
        <MudDivider DividerType="DividerType.Middle"></MudDivider>
        <MudTextField 
            T="string" 
            For="@(() => Model.Description)" 
            Lines="5" 
            Placeholder="Grouping Description" 
            @bind-Value=Model.Description
            @bind-Value:after="OnChanged"></MudTextField>

        <MudText Typo="Typo.h6" Color="Color.Primary" Class="pt-4 pb-0">Grouping Type</MudText>
        <MudDivider DividerType="DividerType.Middle"></MudDivider>
        <MudAutocomplete 
            For="@(() => Model.Type)" 
            T="string" 
            Label="Artifact Type" 
            Class="pt-0 mt-0 pl-2 pr-2" 
            @bind-Value=Model.Type 
            @bind-Value:after="OnChanged"
            SearchFunc="Helpers.SearchItemTypes" 
            CoerceValue=true></MudAutocomplete>
    </MudPaper>

    <MudPaper Class="pa-4 ma-2 rounded" Elevation="3">
        <UploadDropBox 
            @ref="@_uploadComponent"
            FilesUploaded="OnFilesUploaded" 
            ClearClicked="OnClearFilesClicked"
            ExistingFiles="ExistingFiles"></UploadDropBox>
    </MudPaper>
    @if (Model is not null)
    {
        @for (int index = 0; index <  Model.ArtifactEntries.Count; ++index)
        {
            // Capture the current item in a local variable for the lambda
            var currentEntry = Model.ArtifactEntries[index];

            <ArchiveEntryCreatorCard Model="currentEntry"
                                     ModelChanged="(updatedEntry) => HandleEntryUpdate(currentEntry, updatedEntry)"
                                     InputsChanged="OnChanged"
                                     @key="currentEntry"
                                     ArtifactEntryIndex="index"
                                     OnEntryDeletedClicked="OnDeleteEntryClicked"/>
        }    
    }
</div>

<MudGrid Justify="Justify.FlexEnd" Class="pt-6">
    <MudItem>
        <MudCheckBox Label="Publicly Visible" T="bool" @bind-Value=Model.IsPublicallyVisible></MudCheckBox>
    </MudItem>
        
    @if (FormButtonsEnabled)
    {
        <MudItem Class="pr-0">
                <MudButton Color="Color.Primary" Variant="Variant.Filled" Class="ml-4" OnClick="CancelClicked">Cancel</MudButton>
            </MudItem>

            <MudItem Class="pl-2">
                <MudButton Color="Color.Primary" Variant="Variant.Filled" Class="ml-4" OnClick="PublishClicked" Disabled="@(!IsValid)" >Publish</MudButton>
            </MudItem>
    }
</MudGrid>


    @code {
    [Parameter]
    public bool ClearOnPublish { get; set; } = true;

    /// <summary>
    /// The URI to navigate to if cancel is pressed. null to navigate to no page
    /// </summary>
    [Parameter]
    public string? BackLink { get; set; } = null;

    /// <summary>
    /// The URI to navigate to if publish is pressed, null to navigate to no page
    /// </summary>
    [Parameter]
    public string? ForwardLink { get; set; } = null;

    /// <summary>
    /// Called when publish is clicked
    /// </summary>
    [Parameter]
    public EventCallback<ArtifactGroupingValidationModel> GroupingPublished { get; set; }

    /// <summary>
    ///  The model to display on the form
    /// </summary>
    [Parameter]
    public ArtifactGroupingValidationModel  Model { get; set; } = new();

    /// <summary>
    /// Determines if the cancel and publish buttons should be show to the user or if the containing component will
    /// handle their functionality (ie if used in a dialog and you want to use the dialog buttons instead of this component's handlers)
    /// </summary>
    [Parameter]
    public bool FormButtonsEnabled { get; set; } = true;

    private UploadDropBox _uploadComponent = default!;

    private IdentifierTextBox _identifierTextBox = default!;

    private ElementReference _formDiv = default!;

    private bool _isFormDivVisible = false;

    private string _formDivStyle => _isFormDivVisible ? "" : "display: none;";

    public List<string> DatesData { get; set; } = [];

    public List<ArchiveCategory> Categories { get; set; } = new();

    private List<FilePathListing> _filePathListings = new();

    private bool _categorySelected = false;

    public bool IsValid { get; set; } = false;

    public List<ValidationResult> ValidationResults { get; private set; } = [];

    // Used to store the files that have already been uploaded if this component is being displayed
    // with a filled in model. Used to populate the upload drop box
    private List<FilePathListing> ExistingFiles { get; set; } = new();

    protected override async Task OnParametersSetAsync()
    {
        // Ensure to reload the component if a model has been supplied so that the full
        // component will render
        if (Model?.Category is not null)
        {
            await OnCategoryChanged();
        }

        if (Model is not null && Model.Category is not null)
        {
            // The data entry should only be shown if a category has been selected
            _isFormDivVisible = true;
        } else
        {
            _isFormDivVisible = false;
        }

        if (Model is not null)
        {
            var incomingFiles = Model.ArtifactEntries
                             .Where(e => e.Files.Any())
                             .Select(e => e.Files[0])
                             .ToList();

            if (ExistingFiles.Count != incomingFiles.Count)
            {
                ExistingFiles = incomingFiles;
            }
        }
    }

    private async Task PublishClicked(MouseEventArgs args)
    {
        var validationContext = new ValidationContext(Model);
        var validationResult = new List<ValidationResult>();

        IsValid = Validator.TryValidateObject(Model, validationContext, validationResult, validateAllProperties:true);
        ArtifactGroupingValidationModel oldModel = Model;
        if (ForwardLink is not null)
        {
            if (IsValid)
            {
                NavigationManager.NavigateTo(ForwardLink);
            }
        }

        if (IsValid && ClearOnPublish)
        {
            Model = new();
            await _uploadComponent.ClearClicked.InvokeAsync();
            StateHasChanged();
        }
        ValidationResults = new List<ValidationResult>();
        await GroupingPublished.InvokeAsync(oldModel); 
    }

    private void CancelClicked(MouseEventArgs args)
    {
        if (BackLink is not null) {
            NavigationManager.NavigateTo(BackLink);
        }
        else
        {
            throw new ArgumentNullException("No back link provided for the add archive item page.");
        }
    }

    private async Task HandleEntryUpdate(ArtifactEntryValidationModel originalEntry, ArtifactEntryValidationModel updatedEntry)
    {
        // Find the index of the original object 
        var index = Model.ArtifactEntries.IndexOf(originalEntry);

        // If found, replace it with the updated version
        if (index != -1)
        {
            Model.ArtifactEntries[index] = updatedEntry;
        }

        // run the validation logic
        await OnChanged();
    }

    // You can now simplify your OnFilesUploaded method slightly
    private async Task OnFilesUploaded(List<FilePathListing> args)
    {
        _filePathListings = args;

        // This part is tricky. Adding items while iterating can be problematic.
        // A better approach is to create the entries first, then tell Blazor to render.
        var newEntries = new List<ArtifactEntryValidationModel>();
        foreach (var file in args)
        {
            // Associate the file with the entry if needed
            newEntries.Add(new ArtifactEntryValidationModel { Files = [file]});
        }
        Model.ArtifactEntries.AddRange(newEntries);

        // StateHasChanged() is implicitly called by OnChanged() if validation passes
        await OnChanged();
    }

    private async Task OnClearFilesClicked()
    {
        _filePathListings = [];
        Model.ArtifactEntries = [];
        StateHasChanged();
        await OnChanged();
    }
    /*
    async Task OnChanged()
    {
        var validationContext = new ValidationContext(Model);
        var validationResult = new List<ValidationResult>();

        IsValid = Validator.TryValidateObject(Model, validationContext, validationResult, validateAllProperties: true);

        StateHasChanged();
        }
    */

    async Task OnChanged()
    {
        var validationResults = new List<ValidationResult>();
        var validationContext = new ValidationContext(Model);

        IsValid = Validator.TryValidateObject(Model, validationContext, validationResults, validateAllProperties: true);
        ValidationResults = validationResults;

        StateHasChanged();
    }

    async Task OnCategoryChanged()
    {
        if (Model.Category is not null && _identifierTextBox is not null)
        {
            _identifierTextBox.VerifyFormatCategory = Model.Category;
            _isFormDivVisible = true;
            if (!_categorySelected)
            {
                _categorySelected = true;
            }
            StateHasChanged();
        }

        await OnChanged();
    }

    public async Task OnAddCategoryClicked()
    { 
        var options = new DialogOptions { CloseOnEscapeKey = true, BackdropClick = false };
        var dialog = await DialogService.ShowAsync<CategoryCreatorDialog>("Create a Category", options);

        var result = await dialog.Result;

        if (result is not null && !result.Canceled)
        {
            await CategoryProvider.CreateCategoryAsync(CategoryValidationModel.ToArchiveCategory((CategoryValidationModel)result.Data));
            StateHasChanged();
            await OnChanged();
        }
    } 

    private async Task<IEnumerable<ArchiveCategory>> SearchCategory(string value, CancellationToken cancellationToken)
    {
        List<ArchiveCategory> categories; 
        if (string.IsNullOrEmpty(value))
        {
            categories = new(await CategoryProvider.Top(25) ?? []); 
        } 
        else
        {
            categories = new((await CategoryProvider.Search(value) ?? []));
        }

        return categories;
    }

    private async void OnDeleteEntryClicked((int index, string filename) data)
    {
        Model.ArtifactEntries.RemoveAt(data.index);
        _uploadComponent.RemoveFile(data.filename);
        StateHasChanged();
    }
}
