@using System
@using System.Collections.Generic
@using System.Linq
@using Microsoft.JSInterop
@using RolemasterDb.App.Domain
@using RolemasterDb.App.Features
@implements IAsyncDisposable
@inject IJSRuntime JSRuntime

<div class="critical-editor-backdrop"
     @onpointerdown="HandleBackdropPointerDown"
     @onpointerup="HandleBackdropPointerUp"
     @onpointercancel="HandleBackdropPointerCancel">
    <div class="critical-editor-dialog"
         @onpointerdown="HandleDialogPointerDown"
         @onpointerup="HandleDialogPointerUp"
         @onpointercancel="HandleDialogPointerCancel"
         @onpointerdown:stopPropagation="true"
         @onpointerup:stopPropagation="true"
         @onpointercancel:stopPropagation="true">
        <header class="critical-editor-header">
            <div>
                @if (Model is not null)
                {
                    <h3 class="panel-title">Edit Result Card</h3>
                    <p class="muted critical-editor-meta">
                        <strong>@Model.TableName</strong>
                        <span> · Roll band <strong>@Model.RollBand</strong></span>
                        <span> · Severity <strong>@Model.ColumnLabel</strong></span>
                        @if (!string.IsNullOrWhiteSpace(Model.GroupLabel))
                        {
                            <span> · Variant <strong>@Model.GroupLabel</strong></span>
                        }
                    </p>
                }
                else
                {
                    <h3 class="panel-title">Edit Result Card</h3>
                }
            </div>
            <button type="button" class="btn btn-link critical-editor-close" @onclick="OnClose">Close</button>
        </header>

        @if (IsLoading)
        {
            <div class="critical-editor-body">
                <p class="muted">Loading editor...</p>
            </div>
        }
        else if (!string.IsNullOrWhiteSpace(LoadErrorMessage))
        {
            <div class="critical-editor-body">
                <p class="error-text critical-editor-error">@LoadErrorMessage</p>
            </div>
        }
        else if (Model is not null)
        {
            <EditForm @key="Model" Model="Model" OnSubmit="HandleSubmitAsync" class="critical-editor-form">
                <div class="critical-editor-body">
                    @if (!string.IsNullOrWhiteSpace(ReparseErrorMessage))
                    {
                        <p class="error-text critical-editor-error">@ReparseErrorMessage</p>
                    }

                    @if (!string.IsNullOrWhiteSpace(SaveErrorMessage))
                    {
                        <p class="error-text critical-editor-error">@SaveErrorMessage</p>
                    }

                    <section class="critical-editor-section">
                        <div class="critical-editor-section-header">
                            <div>
                                <h4>Quick Parse Input</h4>
                                <p class="muted">First line is the result prose. Later lines are base affixes or <code>condition: ...</code> lines with comma-separated shorthand.</p>
                            </div>
                            <button
                                type="button"
                                class="btn-ritual"
                                @onclick="HandleReparseClickAsync"
                                @onclick:stopPropagation="true"
                                @onclick:preventDefault="true"
                                disabled="@(IsSaving || IsReparsing)">
                                @(IsReparsing ? "Generating..." : "Generate From Quick Input")
                            </button>
                        </div>
                        <div class="field-shell">
                            <label>Quick Parse Input</label>
                            <textarea
                                class="input-shell critical-editor-textarea tall"
                                value="@Model.QuickParseInput"
                                @oninput="HandleQuickParseInputChanged"></textarea>
                        </div>
                        <p class="muted critical-editor-advanced-hint">Example: <code>Foe brings his guard up, frightened by your display.</code> then <code>+5, 1mp</code> or <code>w/o shield: glancing blow, +15, 3s, 3np</code>.</p>
                        <div class="critical-editor-quick-legend">
                            @foreach (var entry in QuickParseLegendEntries)
                            {
                                <div class="critical-editor-quick-legend-item">
                                    <code>@entry.Token</code>
                                    <span class="critical-editor-quick-legend-equals">=</span>
                                    <AffixBadgeList Effects="@entry.Effects" />
                                </div>
                            }
                        </div>
                        @if (Model.ValidationMessages.Count > 0)
                        {
                            <p class="muted critical-editor-advanced-hint">@GetParserNoteSummary(Model.ValidationMessages.Count)</p>
                        }
                        @if (HasComparisonDifferences(Model, ComparisonBaseline))
                        {
                            <p class="muted critical-editor-advanced-hint">Fresh generation differs from the current edited card. Review Generated Compare before saving if you want to keep the overrides.</p>
                        }
                        <div class="field-shell">
                            <label>Result Text</label>
                            <InputTextArea class="input-shell critical-editor-textarea compact" @bind-Value="Model.DescriptionText" @bind-Value:after="MarkDescriptionOverridden" />
                        </div>
                    </section>

                    <section class="critical-editor-section">
                        <div class="critical-editor-section-header">
                            <div>
                                <h4>Base Effects</h4>
                                <p class="muted">Edit the badges and values that appear on the main result.</p>
                            </div>
                            <button type="button" class="btn-ritual critical-editor-compact-button" @onclick="AddBaseEffect">Add Effect</button>
                        </div>
                        @if (Model.Effects.Count == 0)
                        {
                            <p class="muted">No base effects on this result yet.</p>
                        }
                        else
                        {
                            <div class="critical-editor-inline-list">
                                @for (var index = 0; index < Model.Effects.Count; index++)
                                {
                                    var i = index;
                                    var effect = Model.Effects[i];
                                    <div class="critical-editor-inline-row">
                                        @InlineEffectRow(effect, () => RemoveBaseEffect(i))
                                    </div>
                                }
                            </div>
                        }
                    </section>

                    <section class="critical-editor-section">
                        <div class="critical-editor-section-header">
                            <div>
                                <h4>Conditions</h4>
                                <p class="muted">Keep alternate outcomes compact and easy to scan.</p>
                            </div>
                            <button type="button" class="btn-ritual critical-editor-compact-button" @onclick="AddBranch">Add Condition</button>
                        </div>
                        @if (Model.Branches.Count == 0)
                        {
                            <p class="muted">No alternate condition cards on this result yet.</p>
                        }
                        else
                        {
                            @for (var index = 0; index < Model.Branches.Count; index++)
                            {
                                var i = index;
                                var branch = Model.Branches[i];
                                <div class="critical-editor-card branch-card-editor">
                                    <div class="critical-editor-card-header">
                                        <div>
                                            <strong>@GetBranchTitle(branch, i)</strong>
                                            <p class="muted critical-editor-inline-copy">Shown only when this condition applies.</p>
                                        </div>
                                        <button type="button" class="critical-editor-inline-button" @onclick="() => RemoveBranch(i)">Remove</button>
                                    </div>
                                    <div class="critical-editor-branch-line">
                                        <div class="field-shell">
                                            <label>Condition</label>
                                            <InputText class="input-shell" @bind-Value="branch.ConditionText" @bind-Value:after="() => MarkBranchOverridden(branch)" />
                                        </div>
                                        <div class="field-shell critical-editor-branch-outcome">
                                            <label>Outcome Text</label>
                                            <InputText class="input-shell" @bind-Value="branch.DescriptionText" @bind-Value:after="() => MarkBranchOverridden(branch)" />
                                        </div>
                                    </div>

                                    <div class="critical-editor-subsection">
                                        <div class="critical-editor-section-header">
                                            <div>
                                                <h5>Condition Effects</h5>
                                                <p class="muted">These only appear when the condition is met.</p>
                                            </div>
                                            <button type="button" class="btn-ritual critical-editor-compact-button" @onclick="() => AddBranchEffect(branch)">Add Effect</button>
                                        </div>

                                        @if (branch.Effects.Count == 0)
                                        {
                                            <p class="muted">No effects on this condition yet.</p>
                                        }
                                        else
                                        {
                                            <div class="critical-editor-inline-list">
                                                @for (var effectIndex = 0; effectIndex < branch.Effects.Count; effectIndex++)
                                                {
                                                    var j = effectIndex;
                                                    var effect = branch.Effects[j];
                                                    <div class="critical-editor-inline-row">
                                                        @InlineEffectRow(effect, () => RemoveBranchEffect(branch, j))
                                                    </div>
                                                }
                                            </div>
                                        }
                                    </div>
                                </div>
                            }
                        }
                    </section>

                    <section class="critical-editor-section">
                        <details class="critical-editor-advanced">
                            <summary class="critical-editor-advanced-summary">
                                <span>Advanced Review</span>
                                <span class="critical-editor-advanced-meta">@GetReviewSummary(Model, ComparisonBaseline)</span>
                            </summary>

                            <div class="critical-editor-advanced-body">
                                @if (Model.GeneratedState is not null)
                                {
                                    <div class="critical-editor-card nested">
                                        <div class="critical-editor-card-header">
                                            <div>
                                                <strong>Generated Compare</strong>
                                                <p class="muted critical-editor-inline-copy">Compare the current edited card against the fresh generated result from the quick parse input.</p>
                                            </div>
                                        </div>

                                        <div class="chip-row">
                                            @foreach (var item in GetComparisonSummaryItems(Model, ComparisonBaseline))
                                            {
                                                <span class="chip">@item</span>
                                            }
                                        </div>

                                        <div class="critical-editor-compare-grid">
                                            <CriticalResultPreviewCard
                                                Title="Current Edited Card"
                                                Caption="@GetCurrentComparisonCaption(ComparisonBaseline)"
                                                Description="@GetComparisonSourceModel(Model, ComparisonBaseline).DescriptionText"
                                                Effects="@BuildPreviewEffects(GetComparisonSourceModel(Model, ComparisonBaseline))"
                                                Branches="@BuildPreviewBranches(GetComparisonSourceModel(Model, ComparisonBaseline))" />
                                            <CriticalResultPreviewCard
                                                Title="Generated From Quick Input"
                                                Caption="This is the fresh generated output before override preservation."
                                                Description="@Model.GeneratedState.DescriptionText"
                                                Effects="@Model.GeneratedState.Effects"
                                                Branches="@Model.GeneratedState.Branches"
                                                Notes="@Model.GeneratedState.ValidationMessages" />
                                            @if (ComparisonBaseline is not null)
                                            {
                                                <CriticalResultPreviewCard
                                                    Title="Current After Re-Parse"
                                                    Caption="This is the merged editor state after keeping the existing overrides."
                                                    Description="@Model.DescriptionText"
                                                    Effects="@BuildPreviewEffects(Model)"
                                                    Branches="@BuildPreviewBranches(Model)" />
                                            }
                                        </div>
                                    </div>

                                    @if (Model.GeneratedState.TokenReviewItems.Count > 0)
                                    {
                                        <div class="critical-editor-card nested">
                                            <div class="critical-editor-card-header">
                                                <div>
                                                    <strong>Token Review</strong>
                                                    <p class="muted critical-editor-inline-copy">These tokens were unknown or only partially understood during generation and need manual review.</p>
                                                </div>
                                            </div>

                                            <div class="critical-editor-validation-list">
                                                @foreach (var issue in Model.GeneratedState.TokenReviewItems)
                                                {
                                                    <p class="critical-editor-validation-item">@issue.ReviewText</p>
                                                }
                                            </div>
                                        </div>
                                    }
                                }
                            </div>
                        </details>
                    </section>
                </div>

                <footer class="critical-editor-footer">
                    <button type="button" class="btn btn-link" @onclick="OnClose" disabled="@(IsSaving)">Cancel</button>
                    <button type="submit" class="btn-ritual" disabled="@(IsSaving)">
                        @(IsSaving ? "Saving..." : "Save Cell")
                    </button>
                </footer>
            </EditForm>
        }
    </div>
