using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.RegularExpressions;

var exeDir = AppContext.BaseDirectory;

var solutionDir = FindContainingDir(exeDir, "ET.sln")
                  ?? throw new InvalidOperationException("Cannot find ET.sln");

var srcDir = Path.GetFullPath(Path.Combine(solutionDir, "..", "docs", "Notes"));
var dstDir = Path.GetFullPath(Path.Combine(solutionDir, "Web", "wwwroot", "docs", "notes"));

Console.WriteLine($"Source: {srcDir}");
Console.WriteLine($"Destination: {dstDir}");

if (!Directory.Exists(srcDir))
{
    Console.Error.WriteLine($"Source directory not found: {srcDir}");
    return 1;
}

var entries = SyncNotes(srcDir, dstDir);
CopyOverview(solutionDir, dstDir, entries);
CopyImages(solutionDir, Path.GetFullPath(Path.Combine(solutionDir, "Web", "wwwroot", "docs")));
var indexJson = BuildIndex(entries);
File.WriteAllText(Path.Combine(Path.GetDirectoryName(dstDir)!, "notes-index.json"), indexJson);
GenerateMap(srcDir, Path.GetFullPath(Path.Combine(solutionDir, "Web", "wwwroot", "docs")));
GenerateOverviewPage(solutionDir);

Console.WriteLine("Done.");
return 0;

static List<object> SyncNotes(string srcDir, string dstDir)
{
    Directory.CreateDirectory(dstDir);

    foreach (var file in Directory.GetFiles(dstDir))
        File.Delete(file);

    var entries = new List<object>();

    foreach (var file in Directory.EnumerateFiles(srcDir, "*.md"))
    {
        var name = Path.GetFileNameWithoutExtension(file);
        var slug = Slugify(name);

        File.Copy(file, Path.Combine(dstDir, $"{slug}.md"), true);

        string? category = null;
        string? cost = null;
        string? gearCategory = null;
        string? effect = null;
        string? location = null;
        var content = File.ReadAllText(file);
        var fmMatch = Regex.Match(content, @"^---\s*\n(.*?)\n---", RegexOptions.Singleline);
        if (fmMatch.Success)
        {
            var fm = fmMatch.Groups[1].Value;
            var catMatch = Regex.Match(fm, @"(?m)^category:\s*(.+)$", RegexOptions.IgnoreCase);
            if (catMatch.Success)
                category = catMatch.Groups[1].Value.Trim().Trim('"');
            var costMatch = Regex.Match(fm, @"(?m)^cost:\s*(.+)$", RegexOptions.IgnoreCase);
            if (costMatch.Success)
                cost = costMatch.Groups[1].Value.Trim().Trim('"');
            var gcMatch = Regex.Match(fm, @"(?m)^gear category:\s*(.+)$", RegexOptions.IgnoreCase);
            if (gcMatch.Success)
                gearCategory = gcMatch.Groups[1].Value.Trim().Trim('"');
            var effMatch = Regex.Match(fm, @"(?m)^effect:\s*(.+)$", RegexOptions.IgnoreCase);
            if (effMatch.Success)
                effect = effMatch.Groups[1].Value.Trim().Trim('"');
            var locMatch = Regex.Match(fm, @"(?m)^location:\s*(.+)$", RegexOptions.IgnoreCase);
            if (locMatch.Success)
                location = locMatch.Groups[1].Value.Trim().Trim('"');
        }

        var entry = new Dictionary<string, object?>
        {
            ["slug"] = slug,
            ["title"] = name
        };
        if (category != null)
            entry["category"] = category;
        if (cost != null)
            entry["cost"] = cost;
        if (gearCategory != null)
            entry["gearCategory"] = gearCategory;
        if (effect != null)
            entry["effect"] = effect;
        if (location != null)
            entry["location"] = location;

        entries.Add(entry);
    }

    Console.WriteLine($"Copied {entries.Count} notes.");
    return entries;
}

static void CopyOverview(string solutionDir, string dstDir, List<object> entries)
{
    var srcOverview = Path.GetFullPath(Path.Combine(solutionDir, "..", "docs", "Overview.md"));
    if (!File.Exists(srcOverview)) return;

    var slug = "overview";
    File.Copy(srcOverview, Path.Combine(dstDir, $"{slug}.md"), true);

    entries.Add(new Dictionary<string, object?>
    {
        ["slug"] = slug,
        ["title"] = "Overview",
        ["category"] = "Overview"
    });

    Console.WriteLine("Copied Overview.md.");
}

static void CopyImages(string solutionDir, string dstDir)
{
    var srcImgs = Path.GetFullPath(Path.Combine(solutionDir, "..", "docs", "Images"));
    if (!Directory.Exists(srcImgs)) return;

    var dstImgs = Path.Combine(dstDir, "images");
    Directory.CreateDirectory(dstImgs);

    foreach (var file in Directory.EnumerateFiles(srcImgs))
    {
        var name = Path.GetFileName(file);
        File.Copy(file, Path.Combine(dstImgs, name), true);
    }

    Console.WriteLine($"Copied images from {srcImgs}.");
}

