// System.Net.WebUtility intentionally NOT used — its HtmlEncode encodes all non-ASCII
// characters as named entities (e.g. ä→&auml;), which is wrong for UTF-8 pages.
using System.Text;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;

namespace GerbilManagerWebAPI.Cms
{
    /// <summary>
    /// WEB-1: Deterministic static site renderer. Consumes a CMS snapshot (from
    /// SiteSnapshotService.BuildAsync) and produces a flat path→content dictionary
    /// ready for upload to Cloudflare Pages. No external dependencies; Markdown
    /// support handles the common subset used in RichText blocks.
    /// </summary>
    public static class SiteRenderer
    {
        public const string SiteName = "Kleine Chaoten";
        public const string CssPath = "assets/site.css";

        // ── Public entry point ───────────────────────────────────────────────

        /// <summary>
        /// Renders the snapshot to a flat file map (relative-path → file-content).
        /// Only <c>Published</c> pages are included. The "start" page maps to
        /// <c>index.html</c>; all others to <c>{slug}/index.html</c>.
        /// </summary>
        public static IReadOnlyDictionary<string, string> Render(JsonObject snapshot)
        {
            var files = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            files[CssPath] = SiteCss();

            var navSlugs = NavSlugs(snapshot);
            var pages = snapshot["pages"] as JsonArray ?? [];

            foreach (var node in pages)
            {
                if (node is not JsonObject page) continue;
                if (Str(page["status"]) != "Published") continue;

                var slug = Str(page["slug"]) ?? "page";
                var title = Str(page["title"]) ?? slug;
                var seoDes = Str(page["seoDescription"]);
                var blocks = page["blocks"] as JsonArray ?? [];

                var cssHref = slug == "start" ? CssPath : $"../{CssPath}";
                var body = RenderBlocks(blocks, slug);
                var html = PageHtml(title, seoDes, body, navSlugs, slug, cssHref);
                var path = slug == "start" ? "index.html" : $"{slug}/index.html";
                files[path] = html;
            }

            return files;
        }

        // ── Block rendering ──────────────────────────────────────────────────

        private static string RenderBlocks(JsonArray blocks, string currentSlug)
        {
            var sb = new StringBuilder();
            foreach (var node in blocks.OrderBy(b => b?["order"]?.GetValue<int>() ?? 0))
            {
                if (node is not JsonObject b) continue;
                var type = Str(b["type"]);
                var data = b["data"] as JsonObject ?? [];
                sb.Append(type switch
                {
                    "Heading" => RenderHeading(data),
                    "RichText" => RenderRichText(data),
                    "Image" => RenderImage(data),
                    "Gallery" => RenderGallery(data),
                    "ContactInfo" => RenderContactInfo(data),
                    "AbgabetiereList" => RenderAbgabetiereList(data),
                    _ => string.Empty,
                });
            }
            return sb.ToString();
        }

        private static string RenderHeading(JsonObject d)
        {
            var level = d["level"]?.GetValue<int>() ?? 2;
            level = Math.Clamp(level, 1, 6);
            var text = H(Str(d["text"]) ?? "");
            return $"\n<h{level} class=\"cms-heading\">{text}</h{level}>\n";
        }

        private static string RenderRichText(JsonObject d)
        {
            var md = Str(d["markdown"]) ?? "";
            return $"\n<div class=\"cms-richtext\">{MarkdownToHtml(md)}</div>\n";
        }

        private static string RenderImage(JsonObject d)
        {
            var url = H(Str(d["url"]) ?? "");
            var alt = H(Str(d["alt"]) ?? "");
            return $"\n<figure class=\"cms-image\"><img src=\"{url}\" alt=\"{alt}\" loading=\"lazy\"></figure>\n";
        }

        private static string RenderGallery(JsonObject d)
        {
            var images = d["images"] as JsonArray ?? [];
            var sb = new StringBuilder("\n<div class=\"cms-gallery\">\n");
            foreach (var img in images)
            {
                if (img is not JsonObject o) continue;
                var url = H(Str(o["url"]) ?? "");
                var alt = H(Str(o["alt"]) ?? "");
                sb.Append($"  <figure class=\"cms-gallery-item\"><img src=\"{url}\" alt=\"{alt}\" loading=\"lazy\"></figure>\n");
            }
            sb.Append("</div>\n");
            return sb.ToString();
        }

