using System.Net;
using System.Text;
using JobTrackerApi.Models;

namespace JobTrackerApi.Services;

public sealed record TailoredCvRenderResult(string TemplateId, string SuggestedFileName, string Html);

public interface ICvTemplateRenderer
{
    TailoredCvRenderResult Render(TailoredCvDocument? document, string? templateId, string candidateName, string jobTitle, string? companyName, string? photoDataUrl = null);
}

public sealed class CvTemplateRenderer : ICvTemplateRenderer
{
    public TailoredCvRenderResult Render(TailoredCvDocument? document, string? templateId, string candidateName, string jobTitle, string? companyName, string? photoDataUrl = null)
    {
        var normalized = TailoredCvDraftJson.Normalize(document);
        var effectiveTemplateId = TailoredCvTemplateCatalog.NormalizeId(templateId ?? normalized.TemplateId);
        normalized.TemplateId = effectiveTemplateId;
        var suggestedFileName = Slugify($"{candidateName}-{jobTitle}-{effectiveTemplateId}") + ".pdf";
        var html = effectiveTemplateId switch
        {
            "harvard" => RenderHarvard(normalized, candidateName, jobTitle, companyName),
            "auckland" => RenderSidebar(normalized, candidateName, jobTitle, companyName, photoDataUrl, "Auckland", roundedPhoto: false, curvedHeader: false),
            "edinburgh" => RenderSidebar(normalized, candidateName, jobTitle, companyName, photoDataUrl, "Edinburgh", roundedPhoto: true, curvedHeader: true),
            "monarch" => RenderMonarch(normalized, candidateName, jobTitle, companyName, photoDataUrl),
            "fjord" => RenderFjord(normalized, candidateName, jobTitle, companyName, photoDataUrl),
            _ => RenderAtsMinimal(normalized, candidateName, jobTitle, companyName, photoDataUrl)
        };
        return new TailoredCvRenderResult(effectiveTemplateId, suggestedFileName, html);
    }

