using System.Runtime.InteropServices;
using System.Text;

namespace Sunfish.Cli.Tui;

/// <summary>Terminal ANSI/VT: pantalla alternativa, colores y tamano. Sin bibliotecas externas.</summary>
internal static partial class Terminal
{
    public const string Reset = "\e[0m";
    public const string Bold = "\e[1m";
    public const string Dim = "\e[2m";
    public const string Reverse = "\e[7m";

    /// <summary>El terminal acepta color de 24 bits (Windows Terminal lo anuncia con WT_SESSION).</summary>
    public static bool TrueColor { get; } =
        (Environment.GetEnvironmentVariable("COLORTERM") ?? "") is "truecolor" or "24bit" ||
        Environment.GetEnvironmentVariable("WT_SESSION") is not null;

    public static int Width => Math.Max(40, SafeSize(() => Console.WindowWidth, 80));
    public static int Height => Math.Max(12, SafeSize(() => Console.WindowHeight, 25));

    private static int SafeSize(Func<int> get, int fallback)
    {
        try
        {
            int value = get();
            return value > 0 ? value : fallback;
        }
        catch (IOException)
        {
            return fallback;
        }
    }

    public static void Enter()
    {
        EnableVirtualTerminal();
        Console.Write("\e[?1049h\e[?25l\e[H\e[2J");
    }

    public static void Leave() => Console.Write("\e[0m\e[?25h\e[?1049l");

    /// <summary>Escribe el fotograma entero de una vez (sin parpadeo).</summary>
    public static void Draw(string frame) => Console.Write(frame);

    public static string Fg(int r, int g, int b) => TrueColor ? $"\e[38;2;{r};{g};{b}m" : $"\e[38;5;{To256(r, g, b)}m";
    public static string Bg(int r, int g, int b) => TrueColor ? $"\e[48;2;{r};{g};{b}m" : $"\e[48;5;{To256(r, g, b)}m";

    public static string Fg16(int code) => $"\e[{code}m";

    /// <summary>Color mas cercano del cubo 6x6x6 de la paleta de 256 colores.</summary>
    internal static int To256(int r, int g, int b)
    {
        static int Level(int v) => v < 48 ? 0 : v < 115 ? 1 : (v - 35) / 40;
        return 16 + 36 * Level(r) + 6 * Level(g) + Level(b);
    }

    /// <summary>
    /// Como <see cref="Fit"/> pero con texto que ya lleva secuencias de color: solo cuentan los
    /// caracteres visibles y las secuencias se conservan.
    /// </summary>
    public static string FitVisible(string text, int width)
    {
        var sb = new StringBuilder();
        int visible = 0;
        for (int i = 0; i < text.Length; i++)
        {
            char c = text[i];
            if (c == '\e')
            {
                int end = i + 1;
                if (end < text.Length && text[end] == '[')
                {
                    end++;
                    while (end < text.Length && !char.IsAsciiLetter(text[end]))
                        end++;
                }
                sb.Append(text, i, Math.Min(end, text.Length - 1) - i + 1);
                i = end;
                continue;
            }
            if (visible == width)
                break;
            if (visible == width - 1 && HasMoreVisible(text, i + 1))
            {
                sb.Append('…');
                visible++;
                break;
            }
            sb.Append(char.IsControl(c) ? '?' : c);
            visible++;
        }
        if (visible < width)
            sb.Append(Reset).Append(' ', width - visible);
        return sb.ToString();
    }

    private static bool HasMoreVisible(string text, int from)
    {
        for (int i = from; i < text.Length; i++)
        {
            if (text[i] == '\e')
            {
                i++;
                if (i < text.Length && text[i] == '[')
                    while (i < text.Length && !char.IsAsciiLetter(text[i]))
                        i++;
                continue;
            }
            return true;
        }
        return false;
    }

    /// <summary>Texto recortado o rellenado a un ancho exacto (contando caracteres).</summary>
    public static string Fit(string text, int width)
    {
        if (width <= 0)
            return "";
        var sb = new StringBuilder();
        foreach (char c in text)
            sb.Append(char.IsControl(c) ? '?' : c);
        string clean = sb.ToString();
        if (clean.Length > width)
            return width > 1 ? clean[..(width - 1)] + "…" : clean[..width];
        return clean.PadRight(width);
    }

    private static void EnableVirtualTerminal()
    {
        if (!OperatingSystem.IsWindows())
            return;
        const int StdOutputHandle = -11;
        const uint EnableVirtualTerminalProcessing = 0x0004;
        nint handle = GetStdHandle(StdOutputHandle);
        if (GetConsoleMode(handle, out uint mode))
            SetConsoleMode(handle, mode | EnableVirtualTerminalProcessing);
    }

    [LibraryImport("kernel32.dll")]
    private static partial nint GetStdHandle(int nStdHandle);

    [LibraryImport("kernel32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static partial bool GetConsoleMode(nint hConsoleHandle, out uint lpMode);

    [LibraryImport("kernel32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static partial bool SetConsoleMode(nint hConsoleHandle, uint dwMode);
}