        private static string RenderContactInfo(JsonObject d)
        {
            var sb = new StringBuilder("\n<address class=\"cms-contact\">\n");
            void Row(string? val, string icon) { if (!string.IsNullOrWhiteSpace(val)) sb.AppendLine($"  <span class=\"cms-contact-row\">{icon} {H(val)}</span>"); }
            Row(Str(d["name"]), "👤");
            Row(Str(d["address"]), "📍");
            var phone = Str(d["phone"]);
            if (!string.IsNullOrWhiteSpace(phone))
                sb.AppendLine($"  <span class=\"cms-contact-row\">📞 <a href=\"tel:{H(phone)}\">{H(phone)}</a></span>");
            var email = Str(d["email"]);
            if (!string.IsNullOrWhiteSpace(email))
                sb.AppendLine($"  <span class=\"cms-contact-row\">✉️ <a href=\"mailto:{H(email)}\">{H(email)}</a></span>");
            sb.Append("</address>\n");
            return sb.ToString();
        }

        private static string RenderAbgabetiereList(JsonObject d)
        {
            var intro = Str(d["intro"]);
            var animals = (d["animals"] as JsonArray ?? [])
                .OfType<JsonObject>().ToList();
            var sb = new StringBuilder("\n<section class=\"cms-abgabe\">\n");

            if (!string.IsNullOrWhiteSpace(intro))
                sb.Append($"  <p class=\"cms-abgabe-intro\">{H(intro)}</p>\n");

            if (animals.Count == 0)
            {
                sb.Append("  <p class=\"cms-abgabe-empty\">Zurzeit stehen keine Tiere zur Abgabe bereit.</p>\n");
                sb.Append("</section>\n");
                return sb.ToString();
            }

            // Group by Becken name; null group → solo card keyed by animal name
            var groups = animals
                .GroupBy(a => Str(a["group"]) ?? ("__solo__" + (Str(a["name"]) ?? "")))
                .ToList();

            sb.Append("  <div class=\"cms-group-list\">\n");
            foreach (var grp in groups)
            {
                var isSolo = grp.Key.StartsWith("__solo__", StringComparison.Ordinal);
                var groupName = isSolo ? null : grp.Key;
                var memberList = grp.ToList();

                // Determine group gender label
                var genders = memberList.Select(a => Str(a["gender"]) ?? "unknown").Distinct().ToList();
                string genderLabel = genders.Count == 1
                    ? genders[0] switch { "female" => "Weibchen", "male" => "Männchen", _ => "" }
                    : genders.Any(g => g != "unknown") ? "Gemischt" : "";

                sb.Append("    <article class=\"cms-group-card\">\n");
                sb.Append("      <div class=\"cms-group-header\">\n");
                if (!string.IsNullOrEmpty(groupName))
                    sb.Append($"        <h3 class=\"cms-group-name\">{H(groupName)}</h3>\n");
                var metaParts = new List<string>();
                if (!string.IsNullOrEmpty(genderLabel)) metaParts.Add(H(genderLabel));
                metaParts.Add("<span class=\"cms-badge cms-badge--free\">Verfügbar</span>");
                sb.Append($"        <span class=\"cms-group-meta\">{string.Join(" · ", metaParts)}</span>\n");
                sb.Append("      </div>\n");

                // Gallery: first photo of each animal in the group
                var gallery = memberList
                    .Select(a => (
                        url: (a["photos"] as JsonArray ?? []).FirstOrDefault()?.GetValue<string>(),
                        name: Str(a["name"]) ?? ""))
                    .Where(x => !string.IsNullOrEmpty(x.url))
                    .ToList();
                if (gallery.Count > 0)
                {
                    sb.Append("      <div class=\"cms-group-gallery\">\n");
                    foreach (var (url, name) in gallery)
                        sb.Append($"        <img src=\"{H(url)}\" alt=\"{H(name)}\" loading=\"lazy\" class=\"cms-group-photo\">\n");
                    sb.Append("      </div>\n");
                }

                // Per-animal detail rows
                sb.Append("      <ul class=\"cms-animal-list\">\n");
                foreach (var a in memberList)
                {
                    var name = H(Str(a["name"]) ?? "");
                    var farbe = Str(a["farbschlag"]);
                    var dob = Str(a["dateOfBirth"]);
                    var note = Str(a["characterNote"]);
                    var saleText = Str(a["aiSaleText"]);

                    sb.Append("        <li class=\"cms-animal-row\">\n");
                    sb.Append($"          <span class=\"cms-animal-name\">{name}</span>\n");
                    var details = new List<string>();
                    if (!string.IsNullOrEmpty(farbe)) details.Add(H(farbe));
                    if (!string.IsNullOrEmpty(dob) && DateOnly.TryParse(dob, out var dob2))
                        details.Add($"* {dob2.ToString("MMMM yyyy", System.Globalization.CultureInfo.GetCultureInfo("de-DE"))}");
                    if (details.Count > 0)
                        sb.Append($"          <span class=\"cms-animal-detail\">{string.Join(" · ", details)}</span>\n");
                    var personalityText = note ?? saleText;
                    if (!string.IsNullOrWhiteSpace(personalityText))
                        sb.Append($"          <span class=\"cms-animal-note\">{H(personalityText)}</span>\n");
                    sb.Append("        </li>\n");
                }
                sb.Append("      </ul>\n");
                sb.Append("    </article>\n");
            }
            sb.Append("  </div>\n");

            sb.Append("</section>\n");
            return sb.ToString();
        }