static string BuildIndex(List<object> entries)
{
    var index = new Dictionary<string, object> { ["notes"] = entries };
    return JsonSerializer.Serialize(index, new JsonSerializerOptions { WriteIndented = true });
}

static void GenerateOverviewPage(string solutionDir)
{
    var pagesDir = Path.GetFullPath(Path.Combine(solutionDir, "Web", "Pages"));
    Directory.CreateDirectory(pagesDir);

    var component = """
@page "/overview"
@inject DocsService DocsService

<PageTitle>Overview</PageTitle>

@if (loading)
{
    <div class="d-flex justify-content-center py-5">
        <div class="spinner-border text-success" role="status">
            <span class="visually-hidden">Loading...</span>
        </div>
    </div>
}
else if (doc == null)
{
    <h1>Overview Not Found</h1>
    <p>The overview document could not be found.</p>
}
else
{
    <div class="section-header d-flex align-items-center mb-4">
        <h1 class="mb-0">@doc.Title</h1>
        <div class="ms-3 flex-grow-1 border-bottom opacity-25"></div>
    </div>

    @if (!string.IsNullOrEmpty(doc.FrontmatterHtml))
    {
        <details class="frontmatter-section" open>
            <summary>Frontmatter</summary>
            @((MarkupString)doc.FrontmatterHtml)
        </details>
    }

    <div class="markdown-body overview-markdown">
        @((MarkupString)doc.HtmlContent)
    </div>
}

@code {
    private NoteDocument? doc;
    private bool loading = true;

    protected override async Task OnInitializedAsync()
    {
        doc = await DocsService.GetNoteAsync("overview");
        loading = false;
    }
}
""";

    var overviewPagePath = Path.Combine(pagesDir, "Overview.razor");
    File.WriteAllText(overviewPagePath, component);
    Console.WriteLine($"Overview page written to: {overviewPagePath}");
}

