using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using RpgRoller.Contracts;

namespace RpgRoller.Components.Pages.HomeControls;

[ExcludeFromCodeCoverage]
public partial class CampaignLogPanel
{
    private sealed record EventBadgeView(string Label, string Tone);

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        var currentLastRollId = CampaignLog.LastOrDefault()?.RollId;
        if (IsCampaignDataLoading || CampaignLog.Count == 0)
        {
            LastRenderedLogCount = CampaignLog.Count;
            LastRenderedLogRollId = currentLastRollId;
            return;
        }

        if (firstRender || CampaignLog.Count > LastRenderedLogCount || currentLastRollId != LastRenderedLogRollId)
        {
            try
            {
                await JS.InvokeVoidAsync("rpgRollerApi.scrollElementToBottom", LogFeedRef);
            }
            catch (JSDisconnectedException)
            {
            }
            catch (InvalidOperationException ex) when (ex.Message.Contains("statically rendered",
                                                           StringComparison.OrdinalIgnoreCase))
            {
            }
        }

        LastRenderedLogCount = CampaignLog.Count;
        LastRenderedLogRollId = currentLastRollId;
    }

    private async Task SubmitCustomRollAsync()
    {
        CustomRollState.ResetValidation();

        var expression = CustomRollState.Model.Expression.Trim();
        if (string.IsNullOrWhiteSpace(expression))
        {
            SetCustomRollError("Enter a roll expression first.");
            return;
        }

        if (!SelectedCharacterId.HasValue)
        {
            SetCustomRollError("Select a character to make a custom roll.");
            return;
        }

        IsSubmittingCustomRoll = true;
        try
        {
            var roll = await ApiClient.RequestAsync<RollResult>("POST",
                $"/api/characters/{SelectedCharacterId.Value}/custom-rolls", new
                {
                    expression,
                    visibility = NormalizedRollVisibility
                });

            CustomRollState.Model.Expression = string.Empty;
            CustomRollState.ResetValidation();
            CustomRollInputVersion += 1;
            await CustomRollCreated.InvokeAsync(roll);
            await JS.InvokeVoidAsync("rpgRollerApi.clearInputValue", CustomRollInputRef);
            await InvokeAsync(StateHasChanged);
        }
        catch (ApiRequestException ex) when
            (string.Equals(ex.ErrorCode, "invalid_expression", StringComparison.Ordinal))
        {
            SetCustomRollError(ex.Message);
            await InvokeAsync(StateHasChanged);
        }
        catch (ApiRequestException ex)
        {
            await ErrorOccurred.InvokeAsync(ex.Message);
        }
        finally
        {
            IsSubmittingCustomRoll = false;
        }
    }

    private void SetCustomRollError(string message)
    {
        CustomRollState.Errors["expression"] = message;
    }

    private static IReadOnlyList<EventBadgeView> GetEventBadges(CampaignLogListEntry entry)
    {
        return (entry.EventBadges ?? []).Select(ToEventBadgeView).Where(badge => badge is not null)
            .Cast<EventBadgeView>().ToArray();
    }

    private static bool HasSummary(CampaignLogListEntry entry)
    {
        return (entry.EventBadges?.Length ?? 0) > 0 || !string.IsNullOrWhiteSpace(entry.SummaryText);
    }

    private static EventBadgeView? ToEventBadgeView(string code)
    {
        return code switch
        {
            "w6" => new("Wild 6", "positive"),
            "w1" => new("Wild 1", "danger"),
            "n20" => new("Nat 20", "positive"),
            "n1" => new("Nat 1", "danger"),
            "rf" => new("Fumble", "danger"),
            "r100" => new("100", "rare"),
            "r66" => new("66", "rare"),
            "rs5" => new("Retry +5", "rare"),
            "rs10" => new("Retry +10", "rare"),
            _ => null
        };
    }

    private static string LogEntryCssClass(CampaignLogListEntry entry, bool isExpanded, bool isFresh)
    {
        var classes = new List<string> { entry.VisibilityStyle };
        if (isExpanded)
            classes.Add("expanded");

        if (isFresh)
            classes.Add("fresh");

        return string.Join(" ", classes);
    }

    [Inject] private IJSRuntime JS { get; set; } = null!;

    [Inject] private RpgRollerApiClient ApiClient { get; set; } = null!;

    private ElementReference LogPanelRef { get; set; }
    private ElementReference LogFeedRef { get; set; }
    private ElementReference CustomRollInputRef { get; set; }
    private int LastRenderedLogCount { get; set; }
    private Guid? LastRenderedLogRollId { get; set; }
    private int CustomRollInputVersion { get; set; }
    private FormState<CustomRollFormModel> CustomRollState { get; } = new();
    private bool IsSubmittingCustomRoll { get; set; }

    [Parameter] public bool IsCampaignDataLoading { get; set; }

    [Parameter] public IReadOnlyList<CampaignLogListEntry> CampaignLog { get; set; } = [];

    [Parameter] public Guid? ExpandedRollId { get; set; }

    [Parameter] public Guid? FreshRollId { get; set; }

    [Parameter] public EventCallback<Guid> ToggleRollDetailRequested { get; set; }

    [Parameter] public Func<Guid, CampaignRollDetail?> ResolveRollDetail { get; set; } = _ => null;

    [Parameter] public Func<Guid, bool> IsRollDetailLoading { get; set; } = _ => false;

    [Parameter] public Func<Guid, string?> GetRollDetailError { get; set; } = _ => null;

    [Parameter] public Guid? SelectedCharacterId { get; set; }

    [Parameter] public string? SelectedCharacterName { get; set; }

    [Parameter] public string SelectedCampaignRulesetId { get; set; } = string.Empty;

    [Parameter] public string RollVisibility { get; set; } = "public";

    [Parameter] public Func<string>? ResolveRollVisibility { get; set; }

    [Parameter] public bool IsMutating { get; set; }

    [Parameter] public EventCallback<RollResult> CustomRollCreated { get; set; }

    [Parameter] public EventCallback<string> ErrorOccurred { get; set; }

    private bool HasCustomRollError => CustomRollState.Errors.ContainsKey("expression");
    private string? CustomRollErrorMessage => CustomRollState.Errors.GetValueOrDefault("expression");

    private bool IsCustomRollDisabled =>
        IsCampaignDataLoading || IsMutating || IsSubmittingCustomRoll || !SelectedCharacterId.HasValue;

    private string CustomRollInputCssClass => HasCustomRollError ? "custom-roll-input error" : "custom-roll-input";
    private string? CustomRollInputTitle => HasCustomRollError ? CustomRollErrorMessage : null;
    private string CustomRollErrorElementId => "custom-roll-expression-error";
    private string? CustomRollInputDescribedBy => HasCustomRollError ? CustomRollErrorElementId : null;

    private string CustomRollPlaceholder => SelectedCampaignRulesetId.ToLowerInvariant() switch
    {
        RulesetFormHelpers.RulesetIds.D6 => "e.g. 5D+4",
        RulesetFormHelpers.RulesetIds.Dnd5e => "e.g. 2d12+2",
        RulesetFormHelpers.RulesetIds.Rolemaster => "e.g. d10, 15d10, d100!+85",
        _ => "Enter a roll expression"
    };

    private string CustomRollStatusText =>
        SelectedCharacterId.HasValue && !string.IsNullOrWhiteSpace(SelectedCharacterName)
            ? $"For {SelectedCharacterName} • Uses {RollVisibilityLabel.ToLowerInvariant()} visibility"
            : $"Select a character to enable • {RollVisibilityLabel} visibility selected";

    private string CustomRollHelpText => SelectedCampaignRulesetId.ToLowerInvariant() switch
    {
        RulesetFormHelpers.RulesetIds.D6 =>
            "Uses the campaign ruleset. D6 custom rolls use one wild die and allow fumbles.",
        RulesetFormHelpers.RulesetIds.Rolemaster =>
            $"{RulesetFormHelpers.RolemasterExampleText()}. Logged for the selected character.",
        _ => "Uses the selected campaign ruleset and current visibility."
    };

    private string RollVisibilityLabel =>
        string.Equals(NormalizedRollVisibility, "private", StringComparison.OrdinalIgnoreCase) ? "Private" : "Public";

    private string NormalizedRollVisibility =>
        string.Equals(ResolveRollVisibility?.Invoke() ?? RollVisibility, "private", StringComparison.OrdinalIgnoreCase)
            ? "private"
            : "public";

    private string CustomRollExpression
    {
        get => CustomRollState.Model.Expression;
        set
        {
            CustomRollState.Model.Expression = value;
            if (HasCustomRollError)
                CustomRollState.ResetValidation();
        }
    }
}