@page "/docs"
@using System.Text.RegularExpressions
@inject IJSRuntime JS

<PageTitle>Fellowship Docs</PageTitle>

<div class="docs-page">
    <div class="docs-header">
        <h1 class="docs-title">Fellowship Reference</h1>
        <p class="docs-subtitle">@DocsData.All.Count entries across @Groups.Count() categories</p>
        <div class="docs-search">
            <span class="search-icon">&#x1F50D;</span>
            <input type="text" class="search-input" placeholder="Search docs..." @bind="searchTerm" @bind:after="OnSearchChanged" />
            @if (!string.IsNullOrEmpty(searchTerm))
            {
                <button class="search-clear" @onclick="ClearSearch">&times;</button>
            }
        </div>
        <div class="docs-filter-bar">
            <button class="filter-btn @(activeFilter == null ? "active" : "")" @onclick="FilterAll">All</button>
            <button class="filter-btn @(activeFilter == "Skill" ? "active" : "")" @onclick="FilterSkills">Skills</button>
            <button class="filter-btn @(activeFilter == "Debuff" ? "active" : "")" @onclick="FilterDebuffs">Debuffs</button>
            <button class="filter-btn @(activeFilter == "Buff" ? "active" : "")" @onclick="FilterBuffs">Buffs</button>
            <button class="filter-btn @(activeFilter == "Key" ? "active" : "")" @onclick="FilterKeys">Keys</button>
        </div>
    </div>

    <div class="docs-body">
        <nav class="docs-sidebar">
            <div class="sidebar-inner">
                @foreach (var group in FilteredGroups)
                {
                    var groupId = GetGroupId(group.Key);
                    <div class="sidebar-group">
                        <a class="sidebar-group-title" href="@($"#{groupId}")" @onclick:preventDefault @onclick="() => ScrollTo(groupId)">@group.Key</a>
                        <div class="sidebar-items">
                            @foreach (var doc in group)
                            {
                                var docId = GetDocId(doc);
                                <a class="sidebar-item" href="@($"#{docId}")" @onclick:preventDefault @onclick="() => ScrollTo(docId)" title="@doc.FileName">
                                    <span class="sidebar-item-badge @GetTypeClass(doc)"></span>
                                    @GetDisplayName(doc)
                                </a>
                            }
                        </div>
                    </div>
                }
            </div>
        </nav>

        <div class="docs-content">
            @if (!FilteredGroups.Any())
            {
                <div class="no-results">
                    <p>No docs match "@searchTerm"</p>
                    <button class="filter-btn" @onclick="ClearSearch">Clear search</button>
                </div>
            }

            @foreach (var group in FilteredGroups)
            {
                var groupId = GetGroupId(group.Key);
                <section class="group-section">
                    <div class="group-header" id="@groupId">
                        <h2 class="group-title">@group.Key</h2>
                        <span class="group-count">@group.Count()</span>
                    </div>

                    @foreach (var doc in group)
                    {
                        var docId = GetDocId(doc);
                        var typeName = doc.GetType().Name.Replace("Doc", "");
                        <article class="doc-card" id="@docId">
                            <div class="doc-card-header">
                                <div class="doc-card-title-row">
                                    <h3 class="doc-card-title">@GetDisplayName(doc)</h3>
                                    <span class="doc-type-badge @GetTypeClass(doc)">@typeName</span>
                                </div>
                                @if (doc is SkillDoc { Key: { } key } && !string.IsNullOrEmpty(key))
                                {
                                    <div class="doc-key-badge">
                                        <span class="key-icon">&#x2328;</span>
                                        <span class="key-text">@key</span>
                                    </div>
                                }
                            </div>

                            <div class="doc-card-body">
                                @if (doc is SkillDoc { Description: { } desc } && !string.IsNullOrEmpty(desc))
                                {
                                    <div class="doc-description">
                                        @{
                                            var lines = desc.Split('\n');
                                            foreach (var line in lines)
                                            {
                                                <p>@RenderWikiLinks(line)</p>
                                            }
                                        }
                                    </div>
                                }

                                <div class="doc-fields">
                                    @foreach (var field in GetOrderedFields(doc))
                                    {
                                        <div class="doc-field">
                                            <span class="doc-field-label">@field.Label</span>
                                            <span class="doc-field-value">@field.Value</span>
                                        </div>
                                    }
                                </div>

                                @if (GetBody(doc) is { } body && !string.IsNullOrWhiteSpace(body) && (doc is not SkillDoc || string.IsNullOrWhiteSpace(((SkillDoc)doc).Description)))
                                {
                                    <div class="doc-body">
                                        <pre class="doc-body-text">@body.Trim()</pre>
                                    </div>
                                }
                            </div>

                            @if (doc is SkillDoc { Tags: { } tags } && tags.Count > 0)
                            {
                                <div class="doc-card-footer">
                                    @foreach (var tag in tags)
                                    {
                                        <span class="tag-badge">@tag</span>
                                    }
                                </div>
                            }
                        </article>
                    }
                </section>
            }
        </div>
    </div>