        // ── Page template ────────────────────────────────────────────────────

        private static string PageHtml(
            string title, string? seoDesc, string body,
            IReadOnlyList<string> navSlugs, string currentSlug, string cssHref)
        {
            var fullTitle = H($"{title} | {SiteName}");
            var metaDesc = seoDesc is null ? ""
                : $"\n  <meta name=\"description\" content=\"{H(seoDesc)}\">";

            var navBase = currentSlug == "start" ? "" : "../";
            var navHtml = BuildNav(navSlugs, currentSlug, navBase);

            return $"""
                <!DOCTYPE html>
                <html lang="de">
                <head>
                  <meta charset="UTF-8">
                  <meta name="viewport" content="width=device-width, initial-scale=1.0">{metaDesc}
                  <title>{fullTitle}</title>
                  <link rel="stylesheet" href="{H(cssHref)}">
                </head>
                <body>
                  <header class="site-header">
                    <a href="{navBase}index.html" class="site-logo">🐭 {H(SiteName)}</a>
                    <nav class="site-nav" aria-label="Hauptnavigation">
                {navHtml}
                    </nav>
                  </header>
                  <main class="site-main">
                {body}
                  </main>
                  <footer class="site-footer">
                    <p>&copy; {H(SiteName)} — Mongolische Rennmäuse</p>
                    <nav class="site-footer-nav" aria-label="Rechtliches">
                      <a href="{navBase}impressum/index.html" class="site-footer-link">Impressum</a>
                      <a href="{navBase}datenschutz/index.html" class="site-footer-link">Datenschutz</a>
                    </nav>
                  </footer>
                </body>
                </html>
                """;
        }

        private static string BuildNav(IReadOnlyList<string> slugs, string current, string navBase)
        {
            var labels = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
            {
                ["start"] = "Start",
                ["ueber-die-zucht"] = "Über die Zucht",
                ["abgabetiere"] = "Abgabetiere",
                ["abgabebedingungen"] = "Abgabebedingungen",
                ["farben-genetik"] = "Farben & Genetik",
                ["kontakt"] = "Kontakt",
                ["impressum"] = "Impressum",
                ["datenschutz"] = "Datenschutz",
            };

            var sb = new StringBuilder();
            foreach (var slug in slugs)
            {
                var label = labels.TryGetValue(slug, out var l) ? l : slug;
                var href = slug == "start" ? $"{navBase}index.html" : $"{navBase}{slug}/index.html";
                var active = slug == current ? " aria-current=\"page\"" : "";
                sb.AppendLine($"      <a href=\"{H(href)}\" class=\"site-nav-link\"{active}>{H(label)}</a>");
            }
            return sb.ToString().TrimEnd();
        }

        // ── Minimal Markdown → HTML ──────────────────────────────────────────