</div>

@code {
    [Parameter, EditorRequired]
    public CriticalCellEditorModel? Model { get; set; }

    [Parameter]
    public CriticalCellEditorModel? ComparisonBaseline { get; set; }

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

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

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

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

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

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

    [Parameter, EditorRequired]
    public EventCallback OnClose { get; set; }

    [Parameter, EditorRequired]
    public EventCallback OnReparse { get; set; }

    [Parameter, EditorRequired]
    public EventCallback OnSave { get; set; }

    private static readonly IReadOnlyList<(string Token, IReadOnlyList<CriticalEffectLookupResponse> Effects)> QuickParseLegendEntries =
    [
        ("+15", [CreateQuickLegendEffect(CriticalEffectCodes.DirectHits, valueInteger: 15)]),
        ("3s", [CreateQuickLegendEffect(CriticalEffectCodes.StunnedRounds, durationRounds: 3)]),
        ("1mp", [CreateQuickLegendEffect(CriticalEffectCodes.MustParryRounds, durationRounds: 1)]),
        ("3np", [CreateQuickLegendEffect(CriticalEffectCodes.NoParryRounds, durationRounds: 3)]),
        ("1hpr", [CreateQuickLegendEffect(CriticalEffectCodes.BleedPerRound, perRound: 1)]),
        ("-20", [CreateQuickLegendEffect(CriticalEffectCodes.FoePenalty, modifier: -20)]),
        ("+20b", [CreateQuickLegendEffect(CriticalEffectCodes.AttackerBonusNextRound, modifier: 20)]),
        ("+2d10-3pp", [CreateQuickLegendEffect(CriticalEffectCodes.PowerPointModifier, valueExpression: "2d10-3")])
    ];
    private IJSObjectReference? jsModule;
    private bool isBackdropPointerDown;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (!firstRender)
        {
            return;
        }

        jsModule = await JSRuntime.InvokeAsync<IJSObjectReference>(
            "import",
            "./Components/Shared/CriticalCellEditorDialog.razor.js");

        await jsModule.InvokeVoidAsync("lockBackgroundScroll");
    }

    public async ValueTask DisposeAsync()
    {
        if (jsModule is null)
        {
            return;
        }

        try
        {
            await jsModule.InvokeVoidAsync("unlockBackgroundScroll");
            await jsModule.DisposeAsync();
        }
        catch (JSDisconnectedException)
        {
        }
    }

    private void HandleBackdropPointerDown()
    {
        isBackdropPointerDown = true;
    }

    private async Task HandleBackdropPointerUp()
    {
        if (!isBackdropPointerDown)
        {
            return;
        }

        isBackdropPointerDown = false;
        await OnClose.InvokeAsync();
    }

    private void HandleBackdropPointerCancel()
    {
        isBackdropPointerDown = false;
    }

    private void HandleDialogPointerDown()
    {
        isBackdropPointerDown = false;
    }

    private void HandleDialogPointerUp()
    {
        isBackdropPointerDown = false;
    }

    private void HandleDialogPointerCancel()
    {
        isBackdropPointerDown = false;
    }

    private async Task HandleSubmitAsync(EditContext _)
    {
        await OnSave.InvokeAsync();
    }

    private async Task HandleReparseClickAsync(MouseEventArgs _)
    {
        await OnReparse.InvokeAsync();
    }

    private void HandleQuickParseInputChanged(ChangeEventArgs args)
    {
        if (Model is null)
        {
            return;
        }

        Model.QuickParseInput = args.Value?.ToString() ?? string.Empty;
    }

    private void AddBaseEffect()
    {
        if (Model is null)
        {
            return;
        }

        Model.AreEffectsOverridden = true;
        Model.Effects.Add(CreateDefaultEffectModel());
    }

    private void RemoveBaseEffect(int index)
    {
        if (Model is null || index < 0 || index >= Model.Effects.Count)
        {
            return;
        }

        Model.AreEffectsOverridden = true;
        Model.Effects.RemoveAt(index);
    }

    private void AddBranch()
    {
        if (Model is null)
        {
            return;
        }

        Model.Branches.Add(new CriticalBranchEditorModel
        {
            ConditionText = $"Condition {Model.Branches.Count + 1}",
            SortOrder = Model.Branches.Count + 1,
            IsOverridden = true
        });
        Model.AreBranchesOverridden = true;
    }

    private void RemoveBranch(int index)
    {
        if (Model is null || index < 0 || index >= Model.Branches.Count)
        {
            return;
        }

        Model.AreBranchesOverridden = true;
        Model.Branches.RemoveAt(index);
    }

    private static void AddBranchEffect(CriticalBranchEditorModel branch)
    {
        branch.AreEffectsOverridden = true;
        branch.Effects.Add(CreateDefaultEffectModel());
    }

    private static void RemoveBranchEffect(CriticalBranchEditorModel branch, int index)
    {
        if (index < 0 || index >= branch.Effects.Count)
        {
            return;
        }

        branch.AreEffectsOverridden = true;
        branch.Effects.RemoveAt(index);
    }

    private static CriticalEffectEditorModel CreateDefaultEffectModel() =>
        new()
        {
            EffectCode = CriticalEffectCodes.DirectHits,
            SourceType = "symbol",
            IsOverridden = true
        };

    private static string GetBranchTitle(CriticalBranchEditorModel branch, int index) =>
        string.IsNullOrWhiteSpace(branch.ConditionText)
            ? $"Condition {index + 1}"
            : branch.ConditionText;

    private static string GetEffectLabel(CriticalEffectEditorModel effect)
    {
        if (AffixDisplayMap.TryGet(effect.EffectCode, out var info))
        {
            return info.Label;
        }

        return string.IsNullOrWhiteSpace(effect.EffectCode) ? "Custom Effect" : effect.EffectCode;
    }

    private static IEnumerable<KeyValuePair<string, string>> GetEffectOptions(string? currentCode)
    {
        if (!string.IsNullOrWhiteSpace(currentCode) && !AffixDisplayMap.Entries.ContainsKey(currentCode))
        {
            yield return new KeyValuePair<string, string>(currentCode, currentCode);
        }

        foreach (var entry in AffixDisplayMap.Entries.OrderBy(item => item.Value.Label))
        {
            yield return new KeyValuePair<string, string>(entry.Key, entry.Value.Label);
        }
    }

    private static void HandleEffectCodeChanged(CriticalEffectEditorModel effect, string? newCode)
    {
        var normalizedCode = newCode?.Trim() ?? string.Empty;
        if (string.Equals(effect.EffectCode, normalizedCode, StringComparison.Ordinal))
        {
            return;
        }

        effect.EffectCode = normalizedCode;
        effect.Target = null;
        effect.ValueInteger = null;
        effect.ValueDecimal = null;
        effect.ValueExpression = null;
        effect.DurationRounds = null;
        effect.PerRound = null;
        effect.Modifier = null;
        effect.BodyPart = null;
        effect.IsPermanent = false;
        effect.SourceText = null;
        effect.SourceType = AffixDisplayMap.TryGet(effect.EffectCode, out _) ? "symbol" : "manual";
        effect.IsOverridden = true;
    }

    private void MarkDescriptionOverridden()
    {
        if (Model is not null)
        {
            Model.IsDescriptionOverridden = true;
        }
    }

    private static void MarkBranchOverridden(CriticalBranchEditorModel branch)
    {
        branch.IsOverridden = true;
    }

    private static void MarkEffectOverridden(CriticalEffectEditorModel effect)
    {
        effect.IsOverridden = true;
    }

    private static string GetReviewSummary(CriticalCellEditorModel model, CriticalCellEditorModel? comparisonBaseline)
    {
        var differenceCount = GetComparisonDifferenceCount(model, comparisonBaseline);
        var noteCount = model.ValidationMessages.Count;
        var segments = new List<string>();

        if (differenceCount > 0)
        {
            segments.Add($"{differenceCount} review change{(differenceCount == 1 ? string.Empty : "s")}");
        }

        if (noteCount > 0)
        {
            segments.Add($"{noteCount} parser note{(noteCount == 1 ? string.Empty : "s")}");
        }

        return segments.Count == 0 ? "Generated compare" : string.Join(" · ", segments);
    }

    private static string GetParserNoteSummary(int noteCount) =>
        noteCount == 1
            ? "1 parser note is available under Advanced Review."
            : $"{noteCount} parser notes are available under Advanced Review.";

    private static CriticalEffectLookupResponse CreateQuickLegendEffect(
        string effectCode,
        int? valueInteger = null,
        string? valueExpression = null,
        int? durationRounds = null,
        int? perRound = null,
        int? modifier = null) =>
        new(
            effectCode,
            "foe",
            valueInteger,
            valueExpression,
            durationRounds,
            perRound,
            modifier,
            null,
            false,
            "legend",
            null);

    private static IReadOnlyList<string> GetComparisonSummaryItems(CriticalCellEditorModel model, CriticalCellEditorModel? comparisonBaseline)
    {
        if (model.GeneratedState is null)
        {
            return ["Generated comparison is unavailable."];
        }

        var comparisonSourceModel = GetComparisonSourceModel(model, comparisonBaseline);
        var comparisonSourceEffects = BuildPreviewEffects(comparisonSourceModel);
        var comparisonSourceBranches = BuildPreviewBranches(comparisonSourceModel);
        var items = new List<string>();

        if (CriticalCellComparisonEvaluator.DescriptionDiffers(comparisonSourceModel.DescriptionText, model.GeneratedState.DescriptionText))
        {
            items.Add("Result text differs");
        }

        if (CriticalCellComparisonEvaluator.EffectsDiffer(comparisonSourceEffects, model.GeneratedState.Effects))
        {
            items.Add("Base effects differ");
        }

        if (CriticalCellComparisonEvaluator.BranchesDiffer(comparisonSourceBranches, model.GeneratedState.Branches))
        {
            items.Add("Conditions differ");
        }

        if (items.Count == 0)
        {
            items.Add("Current card matches the fresh generation");
        }

        if (model.GeneratedState.TokenReviewItems.Count > 0)
        {
            items.Add($"{model.GeneratedState.TokenReviewItems.Count} token review item{(model.GeneratedState.TokenReviewItems.Count == 1 ? string.Empty : "s")}");
        }

        if (model.GeneratedState.ValidationMessages.Count > 0)
        {
            items.Add($"{model.GeneratedState.ValidationMessages.Count} parser note{(model.GeneratedState.ValidationMessages.Count == 1 ? string.Empty : "s")}");
        }

        return items;
    }

    private static int GetComparisonDifferenceCount(CriticalCellEditorModel model, CriticalCellEditorModel? comparisonBaseline)
    {
        if (model.GeneratedState is null)
        {
            return 0;
        }

        var comparisonSourceModel = GetComparisonSourceModel(model, comparisonBaseline);
        return CriticalCellComparisonEvaluator.GetDifferenceCount(
            comparisonSourceModel.DescriptionText,
            BuildPreviewEffects(comparisonSourceModel),
            BuildPreviewBranches(comparisonSourceModel),
            model.GeneratedState);
    }

    private static bool HasComparisonDifferences(CriticalCellEditorModel? model, CriticalCellEditorModel? comparisonBaseline) =>
        model is not null && GetComparisonDifferenceCount(model, comparisonBaseline) > 0;

    private static CriticalCellEditorModel GetComparisonSourceModel(
        CriticalCellEditorModel model,
        CriticalCellEditorModel? comparisonBaseline) =>
        comparisonBaseline ?? model;

    private static string GetCurrentComparisonCaption(CriticalCellEditorModel? comparisonBaseline) =>
        comparisonBaseline is null
            ? "This is the result that will be saved."
            : "This is the edited card before the last re-parse.";

    private static IReadOnlyList<CriticalEffectLookupResponse> BuildPreviewEffects(CriticalCellEditorModel model) =>
        model.Effects
            .Select(CreatePreviewEffect)
            .ToList();

    private static IReadOnlyList<CriticalBranchLookupResponse> BuildPreviewBranches(CriticalCellEditorModel model) =>
        model.Branches
            .OrderBy(branch => branch.SortOrder)
            .Select(CreatePreviewBranch)
            .ToList();

    private static IReadOnlyList<CriticalEffectLookupResponse> BuildSingleBadgeEffect(CriticalEffectEditorModel effect) =>
        [CreateBadgeEffect(effect)];

    private static CriticalEffectLookupResponse CreateBadgeEffect(CriticalEffectEditorModel effect) =>
        new(
            effect.EffectCode,
            effect.Target,
            effect.ValueInteger,
            effect.ValueExpression,
            effect.DurationRounds,
            effect.PerRound,
            effect.Modifier,
            effect.BodyPart,
            effect.IsPermanent,
            effect.SourceType,
            effect.SourceText);

    private static CriticalEffectLookupResponse CreatePreviewEffect(CriticalEffectEditorModel effect) =>
        CreateBadgeEffect(effect);

    private static CriticalBranchLookupResponse CreatePreviewBranch(CriticalBranchEditorModel branch) =>
        new(
            branch.BranchKind,
            branch.ConditionKey,
            branch.ConditionText,
            branch.DescriptionText,
            branch.RawAffixText,
            branch.Effects
                .Select(CreatePreviewEffect)
                .ToList(),
            branch.RawText,
            branch.SortOrder);
}

