using System.Globalization;
using System.Text;

namespace Sunfish.Web;

/// <summary>
/// Construye HTML escapando todo texto. En modo clasico la pagina es ISO-8859-1 y solo contiene
/// ASCII: los caracteres Latin-1 van como referencias numericas (las entienden hasta los
/// navegadores de HTML 2.0) y el resto se translitera o se cambia por '?'. En modo moderno, UTF-8.
/// </summary>
internal sealed class HtmlBuilder(bool classic)
{
    private readonly StringBuilder _sb = new();

    public bool Classic { get; } = classic;

    public string Charset => Classic ? "iso-8859-1" : "utf-8";

    public string ContentType => "text/html; charset=" + Charset;

    /// <summary>HTML literal (debe ser ASCII).</summary>
    public HtmlBuilder Raw(string html)
    {
        _sb.Append(html);
        return this;
    }

    /// <summary>Texto escapado.</summary>
    public HtmlBuilder Text(string text)
    {
        _sb.Append(Escape(text, Classic));
        return this;
    }

    public byte[] ToBytes() =>
        Classic ? Encoding.Latin1.GetBytes(_sb.ToString()) : Encoding.UTF8.GetBytes(_sb.ToString());

    public override string ToString() => _sb.ToString();

    public static string Escape(string text, bool classic)
    {
        var sb = new StringBuilder(text.Length + 16);
        foreach (char c in text)
        {
            switch (c)
            {
                case '&': sb.Append("&amp;"); break;
                case '<': sb.Append("&lt;"); break;
                case '>': sb.Append("&gt;"); break;
                case '"': sb.Append("&quot;"); break;
                case '\'': sb.Append("&#39;"); break;
                default:
                    if (c < 0x20)
                        sb.Append('?');
                    else if (!classic || c < 0x80)
                        sb.Append(c);
                    else if (c is >= ' ' and <= 'ÿ')
                        sb.Append("&#").Append(((int)c).ToString(CultureInfo.InvariantCulture)).Append(';');
                    else
                        sb.Append(Transliterate(c));
                    break;
            }
        }
        return sb.ToString();
    }

    /// <summary>Quita los diacriticos ("ő" -> "o"); si no queda un caracter Latin-1, '?'.</summary>
    internal static string Transliterate(char c)
    {
        if (char.IsSurrogate(c))
            return "?";
        string decomposed = c.ToString().Normalize(NormalizationForm.FormD);
        foreach (char d in decomposed)
        {
            if (CharUnicodeInfo.GetUnicodeCategory(d) == UnicodeCategory.NonSpacingMark)
                continue;
            if (d < 0x80)
                return d.ToString();
            if (d is >= ' ' and <= 'ÿ')
                return "&#" + ((int)d).ToString(CultureInfo.InvariantCulture) + ";";
            break;
        }
        return "?";
    }

    /// <summary>
    /// Un componente de URL codificado como %XX de sus bytes UTF-8: solo ASCII, valido en
    /// cualquier pagina; el navegador devuelve los mismos bytes al seguir el enlace.
    /// </summary>
    public static string UrlSegment(string name) => Uri.EscapeDataString(name);
}