        internal static string MarkdownToHtml(string md)
        {
            if (string.IsNullOrEmpty(md)) return "";
            var lines = md.Replace("\r\n", "\n").Replace("\r", "\n").Split('\n');
            var sb = new StringBuilder();
            var inList = false;
            var isOrdered = false;

            void CloseList()
            {
                if (!inList) return;
                sb.AppendLine(isOrdered ? "</ol>" : "</ul>");
                inList = false;
            }

            foreach (var raw in lines)
            {
                var line = raw;

                // ATX headings
                var hm = Regex.Match(line, @"^(#{1,6})\s+(.+)$");
                if (hm.Success)
                {
                    CloseList();
                    var lvl = hm.Groups[1].Length;
                    sb.AppendLine($"<h{lvl}>{InlineHtml(hm.Groups[2].Value)}</h{lvl}>");
                    continue;
                }

                // Unordered list
                var ulm = Regex.Match(line, @"^[-*+]\s+(.+)$");
                if (ulm.Success)
                {
                    if (!inList || isOrdered) { CloseList(); sb.AppendLine("<ul>"); inList = true; isOrdered = false; }
                    sb.AppendLine($"<li>{InlineHtml(ulm.Groups[1].Value)}</li>");
                    continue;
                }

                // Ordered list
                var olm = Regex.Match(line, @"^\d+\.\s+(.+)$");
                if (olm.Success)
                {
                    if (!inList || !isOrdered) { CloseList(); sb.AppendLine("<ol>"); inList = true; isOrdered = true; }
                    sb.AppendLine($"<li>{InlineHtml(olm.Groups[1].Value)}</li>");
                    continue;
                }

                CloseList();

                // Blank line
                if (string.IsNullOrWhiteSpace(line))
                {
                    sb.AppendLine();
                    continue;
                }

                // Horizontal rule
                if (Regex.IsMatch(line, @"^(-{3,}|\*{3,}|_{3,})$"))
                {
                    sb.AppendLine("<hr>");
                    continue;
                }

                // Regular paragraph
                sb.AppendLine($"<p>{InlineHtml(line)}</p>");
            }

            CloseList();
            return sb.ToString().Trim();
        }

        private static string InlineHtml(string text)
        {
            // First HTML-encode, then selectively un-encode our safe inline patterns
            // to avoid encoding the tags we are about to add.
            // Order matters: bold before italic.
            text = H(text);
            // Bold **text**
            text = Regex.Replace(text, @"\*\*(.+?)\*\*", "<strong>$1</strong>");
            // Italic *text* (single, not preceded/followed by *)
            text = Regex.Replace(text, @"(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)", "<em>$1</em>");
            // Inline code `text`
            text = Regex.Replace(text, @"`(.+?)`", "<code>$1</code>");
            // Links [text](url) — note: url is already HTML-encoded by H()
            text = Regex.Replace(text, @"\[(.+?)\]\((.+?)\)", "<a href=\"$2\">$1</a>");
            return text;
        }

        // ── CSS ──────────────────────────────────────────────────────────────