@functions {
    private RenderFragment InlineEffectRow(CriticalEffectEditorModel effect, Action onRemove) => @<div class="critical-editor-effect-row">
        <div class="critical-editor-effect-row-main">
            <div class="critical-editor-effect-badge">
                <AffixBadgeList Effects="@BuildSingleBadgeEffect(effect)" />
            </div>
            <div class="field-shell critical-editor-effect-kind">
                <label>Effect</label>
                <select class="input-shell" value="@effect.EffectCode" @onchange="args => HandleEffectCodeChanged(effect, args.Value?.ToString())">
                    @foreach (var option in GetEffectOptions(effect.EffectCode))
                    {
                        <option value="@option.Key">@option.Value</option>
                    }
                </select>
            </div>
            @InlineValueField(effect)
            @if (!string.IsNullOrWhiteSpace(effect.BodyPart))
            {
                <div class="field-shell critical-editor-effect-extra">
                    <label>Body Part</label>
                    <InputText class="input-shell" @bind-Value="effect.BodyPart" @bind-Value:after="() => MarkEffectOverridden(effect)" />
                </div>
            }
        </div>
        <button type="button" class="critical-editor-inline-button" @onclick="onRemove">Remove</button>
    </div>;

    private RenderFragment InlineValueField(CriticalEffectEditorModel effect) => @<div class="field-shell critical-editor-effect-value">
        @switch (effect.EffectCode)
        {
            case CriticalEffectCodes.DirectHits:
                <label>Hits</label>
                <InputNumber TValue="int?" class="input-shell" @bind-Value="effect.ValueInteger" @bind-Value:after="() => MarkEffectOverridden(effect)" />
                break;
            case CriticalEffectCodes.StunnedRounds:
            case CriticalEffectCodes.MustParryRounds:
            case CriticalEffectCodes.NoParryRounds:
                <label>Rounds</label>
                <InputNumber TValue="int?" class="input-shell" @bind-Value="effect.DurationRounds" @bind-Value:after="() => MarkEffectOverridden(effect)" />
                break;
            case CriticalEffectCodes.BleedPerRound:
                <label>Bleed / Round</label>
                <InputNumber TValue="int?" class="input-shell" @bind-Value="effect.PerRound" @bind-Value:after="() => MarkEffectOverridden(effect)" />
                break;
            case CriticalEffectCodes.FoePenalty:
            case CriticalEffectCodes.AttackerBonusNextRound:
                <label>Modifier</label>
                <InputNumber TValue="int?" class="input-shell" @bind-Value="effect.Modifier" @bind-Value:after="() => MarkEffectOverridden(effect)" />
                break;
            case CriticalEffectCodes.PowerPointModifier:
                <label>Expression</label>
                <InputText class="input-shell" @bind-Value="effect.ValueExpression" @bind-Value:after="() => MarkEffectOverridden(effect)" />
                break;
            default:
                <label>Display Text</label>
                <InputText class="input-shell" @bind-Value="effect.SourceText" @bind-Value:after="() => MarkEffectOverridden(effect)" />
                break;
        }
    </div>;
}