    private static string RenderAtsMinimal(TailoredCvDocument document, string candidateName, string jobTitle, string? companyName, string? photoDataUrl)
    {
        var accent = TailoredCvTemplateCatalog.ResolveAccent(document.RenderOptions.AccentColor);
        var showPhoto = document.RenderOptions.ShowPhoto && !string.IsNullOrWhiteSpace(photoDataUrl);
        var body = RenderMainSections(document, accent, headingStyle: "caps-rule");
        var companyFocusMarkup = string.IsNullOrWhiteSpace(companyName) ? string.Empty : $"<span>Company focus: {Encode(companyName)}</span>";
        var photoMarkup = showPhoto ? $"<div class=\"photo\"><img src=\"{EncodeAttribute(photoDataUrl)}\" alt=\"Profile photo\" /></div>" : string.Empty;

        return $@"<!DOCTYPE html>
<html lang=""en"">
<head>
  <meta charset=""utf-8"" />
  <title>{Encode(candidateName)} — ATS Minimal</title>
  <style>
    :root {{ --accent:{accent}; --ink:#111827; --muted:#4b5563; --line:#d1d5db; --paper:#fff; }}
    * {{ box-sizing:border-box; }}
    body {{ margin:0; background:#eef2f7; color:var(--ink); font-family:Georgia, 'Times New Roman', serif; -webkit-print-color-adjust:exact; print-color-adjust:exact; }}
    .page {{ width:210mm; min-height:297mm; margin:0 auto; background:var(--paper); padding:16mm; }}
    .header {{ display:grid; grid-template-columns:minmax(0,1fr) auto; gap:6mm; border-bottom:2px solid var(--accent); padding-bottom:8mm; margin-bottom:7mm; }}
    .name {{ margin:0; font-size:25pt; letter-spacing:.02em; }}
    .headline {{ margin-top:2mm; color:var(--muted); font-size:11pt; }}
    .meta {{ margin-top:3mm; display:flex; flex-wrap:wrap; gap:3mm; color:var(--muted); font-size:9pt; }}
    .photo {{ width:28mm; height:36mm; border-radius:5mm; overflow:hidden; border:1px solid var(--line); }}
    .photo img {{ width:100%; height:100%; object-fit:cover; display:block; }}
    {BaseSectionCss(accent, "caps-rule")}
    @page {{ size:A4; margin:0; }}
  </style>
</head>
<body>
  <main class=""page"">
    <header class=""header"">
      <div>
        <h1 class=""name"">{Encode(candidateName)}</h1>
        <div class=""headline"">{Encode(document.Headline ?? jobTitle)}</div>
        <div class=""meta""><span>Target role: {Encode(jobTitle)}</span>{companyFocusMarkup}<span>Template: ATS Minimal</span></div>
      </div>
      {photoMarkup}
    </header>
    {body}
  </main>
</body>
</html>";
    }

    private static string RenderHarvard(TailoredCvDocument document, string candidateName, string jobTitle, string? companyName)
    {
        var accent = TailoredCvTemplateCatalog.ResolveAccent(document.RenderOptions.AccentColor);
        var body = RenderMainSections(document, accent, headingStyle: "harvard");
        var contactLine = string.Join(" &nbsp;•&nbsp; ", new[]
        {
            string.IsNullOrWhiteSpace(companyName) ? null : $"Targeting {Encode(companyName)}",
            Encode(jobTitle)
        }.Where(x => !string.IsNullOrWhiteSpace(x)));

        return $@"<!DOCTYPE html>
<html lang=""en"">
<head>
  <meta charset=""utf-8"" />
  <title>{Encode(candidateName)} — Harvard</title>
  <style>
    :root {{ --accent:{accent}; --ink:#111; --muted:#333; --line:#111; --paper:#fff; }}
    * {{ box-sizing:border-box; }}
    body {{ margin:0; background:#f5f5f5; color:var(--ink); font-family:Georgia, 'Times New Roman', serif; -webkit-print-color-adjust:exact; print-color-adjust:exact; }}
    .page {{ width:210mm; min-height:297mm; margin:0 auto; background:#fff; padding:16mm 18mm; }}
    .header {{ text-align:center; margin-bottom:6mm; }}
    .name {{ margin:0; font-size:23pt; font-weight:700; }}
    .headline {{ margin-top:2mm; font-size:10pt; font-style:italic; }}
    .meta {{ margin-top:4mm; font-size:9pt; }}
    {BaseSectionCss(accent, "harvard")}
    .section-title {{ color:var(--ink); }}
    @page {{ size:A4; margin:0; }}
  </style>
</head>
<body>
  <main class=""page"">
    <header class=""header"">
      <h1 class=""name"">{Encode(candidateName)}</h1>
      <div class=""headline"">{Encode(document.Headline ?? jobTitle)}</div>
      <div class=""meta"">{contactLine}</div>
    </header>
    {body}
  </main>
</body>
</html>";
    }

    private static string RenderSidebar(TailoredCvDocument document, string candidateName, string jobTitle, string? companyName, string? photoDataUrl, string templateLabel, bool roundedPhoto, bool curvedHeader)
    {
        var accent = TailoredCvTemplateCatalog.ResolveAccent(document.RenderOptions.AccentColor);
        var showPhoto = document.RenderOptions.ShowPhoto && !string.IsNullOrWhiteSpace(photoDataUrl);
        var sidebarSections = new StringBuilder();
        sidebarSections.Append(RenderSidebarMetaSection("Personal Details", new[]
        {
            $"Name\n{candidateName}",
            $"Target role\n{jobTitle}",
            string.IsNullOrWhiteSpace(companyName) ? null : $"Company focus\n{companyName}"
        }));
        if (document.CustomSections.Count > 0)
        {
            foreach (var section in document.CustomSections.Take(2))
            {
                sidebarSections.Append(RenderSidebarMetaSection(section.Title ?? "Additional", section.Items));
            }
        }
        if (document.SelectedSkills.Count > 0)
        {
            sidebarSections.Append(RenderSidebarMetaSection("Skills", document.SelectedSkills.Take(8)));
        }

        var main = RenderMainSections(document, accent, headingStyle: "sidebar");
        var photoClass = roundedPhoto ? "photo round" : "photo";
        var photoMarkup = showPhoto ? $"<div class=\"{photoClass}\"><img src=\"{EncodeAttribute(photoDataUrl)}\" alt=\"Profile photo\" /></div>" : string.Empty;
        var heroClass = curvedHeader ? "hero curved" : "hero";

        return $@"<!DOCTYPE html>
<html lang=""en"">
<head>
  <meta charset=""utf-8"" />
  <title>{Encode(candidateName)} — {Encode(templateLabel)}</title>
  <style>
    :root {{ --accent:{accent}; --ink:#1f2937; --muted:#4b5563; --line:#d1d5db; --sidebar:#f3f4f6; --paper:#fff; }}
    * {{ box-sizing:border-box; }}
    body {{ margin:0; background:#edf2f7; color:var(--ink); font-family:Arial, Helvetica, sans-serif; -webkit-print-color-adjust:exact; print-color-adjust:exact; }}
    .page {{ width:210mm; min-height:297mm; margin:0 auto; background:#fff; display:grid; grid-template-columns:34% minmax(0,66%); }}
    .sidebar {{ background:linear-gradient(180deg, var(--accent), color-mix(in srgb, var(--accent) 8%, white)); color:#fff; padding:12mm 8mm 12mm 10mm; }}
    .hero {{ margin:-12mm -8mm 8mm -10mm; padding:10mm 10mm 8mm 10mm; background:var(--accent); }}
    .hero.curved {{ border-bottom-right-radius:28mm; }}
    .name {{ margin:0; font-size:18pt; letter-spacing:.08em; font-weight:700; }}
    .headline {{ margin-top:2mm; font-size:10pt; opacity:.95; }}
    .photo {{ width:34mm; height:34mm; margin-top:6mm; border:2px solid rgba(255,255,255,.85); overflow:hidden; }}
    .photo.round {{ border-radius:50%; }}
    .photo img {{ width:100%; height:100%; object-fit:cover; display:block; }}
    .sidebar-section {{ margin-top:7mm; }}
    .sidebar-title {{ margin:0 0 3mm 0; font-size:9pt; text-transform:uppercase; letter-spacing:.16em; }}
    .sidebar-item {{ margin:0 0 2.4mm 0; font-size:8.8pt; line-height:1.4; white-space:pre-line; }}
    .content {{ padding:14mm 14mm 14mm 10mm; }}
    {BaseSectionCss(accent, "sidebar")}
    @page {{ size:A4; margin:0; }}
  </style>
</head>
<body>
  <main class=""page"">
    <aside class=""sidebar"">
      <div class=""{heroClass}"">
        <h1 class=""name"">{Encode(candidateName)}</h1>
        <div class=""headline"">{Encode(document.Headline ?? jobTitle)}</div>
        {photoMarkup}
      </div>
      {sidebarSections}
    </aside>
    <section class=""content"">
      {main}
    </section>
  </main>
</body>
</html>";
    }

    private static string RenderMonarch(TailoredCvDocument document, string candidateName, string jobTitle, string? companyName, string? photoDataUrl)
    {
        var accent = TailoredCvTemplateCatalog.ResolveAccent(document.RenderOptions.AccentColor);
        var showPhoto = document.RenderOptions.ShowPhoto && !string.IsNullOrWhiteSpace(photoDataUrl);
        var photoMarkup = showPhoto ? $"<div class=\"monarch-photo\"><img src=\"{EncodeAttribute(photoDataUrl)}\" alt=\"Profile photo\" /></div>" : string.Empty;
        var body = RenderMainSections(document, accent, headingStyle: "sidebar");
        var companyMarkup = string.IsNullOrWhiteSpace(companyName) ? string.Empty : $"<div class=\"monarch-company\">Tailored toward {Encode(companyName)}</div>";
        return $@"<!DOCTYPE html>
<html lang=""en"">
<head>
  <meta charset=""utf-8"" />
  <title>{Encode(candidateName)} — Monarch</title>
  <style>
    :root {{ --accent:{accent}; --ink:#1c1917; --muted:#57534e; --paper:#fffdf8; --panel:#f7efe6; --line:#d6c1a8; }}
    * {{ box-sizing:border-box; }}
    body {{ margin:0; background:#efe7dc; color:var(--ink); font-family:'Times New Roman', Georgia, serif; -webkit-print-color-adjust:exact; print-color-adjust:exact; }}
    .page {{ width:210mm; min-height:297mm; margin:0 auto; background:var(--paper); padding:16mm; }}
    .monarch-shell {{ border:1px solid var(--line); padding:10mm; position:relative; }}
    .monarch-shell::before {{ content:''; position:absolute; inset:6mm; border:1px solid color-mix(in srgb, var(--line) 70%, white); pointer-events:none; }}
    .monarch-header {{ display:grid; grid-template-columns:minmax(0,1fr) auto; gap:6mm; align-items:center; margin-bottom:8mm; }}
    .monarch-kicker {{ display:inline-block; text-transform:uppercase; letter-spacing:.3em; font-size:8pt; color:var(--accent); margin-bottom:2mm; }}
    .monarch-name {{ margin:0; font-size:28pt; line-height:1.05; }}
    .monarch-headline {{ margin-top:2mm; font-size:11pt; color:var(--muted); max-width:130mm; }}
    .monarch-company {{ margin-top:2mm; font-size:9pt; color:var(--accent); text-transform:uppercase; letter-spacing:.16em; }}
    .monarch-photo {{ width:30mm; height:38mm; border:1px solid var(--line); background:var(--panel); overflow:hidden; }}
    .monarch-photo img {{ width:100%; height:100%; object-fit:cover; display:block; }}
    .monarch-summary {{ margin-bottom:5mm; padding:4mm 5mm; background:var(--panel); border-left:3px solid var(--accent); font-size:10pt; color:var(--muted); }}
    {BaseSectionCss(accent, "harvard")}
    .section-title {{ text-transform:uppercase; letter-spacing:.12em; font-size:10pt; }}
    @page {{ size:A4; margin:0; }}
  </style>
</head>
<body>
  <main class=""page"">
    <section class=""monarch-shell"">
      <header class=""monarch-header"">
        <div>
          <span class=""monarch-kicker"">Executive CV</span>
          <h1 class=""monarch-name"">{Encode(candidateName)}</h1>
          <div class=""monarch-headline"">{Encode(document.Headline ?? jobTitle)}</div>
          {companyMarkup}
        </div>
        {photoMarkup}
      </header>
      {(!string.IsNullOrWhiteSpace(jobTitle) ? $"<div class=\"monarch-summary\">Primary role target: {Encode(jobTitle)}</div>" : string.Empty)}
      {body}
    </section>
  </main>
</body>
</html>";
    }

    private static string RenderFjord(TailoredCvDocument document, string candidateName, string jobTitle, string? companyName, string? photoDataUrl)
    {
        var accent = TailoredCvTemplateCatalog.ResolveAccent(document.RenderOptions.AccentColor);
        var showPhoto = document.RenderOptions.ShowPhoto && !string.IsNullOrWhiteSpace(photoDataUrl);
        var body = RenderMainSections(document, accent, headingStyle: "sidebar");
        var photoMarkup = showPhoto ? $"<div class=\"fjord-photo\"><img src=\"{EncodeAttribute(photoDataUrl)}\" alt=\"Profile photo\" /></div>" : string.Empty;
        var companyMarkup = string.IsNullOrWhiteSpace(companyName) ? string.Empty : $"<span>{Encode(companyName)}</span>";
        return $@"<!DOCTYPE html>
<html lang=""en"">
<head>
  <meta charset=""utf-8"" />
  <title>{Encode(candidateName)} — Fjord</title>
  <style>
    :root {{ --accent:{accent}; --ink:#102a43; --muted:#486581; --panel:#e6f1f3; --line:#9fb3c8; --paper:#fbfdff; }}
    * {{ box-sizing:border-box; }}
    body {{ margin:0; background:#d9e8ef; color:var(--ink); font-family:Arial, Helvetica, sans-serif; -webkit-print-color-adjust:exact; print-color-adjust:exact; }}
    .page {{ width:210mm; min-height:297mm; margin:0 auto; background:var(--paper); padding:0; }}
    .fjord-grid {{ display:grid; grid-template-columns:72mm minmax(0,1fr); min-height:297mm; }}
    .fjord-rail {{ background:linear-gradient(180deg, var(--accent), color-mix(in srgb, var(--accent) 15%, white)); color:white; padding:16mm 8mm; }}
    .fjord-name {{ margin:0; font-size:21pt; line-height:1.08; }}
    .fjord-headline {{ margin-top:2mm; font-size:10pt; opacity:.95; }}
    .fjord-meta {{ margin-top:4mm; font-size:8.5pt; display:flex; flex-direction:column; gap:1.2mm; opacity:.9; }}
    .fjord-photo {{ width:28mm; height:28mm; border-radius:50%; overflow:hidden; border:2px solid rgba(255,255,255,.65); margin-top:6mm; }}
    .fjord-photo img {{ width:100%; height:100%; object-fit:cover; display:block; }}
    .fjord-main {{ padding:14mm 14mm 14mm 10mm; }}
    {BaseSectionCss(accent, "sidebar")}
    .section {{ margin-top:5mm; }}
    .skills {{ gap:1.5mm; }}
    .skill-pill {{ background:var(--panel); border-color:transparent; color:var(--ink); }}
    @page {{ size:A4; margin:0; }}
  </style>
</head>
<body>
  <main class=""page"">
    <section class=""fjord-grid"">
      <aside class=""fjord-rail"">
        <h1 class=""fjord-name"">{Encode(candidateName)}</h1>
        <div class=""fjord-headline"">{Encode(document.Headline ?? jobTitle)}</div>
        <div class=""fjord-meta""><span>{Encode(jobTitle)}</span>{companyMarkup}<span>Template: Fjord</span></div>
        {photoMarkup}
      </aside>
      <section class=""fjord-main"">{body}</section>
    </section>
  </main>
</body>
</html>";
    }

    private static string RenderMainSections(TailoredCvDocument document, string accent, string headingStyle)
    {
        var sectionOrder = document.RenderOptions.SectionOrder.Count == 0
            ? new List<string> { "summary", "skills", "experience", "education", "custom" }
            : document.RenderOptions.SectionOrder;

        var sections = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
        {
            ["summary"] = RenderListSection("Profile", document.Summary, bulletList: true),
            ["skills"] = RenderSkillSection(document.SelectedSkills),
            ["experience"] = RenderExperienceSection(document.Experience),
            ["education"] = RenderEducationSection(document.Education),
            ["custom"] = string.Join(string.Empty, document.CustomSections.Select(RenderCustomSection)),
        };

        return string.Join(string.Empty, sectionOrder
            .Select(key => sections.TryGetValue(key, out var section) ? section : string.Empty)
            .Where(section => !string.IsNullOrWhiteSpace(section)));
    }

    private static string BaseSectionCss(string accent, string headingStyle)
    {
        var headingCss = headingStyle switch
        {
            "harvard" => ".section-title{font-size:17pt;font-weight:700;border-bottom:1.5px solid var(--line);padding-bottom:1.5mm;margin-bottom:3mm;}",
            "sidebar" => ".section-title{font-size:14pt;font-weight:700;letter-spacing:.02em;margin-bottom:3mm;}",
            _ => ".section-title{font-size:9pt;letter-spacing:.16em;text-transform:uppercase;color:var(--accent);border-bottom:1px solid var(--line);padding-bottom:1.5mm;margin-bottom:3mm;}"
        };

        return $@"
    .page,.page *{{min-width:0;}}
    .page{{overflow:visible;overflow-wrap:anywhere;word-break:normal;}}
    .name,.headline,.meta,.monarch-name,.monarch-headline,.monarch-company,.fjord-name,.fjord-headline,.fjord-meta,.sidebar-item{{overflow-wrap:anywhere;word-break:break-word;}}
    .section{{margin-top:6mm;}}
    {headingCss}
    .summary,.custom-list,.education-list,.experience-bullets{{margin:0;padding-left:4.5mm;}}
    .summary li,.custom-list li,.education-list li,.experience-bullets li{{margin:0 0 1.6mm 0;line-height:1.42;overflow-wrap:anywhere;break-inside:avoid-page;page-break-inside:avoid;orphans:2;widows:2;}}
    .summary li.item-flow,.custom-list li.item-flow,.education-list li.item-flow,.experience-bullets li.item-flow{{break-inside:auto;page-break-inside:auto;}}
    .skills{{list-style:none;padding-left:0;display:flex;flex-wrap:wrap;gap:2mm;}}
    .skill-pill{{border:1px solid var(--line);border-radius:999px;padding:1mm 2.4mm;font-size:9pt;max-width:100%;overflow-wrap:anywhere;}}
    .entry{{margin-bottom:4.8mm;break-inside:avoid-page;page-break-inside:avoid;}}
    .entry.entry-flow{{break-inside:auto;page-break-inside:auto;}}
    .entry-header{{display:flex;justify-content:space-between;gap:1.5mm 4mm;align-items:baseline;flex-wrap:wrap;margin-bottom:1.2mm;break-after:avoid-page;page-break-after:avoid;}}
    .entry-title{{font-weight:700;font-size:11pt;flex:1 1 50mm;overflow-wrap:anywhere;}}
    .entry-meta{{color:var(--muted);font-size:9pt;text-align:right;white-space:normal;max-width:100%;overflow-wrap:anywhere;}}
    .entry-subtitle{{color:var(--muted);font-size:9.5pt;margin-bottom:1.3mm;overflow-wrap:anywhere;}}
    .section-title{{break-after:avoid-page;page-break-after:avoid;}}
    @media print{{body{{background:transparent;}}}}";
    }

    private static string RenderSidebarMetaSection(string title, IEnumerable<string?> items)
    {
        var content = string.Join(string.Empty, items.Where(item => !string.IsNullOrWhiteSpace(item)).Select(item => $"<p class=\"sidebar-item\">{Encode(item)}</p>"));
        if (string.IsNullOrWhiteSpace(content)) return string.Empty;
        return $"<section class=\"sidebar-section\"><h2 class=\"sidebar-title\">{Encode(title)}</h2>{content}</section>";
    }

    private static string RenderListSection(string title, IReadOnlyCollection<string> items, bool bulletList)
    {
        if (items.Count == 0) return string.Empty;
        var tag = bulletList ? "summary" : "custom-list";
        return $"<section class=\"section\"><h2 class=\"section-title\">{Encode(title)}</h2><ul class=\"{tag}\">{string.Join(string.Empty, items.Select(RenderListItem))}</ul></section>";
    }

    private static string RenderSkillSection(IReadOnlyCollection<string> skills)
    {
        if (skills.Count == 0) return string.Empty;
        return $"<section class=\"section\"><h2 class=\"section-title\">Skills</h2><ul class=\"skills\">{string.Join(string.Empty, skills.Select(skill => $"<li class=\"skill-pill\">{Encode(skill)}</li>"))}</ul></section>";
    }

    private static string RenderExperienceSection(IReadOnlyCollection<TailoredCvExperienceItem> experience)
    {
        if (experience.Count == 0) return string.Empty;
        var items = new StringBuilder();
        foreach (var entry in experience)
        {
            var subtitle = string.Join(" · ", new[] { entry.Company, entry.Location }.Where(x => !string.IsNullOrWhiteSpace(x)).Select(Encode));
            var dateRange = FormatDateRange(entry.Start, entry.End, entry.IsCurrent);
            items.Append(IsFlowingEntry(entry.Title, subtitle, dateRange, entry.Bullets)
                ? "<article class=\"entry entry-flow\">"
                : "<article class=\"entry\">");
            items.Append($"<div class=\"entry-header\"><div class=\"entry-title\">{Encode(entry.Title)}</div><div class=\"entry-meta\">{Encode(dateRange)}</div></div>");
            if (!string.IsNullOrWhiteSpace(subtitle)) items.Append($"<div class=\"entry-subtitle\">{subtitle}</div>");
            if (entry.Bullets.Count > 0) items.Append($"<ul class=\"experience-bullets\">{string.Join(string.Empty, entry.Bullets.Select(RenderListItem))}</ul>");
            items.Append("</article>");
        }
        return $"<section class=\"section\"><h2 class=\"section-title\">Professional Experience</h2>{items}</section>";
    }

    private static string RenderEducationSection(IReadOnlyCollection<TailoredCvEducationItem> education)
    {
        if (education.Count == 0) return string.Empty;
        var items = new StringBuilder();
        foreach (var entry in education)
        {
            var subtitle = string.Join(" · ", new[] { entry.Institution, entry.Location, FormatDateRange(entry.Start, entry.End, false) }
                .Where(x => !string.IsNullOrWhiteSpace(x))
                .Select(Encode));
            items.Append(IsFlowingEntry(entry.Qualification, subtitle, null, entry.Details)
                ? "<article class=\"entry entry-flow\">"
                : "<article class=\"entry\">");
            var title = string.IsNullOrWhiteSpace(entry.QualificationLevel)
                ? entry.Qualification
                : $"{entry.Qualification} ({entry.QualificationLevel})";
            items.Append($"<div class=\"entry-title\">{Encode(title)}</div>");
            if (!string.IsNullOrWhiteSpace(subtitle)) items.Append($"<div class=\"entry-subtitle\">{subtitle}</div>");
            if (entry.Details.Count > 0) items.Append($"<ul class=\"education-list\">{string.Join(string.Empty, entry.Details.Select(RenderListItem))}</ul>");
            items.Append("</article>");
        }
        return $"<section class=\"section\"><h2 class=\"section-title\">Education</h2>{items}</section>";
    }

    private static string RenderCustomSection(TailoredCvCustomSection section)
    {
        if (section.Items.Count == 0) return string.Empty;
        return $"<section class=\"section\"><h2 class=\"section-title\">{Encode(section.Title ?? "Additional Information")}</h2><ul class=\"custom-list\">{string.Join(string.Empty, section.Items.Select(RenderListItem))}</ul></section>";
    }

    private static string RenderListItem(string? value) =>
        $"<li{(IsFlowingItem(value) ? " class=\"item-flow\"" : string.Empty)}>{Encode(value)}</li>";

    private static bool IsFlowingEntry(string? title, string? subtitle, string? meta, IEnumerable<string> items)
    {
        var values = items.ToList();
        var textLength = (title?.Length ?? 0) + (subtitle?.Length ?? 0) + (meta?.Length ?? 0)
            + values.Sum(item => item?.Length ?? 0);
        return values.Count > 5 || values.Any(IsFlowingItem) || textLength > 900;
    }

    private static bool IsFlowingItem(string? value) => (value?.Length ?? 0) > 360;

    private static string FormatDateRange(string? start, string? end, bool isCurrent)
    {
        var normalizedStart = (start ?? string.Empty).Trim();
        var normalizedEnd = (end ?? string.Empty).Trim();
        if (string.IsNullOrWhiteSpace(normalizedStart) && string.IsNullOrWhiteSpace(normalizedEnd)) return string.Empty;
        if (string.IsNullOrWhiteSpace(normalizedStart)) return normalizedEnd;
        return $"{normalizedStart} - {(isCurrent ? "Present" : string.IsNullOrWhiteSpace(normalizedEnd) ? "Present" : normalizedEnd)}";
    }

    private static string Encode(string? value) => WebUtility.HtmlEncode(value ?? string.Empty);
    private static string EncodeAttribute(string? value) => WebUtility.HtmlEncode(value ?? string.Empty).Replace("'", "&#39;", StringComparison.Ordinal);

    private static string Slugify(string value)
    {
        var cleaned = new string((value ?? string.Empty).ToLowerInvariant().Select(ch => char.IsLetterOrDigit(ch) ? ch : '-').ToArray());
        while (cleaned.Contains("--", StringComparison.Ordinal)) cleaned = cleaned.Replace("--", "-", StringComparison.Ordinal);
        return cleaned.Trim('-');
    }
}