        internal static string SiteCss() => """
            *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
            :root {
              --color-bg: #fdf7f0;
              --color-surface: #fff;
              --color-surface-warm: #fef9f4;
              --color-text: #2a2118;
              --color-muted: #7a6d62;
              --color-accent: #b5331a;
              --color-accent-hover: #8f2815;
              --color-accent-soft: #fdf0ed;
              --color-border: #e8ddd4;
              --color-badge-free: #2d7a4a;
              --color-badge-free-bg: #e6f4ec;
              --font-body: Georgia, "Times New Roman", serif;
              --font-ui: system-ui, -apple-system, sans-serif;
              --max-w: 900px;
              --radius: 10px;
            }
            body {
              font-family: var(--font-body);
              background: var(--color-bg);
              color: var(--color-text);
              line-height: 1.7;
              font-size: 1rem;
            }
            a { color: var(--color-accent); text-decoration: none; }
            a:hover { text-decoration: underline; color: var(--color-accent-hover); }
            img { max-width: 100%; height: auto; display: block; }
            p { margin-bottom: .75rem; }

            /* ── Header ── */
            .site-header {
              background: var(--color-surface);
              border-bottom: 2px solid var(--color-border);
              padding: 0 1rem;
              display: flex;
              align-items: stretch;
              gap: 0;
              min-height: 56px;
            }
            .site-logo {
              font-family: var(--font-body);
              font-weight: 700;
              font-size: 1.15rem;
              color: var(--color-text);
              white-space: nowrap;
              display: flex;
              align-items: center;
              padding-right: 1.5rem;
              border-right: 1px solid var(--color-border);
              text-decoration: none;
              flex-shrink: 0;
            }
            .site-logo:hover { color: var(--color-accent); text-decoration: none; }
            .site-nav {
              display: flex;
              align-items: center;
              gap: 0;
              overflow-x: auto;
              -webkit-overflow-scrolling: touch;
              scrollbar-width: none;
              padding-left: .5rem;
            }
            .site-nav::-webkit-scrollbar { display: none; }
            .site-nav-link {
              font-family: var(--font-ui);
              font-size: .875rem;
              color: var(--color-muted);
              padding: .5rem .65rem;
              border-radius: 6px;
              white-space: nowrap;
              transition: color .15s, background .15s;
            }
            .site-nav-link:hover { color: var(--color-accent); background: var(--color-accent-soft); text-decoration: none; }
            .site-nav-link[aria-current="page"] {
              color: var(--color-accent);
              background: var(--color-accent-soft);
              font-weight: 600;
            }

            /* ── Main ── */
            .site-main {
              max-width: var(--max-w);
              margin: 2.5rem auto;
              padding: 0 1rem 4rem;
            }

            /* ── Footer ── */
            .site-footer {
              background: var(--color-surface);
              border-top: 2px solid var(--color-border);
              padding: 1.75rem 1rem;
              text-align: center;
              font-family: var(--font-ui);
              font-size: .85rem;
              color: var(--color-muted);
            }
            .site-footer-nav { margin-top: .6rem; display: flex; justify-content: center; gap: 1.25rem; }
            .site-footer-link { color: var(--color-muted); font-size: .8rem; }
            .site-footer-link:hover { color: var(--color-accent); }

            /* ── CMS blocks ── */
            .cms-heading { margin: 2rem 0 .6rem; line-height: 1.3; }
            h1.cms-heading { font-size: 2rem; margin-top: 0; }
            h2.cms-heading { font-size: 1.5rem; }
            h3.cms-heading { font-size: 1.2rem; }
            .cms-richtext { margin: 1rem 0; }
            .cms-richtext p { margin-bottom: .75rem; }
            .cms-richtext ul, .cms-richtext ol { margin: .5rem 0 .75rem 1.5rem; }
            .cms-richtext li { margin-bottom: .35rem; }
            .cms-richtext hr { border: none; border-top: 1px solid var(--color-border); margin: 1.5rem 0; }
            .cms-image { margin: 1.75rem 0; }
            .cms-image img { border-radius: var(--radius); box-shadow: 0 2px 8px rgba(0,0,0,.08); }
            .cms-gallery {
              display: grid;
              grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
              gap: .75rem;
              margin: 1.75rem 0;
            }
            .cms-gallery-item img { border-radius: 6px; aspect-ratio: 1; object-fit: cover; }
            .cms-contact {
              font-style: normal;
              font-family: var(--font-ui);
              display: flex;
              flex-direction: column;
              gap: .5rem;
              margin: 1.25rem 0;
              background: var(--color-surface-warm);
              border: 1px solid var(--color-border);
              border-radius: var(--radius);
              padding: 1.25rem;
            }
            .cms-contact-row { display: flex; gap: .6rem; align-items: flex-start; }

            /* ── Abgabetiere — group cards ── */
            .cms-abgabe { margin: 1.25rem 0; }
            .cms-abgabe-intro { margin-bottom: 1.5rem; font-size: 1.05rem; }
            .cms-abgabe-empty { color: var(--color-muted); font-style: italic; }

            .cms-group-list { display: flex; flex-direction: column; gap: 2rem; }

            .cms-group-card {
              background: var(--color-surface);
              border: 1px solid var(--color-border);
              border-radius: var(--radius);
              overflow: hidden;
              box-shadow: 0 2px 6px rgba(0,0,0,.06);
            }
            .cms-group-header {
              padding: 1rem 1.25rem .75rem;
              display: flex;
              flex-wrap: wrap;
              align-items: center;
              gap: .5rem .75rem;
              border-bottom: 1px solid var(--color-border);
              background: var(--color-surface-warm);
            }
            .cms-group-name {
              font-family: var(--font-body);
              font-size: 1.2rem;
              font-weight: 700;
              color: var(--color-text);
            }
            .cms-group-meta {
              font-family: var(--font-ui);
              font-size: .875rem;
              color: var(--color-muted);
              display: flex;
              align-items: center;
              gap: .5rem;
            }

            /* availability badge */
            .cms-badge {
              display: inline-block;
              font-size: .75rem;
              font-weight: 600;
              font-family: var(--font-ui);
              padding: .2rem .55rem;
              border-radius: 20px;
              letter-spacing: .02em;
              text-transform: uppercase;
            }
            .cms-badge--free {
              background: var(--color-badge-free-bg);
              color: var(--color-badge-free);
            }

            /* group photo strip */
            .cms-group-gallery {
              display: flex;
              gap: .375rem;
              overflow-x: auto;
              -webkit-overflow-scrolling: touch;
              scrollbar-width: none;
              padding: .75rem 1.25rem;
              background: #f5ede4;
            }
            .cms-group-gallery::-webkit-scrollbar { display: none; }
            .cms-group-photo {
              width: 140px;
              height: 140px;
              object-fit: cover;
              border-radius: 6px;
              flex-shrink: 0;
            }

            /* animal detail list */
            .cms-animal-list {
              list-style: none;
              padding: .75rem 1.25rem 1.25rem;
              display: flex;
              flex-direction: column;
              gap: .875rem;
            }
            .cms-animal-row {
              display: flex;
              flex-direction: column;
              gap: .15rem;
              padding-bottom: .875rem;
              border-bottom: 1px solid var(--color-border);
            }
            .cms-animal-row:last-child { border-bottom: none; padding-bottom: 0; }
            .cms-animal-name {
              font-family: var(--font-body);
              font-size: 1.05rem;
              font-weight: 700;
              color: var(--color-text);
            }
            .cms-animal-detail {
              font-family: var(--font-ui);
              font-size: .875rem;
              color: var(--color-muted);
            }
            .cms-animal-note {
              font-style: italic;
              font-size: .925rem;
              color: var(--color-text);
              margin-top: .1rem;
            }

            /* ── Responsive ── */
            @media (max-width: 600px) {
              .site-header { padding: 0 .75rem; min-height: 50px; }
              .site-logo { font-size: 1rem; padding-right: 1rem; }
              h1.cms-heading { font-size: 1.5rem; }
              .site-main { margin-top: 1.5rem; }
              .cms-group-photo { width: 110px; height: 110px; }
            }
            @media (min-width: 700px) {
              .cms-animal-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 1rem; }
              .cms-animal-row { border-bottom: none; padding-bottom: 0; border: 1px solid var(--color-border); border-radius: 8px; padding: .75rem; }
            }
            """;

        // ── Helpers ─────────────────────────────────────────────────────────

        private static IReadOnlyList<string> NavSlugs(JsonObject snapshot)
        {
            var navArr = snapshot["site"]?["navOrder"] as JsonArray;
            if (navArr is null) return [];
            return navArr.Select(n => n?.GetValue<string>()).Where(s => s is not null).ToList()!;
        }

        /// <summary>
        /// HTML-encode only the 5 HTML special characters. Non-ASCII characters (e.g. German
        /// umlauts) are left as-is — the page declares UTF-8, so no entity encoding needed.
        /// </summary>
        private static string H(string? s)
        {
            if (s is null) return "";
            return s.Replace("&", "&amp;")
                    .Replace("<", "&lt;")
                    .Replace(">", "&gt;")
                    .Replace("\"", "&quot;")
                    .Replace("'", "&#39;");
        }

        /// <summary>Extract a string value from a JsonNode; returns null if not a string.</summary>
        private static string? Str(JsonNode? node) =>
            node is JsonValue v && v.TryGetValue<string>(out var s) ? s : null;
    }
}