</div>

@code {
    private string? searchTerm;
    private string? activeFilter;
    private List<IGrouping<string, DocEntry>> Groups = [];
    private IEnumerable<IGrouping<string, DocEntry>> FilteredGroups => Groups
        .Where(g => g.Any(d => MatchesFilter(d)))
        .OrderBy(g => SortOrder(g.Key));

    protected override void OnInitialized()
    {
        Groups = DocsData.All
            .GroupBy(d => GetCharacterOrType(d))
            .ToList();
    }

    private bool MatchesFilter(DocEntry doc)
    {
        if (!string.IsNullOrEmpty(searchTerm))
        {
            var term = searchTerm.Trim().ToLowerInvariant();
            var name = GetDisplayName(doc).ToLowerInvariant();
            if (!name.Contains(term)) return false;
        }
        if (activeFilter != null)
        {
            var type = doc.GetType().Name.Replace("Doc", "");
            if (type != activeFilter) return false;
        }
        return true;
    }

    private static int SortOrder(string groupKey) => groupKey switch
    {
        "Xavian" => 0,
        "Rime" => 1,
        "Vigour" => 2,
        "Keys" => 3,
        _ => 99
    };

    private void OnSearchChanged()
    {
        StateHasChanged();
    }

    private void ClearSearch()
    {
        searchTerm = null;
        activeFilter = null;
    }

    private async Task ScrollTo(string id)
    {
        await JS.InvokeVoidAsync("scrollToFragment", id);
    }

    private void FilterAll() { activeFilter = null; }
    private void FilterSkills() { activeFilter = activeFilter == "Skill" ? null : "Skill"; }
    private void FilterDebuffs() { activeFilter = activeFilter == "Debuff" ? null : "Debuff"; }
    private void FilterBuffs() { activeFilter = activeFilter == "Buff" ? null : "Buff"; }
    private void FilterKeys() { activeFilter = activeFilter == "Key" ? null : "Key"; }

    private static string GetCharacterOrType(DocEntry doc) => doc switch
    {
        SkillDoc s => s.Character,
        DebuffDoc d => d.Character,
        BuffDoc b => b.Character,
        CharacterDoc c => c.Character,
        KeyDoc => "Keys",
        _ => "Other"
    };

    private static string GetDisplayName(DocEntry doc) =>
        Path.GetFileNameWithoutExtension(doc.FileName);

    private static string GetGroupId(string group) =>
        $"group-{group.GetHashCode():x}";

    private static string GetDocId(DocEntry doc) =>
        $"doc-{doc.FileName.GetHashCode():x}";

    private static string GetTypeClass(DocEntry doc) => doc switch
    {
        SkillDoc => "type-skill",
        DebuffDoc => "type-debuff",
        BuffDoc => "type-buff",
        KeyDoc => "type-key",
        CharacterDoc => "type-character",
        _ => ""
    };

    private static MarkupString RenderWikiLinks(string line)
    {
        var result = Regex.Replace(line, @"\[\[([^\]]+)\]\]", m =>
        {
            var name = m.Groups[1].Value;
            return $"<span class=\"wiki-link\">{name}</span>";
        });
        return new MarkupString(result);
    }

    private static readonly Dictionary<string, string> FieldLabels = new()
    {
        ["Character"] = "Character",
        ["Cast"] = "Cast Time",
        ["Key"] = "Key Bind",
        ["Range"] = "Range",
        ["Damage"] = "Damage",
        ["DamageType"] = "Damage Type",
        ["Heal"] = "Healing",
        ["Shield"] = "Shield",
        ["Cooldown"] = "Cooldown",
        ["Mana"] = "Mana Cost",
        ["OffGlobalCooldown"] = "Off GCD",
        ["Gdc"] = "GDC",
        ["Duration"] = "Duration",
        ["DamageReduction"] = "Dmg Reduction",
        ["DamageTickTime"] = "Tick Interval",
        ["IsToggle"] = "Toggle",
        ["ManaUpkeepTick"] = "Mana Upkeep",
        ["ParryChance"] = "Parry Chance",
        ["DamageRedirection"] = "Dmg Redirection",
        ["SpiritCost"] = "Spirit Cost",
        ["SecondEffectDuration"] = "Effect 2 Duration",
        ["SecondEffectDamageReduction"] = "Effect 2 Dmg Reduction",
        ["HealingDuration"] = "Healing Duration",
        ["HealingTickTime"] = "Healing Tick",
        ["CostSwiftReprieval"] = "Cost: Swift Reprieval",
        ["GdcDuration"] = "GDC Duration",
        ["Effect"] = "Effect",
        ["GeneratesSpirit"] = "Generates Spirit",
        ["AreaDamagePercentage"] = "Cleave %",
        ["SwiftReprievalChance"] = "Swift Reprieval %",
        ["MaxStacks"] = "Max Stacks",
        ["Action"] = "Action",
        ["ParryChanceBonus"] = "Parry Bonus",
        ["ManaRestoreBase"] = "Mana Restore Base",
        ["ManaRestorePerStack"] = "Mana Per Stack",
        ["Order"] = "Order",
        ["Priority"] = "Priority",
        ["Completed"] = "Completed",
        ["Raw"] = "Raw",
    };

    private static readonly Dictionary<Type, string[]> FieldOrder = new()
    {
        [typeof(SkillDoc)] = ["Character", "Cast", "Key", "Range", "Damage", "DamageType", "Heal", "Shield", "Cooldown", "Mana", "OffGlobalCooldown", "Gdc", "Duration", "DamageReduction", "DamageTickTime", "IsToggle", "ManaUpkeepTick", "ParryChance", "DamageRedirection", "GeneratesSpirit", "AreaDamagePercentage", "SwiftReprievalChance", "Effect", "SpiritCost", "SecondEffectDuration", "SecondEffectDamageReduction", "HealingDuration", "HealingTickTime", "CostSwiftReprieval", "GdcDuration"],
        [typeof(DebuffDoc)] = ["Character", "MaxStacks", "Duration", "ParryChanceBonus", "ManaRestoreBase", "ManaRestorePerStack"],
        [typeof(BuffDoc)] = ["Character", "MaxStacks"],
        [typeof(KeyDoc)] = ["Action"],
        [typeof(CharacterDoc)] = ["Character"],
    };

    private static List<FieldEntry> GetOrderedFields(DocEntry doc)
    {
        var result = new List<FieldEntry>();
        var type = doc.GetType();
        var order = FieldOrder.GetValueOrDefault(type, []);
        var allProps = new Dictionary<string, string>();

        foreach (var prop in type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
        {
            var name = prop.Name;
            if (name is "FileName" or "FilePath" or "Body" or "Description") continue;

            var value = prop.GetValue(doc);
            if (value == null) continue;

            if (value is bool bVal && !bVal) continue;
            if (value is string s && string.IsNullOrEmpty(s)) continue;

            var display = value switch
            {
                List<string> list => string.Join(", ", list),
                _ => value.ToString()
            };

            if (string.IsNullOrEmpty(display)) continue;
            allProps[name] = display;
        }

        foreach (var key in order)
        {
            if (allProps.Remove(key, out var val))
            {
                result.Add(new FieldEntry(FieldLabels.GetValueOrDefault(key, key), val));
            }
        }

        foreach (var (key, val) in allProps)
        {
            result.Add(new FieldEntry(FieldLabels.GetValueOrDefault(key, key), val));
        }

        return result;
    }

    private static string? GetBody(DocEntry doc) => doc.Body;

    private record FieldEntry(string Label, string Value);
}