static void GenerateMap(string srcDir, string dstDir)
{
    var terrainColors = new Dictionary<string, string>
    {
        ["Grass"] = "#4caf50",
        ["Forest"] = "#2e7d32",
        ["Mountain"] = "#78909c",
        ["Water"] = "#42a5f5",
        ["Wasteland"] = "#8d6e63"
    };

    var regionFiles = Directory.EnumerateFiles(srcDir, "*.md")
        .Select(f => (File: f, Content: File.ReadAllText(f)))
        .Where(t => Regex.IsMatch(t.Content, @"(?m)^category:\s*Region\s*$", RegexOptions.Multiline))
        .ToList();

    var regions = new List<RegionData>();

    foreach (var (file, content) in regionFiles)
    {
        var fmMatch = Regex.Match(content, @"^---\s*\n(.*?)\n---", RegexOptions.Singleline);
        if (!fmMatch.Success) continue;

        var fm = fmMatch.Groups[1].Value;
        var name = Path.GetFileNameWithoutExtension(file);

        var xMatch = Regex.Match(fm, @"(?m)^X:\s*(\d+)");
        var yMatch = Regex.Match(fm, @"(?m)^Y:\s*(\d+)");
        if (!xMatch.Success || !yMatch.Success) continue;

        var connMatches = Regex.Matches(fm, @"\[\[([^\]]+)\]\]");
        var conns = connMatches.Select(m => m.Groups[1].Value).ToList();

        var landmarks = new List<string>();
        var lmSection = Regex.Match(fm, @"Landmarks:\s*\n((?:\s*-\s*""\[\[([^\]]+)\]\]""\s*\n?)*)");
        if (lmSection.Success)
        {
            var lmEntries = Regex.Matches(lmSection.Groups[1].Value, @"\[\[([^\]]+)\]\]");
            landmarks.AddRange(lmEntries.Select(m => m.Groups[1].Value));
        }

        var terrain = name.Split(' ')[0];

        regions.Add(new RegionData
        {
            Name = name,
            Slug = Slugify(name),
            Terrain = terrain,
            X = int.Parse(xMatch.Groups[1].Value),
            Y = int.Parse(yMatch.Groups[1].Value),
            Connections = conns,
            Landmarks = landmarks
        });
    }


    var srcMapImage = Path.GetFullPath(Path.Combine(srcDir, "..", "Images", "Map.png"));
    var dstMapImage = Path.Combine(dstDir, "Map.png");
    if (File.Exists(srcMapImage))
        File.Copy(srcMapImage, dstMapImage, true);

    var nameLookup = regions.ToDictionary(r => r.Name);

    var pad = 60;
    var maxX = (regions.Count > 0 ? regions.Max(r => r.X) : 0) + pad * 2;
    var maxY = (regions.Count > 0 ? regions.Max(r => r.Y) : 0) + pad * 2;
    var svg = new StringBuilder();
    svg.AppendLine("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 700 461\">");

    svg.AppendLine("""<image href="Map.png" width="700" height="461" preserveAspectRatio="xMidYMid meet"/>""");

    svg.AppendLine("""<defs>""");
    foreach (var (terrain, color) in terrainColors)
    {
        svg.AppendLine($"""<radialGradient id="glow-{terrain}" cx="50%" cy="50%" r="50%">""");
        svg.AppendLine($"""<stop offset="0%" stop-color="{color}" stop-opacity="0.3"/>""");
        svg.AppendLine($"""<stop offset="100%" stop-color="{color}" stop-opacity="0"/>""");
        svg.AppendLine("</radialGradient>");
    }

    svg.AppendLine("</defs>");

    foreach (var region in regions)
    foreach (var conn in region.Connections)
    {
        if (!nameLookup.TryGetValue(conn, out var target) ||
            string.Compare(region.Name, conn, StringComparison.OrdinalIgnoreCase) >= 0)
            continue;

        svg.AppendLine(
            $"""<line x1="{region.X}" y1="{region.Y}" x2="{target.X}" y2="{target.Y}" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>""");
    }

    foreach (var region in regions)
    {
        var cx = region.X;
        var cy = region.Y;
        var color = terrainColors.GetValueOrDefault(region.Terrain, "#888");

        svg.AppendLine($"""<a href="/docs/{region.Slug}" target="_top">""");
        svg.AppendLine($"""<circle cx="{cx}" cy="{cy}" r="32" fill="url(#glow-{region.Terrain})"/>""");
        svg.AppendLine(
            $"""<circle cx="{cx}" cy="{cy}" r="18" fill="{color}cc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>""");

        var labelX = cx + 24;
        svg.AppendLine(
            $"""<text x="{labelX}" y="{cy + 4}" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">""");
        svg.Append(HtmlEncoder.Default.Encode(region.Name));
        svg.AppendLine("</text>");

        foreach (var lm in region.Landmarks)
        {
            svg.AppendLine(
                $"""<text x="{labelX}" y="{cy + 18}" fill="rgba(255,255,255,0.45)" font-family="system-ui,sans-serif" font-size="9" font-style="italic">""");
            svg.Append(HtmlEncoder.Default.Encode($"\u2605 {lm}"));
            svg.AppendLine("</text>");
        }

        svg.AppendLine("</a>");
    }

    /**
    var legendX = 700 - 160;
    var legendY = 20;
    svg.AppendLine($"""<rect x="{legendX}" y="{legendY}" width="140" height="{20 + terrainColors.Count * 22}" rx="6" fill="rgba(0,0,0,0.4)" stroke="rgba(255,255,255,0.08)"/>""");
    svg.AppendLine($"""<text x="{legendX + 10}" y="{legendY + 15}" fill="rgba(255,255,255,0.6)" font-family="system-ui,sans-serif" font-size="10" font-weight="600">Legend</text>""");
    var ly = legendY + 32;
    foreach (var (terrain, color) in terrainColors)
    {
        svg.AppendLine($"""<circle cx="{legendX + 14}" cy="{ly - 3}" r="5" fill="{color}"/>""");
        svg.AppendLine($"""<text x="{legendX + 26}" y="{ly + 1}" fill="rgba(255,255,255,0.7)" font-family="system-ui,sans-serif" font-size="10">{terrain}</text>""");
        ly += 22;
    }
    */

    svg.AppendLine("</svg>");

    var mapPath = Path.Combine(dstDir, "map.svg");
    File.WriteAllText(mapPath, svg.ToString());
    Console.WriteLine($"Map written to: {mapPath} ({regions.Count} regions)");
}

static string Slugify(string name)
{
    var slug = name.ToLowerInvariant()
        .Replace("'", "")
        .Replace(".", "")
        .Replace("(", "")
        .Replace(")", "");
    slug = Regex.Replace(slug, @"[^a-z0-9 -]", "");
    slug = Regex.Replace(slug, @"\s+", "-");
    slug = Regex.Replace(slug, @"-+", "-");
    return slug.Trim('-');
}

static string? FindContainingDir(string startDir, string markerFile)
{
    var dir = new DirectoryInfo(startDir);
    while (dir != null)
    {
        if (File.Exists(Path.Combine(dir.FullName, markerFile)))
            return dir.FullName;
        dir = dir.Parent;
    }

    return null;
}

internal class RegionData
{
    public string Name { get; set; } = "";
    public string Slug { get; set; } = "";
    public string Terrain { get; set; } = "";
    public int X { get; set; }
    public int Y { get; set; }
    public List<string> Connections { get; set; } = new();
    public List<string> Landmarks { get; set; } = new();
}
