using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using static System.ConsoleColor;
using static System.ConsoleKey;
using static System.StringComparison;

namespace InnovEnergy.Lib.Utils;

public static class AutoComplete
{
    [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
    public static String? ChooseFrom(this String prompt,
                             IEnumerable<String> chooseFrom,
                                          String initialInput = "",
                                           Int32 nPopUpLines = 7)
    {
        if (chooseFrom == null || !chooseFrom.Any())
            throw new ArgumentException(nameof(chooseFrom));

        var chooseFromSorted = chooseFrom.OrderBy(s => s).ToList();

        if (chooseFromSorted.Count == 0 || chooseFromSorted.Any(StringUtils.IsNullOrEmpty))
            throw new ArgumentException(nameof(chooseFrom));

        var acceptedChars = chooseFromSorted
                           .SelectMany(c => c.ToLower())
                           .ToHashSet();

        var filtered = chooseFromSorted;

        prompt = prompt.TrimEnd() + " ";

        var input        = initialInput;
        var selectedLine = 0;
        var defaultBg    = Console.BackgroundColor;
        var maxLen       = chooseFromSorted.Max(n => n.Length);

        prompt.Write();

        // TODO: remove handler when done
        Console.CancelKeyPress += (_, _) =>
        {
            ClearPopUp();
            BackToInput();
        };
        
        while (true)
        {
            filtered = chooseFromSorted.Any(e => e.Contains(input, InvariantCultureIgnoreCase))
                     ? chooseFromSorted.Where(e => e.Contains(input, InvariantCultureIgnoreCase)).ToList()
                     : filtered;

            Debug.Assert(filtered.Count > 0);

            var nChoices = Math.Min(nPopUpLines, filtered.Count);

            ShowInput(input, prompt);
            ShowPopUp(nChoices);
            BackToInput();

            var keyInfo = Console.ReadKey(true);
            var key     = keyInfo.Key;
            var keyChar = keyInfo.KeyChar;

            if      (key == UpArrow  )  selectedLine = (selectedLine - 1).Modulo(nChoices);
            else if (key == DownArrow)  selectedLine = (selectedLine + 1).Modulo(nChoices);
            else if (key == Escape)
            {
                 ClearPopUp();
                 BackToInput();
                 return null;
            }
            else if (key == Backspace && !input.IsNullOrEmpty())
            {
                input = input.Substring(0, input.Length - 1);
                selectedLine = 0;
            }
            else if (key is Enter or Tab)
            {
                var selectedElement = filtered[selectedLine];

                ClearPopUp();
                BackToBeforeInput();

                return selectedElement;
            }
            else if (acceptedChars.Contains(Char.ToLower(keyChar)))
            {
                input += keyInfo.KeyChar;
                selectedLine = 0;
            }
        }

        void BackToInput()
        {
            Console.SetCursorPosition(prompt.Length + input.Length, Console.CursorTop - nPopUpLines - 1);
        }
        
        void BackToBeforeInput()
        {
            Console.SetCursorPosition(0, Console.CursorTop - nPopUpLines - 2);
        }

        void ClearPopUp()
        {
            foreach (var _ in Enumerable.Range(0, nPopUpLines + 1))
                "".PadRight(maxLen + prompt.Length).WriteLine();
        }

        void ShowPopUp(Int32 nChoices)
        {
            foreach (var i in Enumerable.Range(0, nPopUpLines))
            {
                var line = i < nChoices
                    ? filtered[i]
                    : "";

                var hlStart = line.IndexOf(input, InvariantCultureIgnoreCase);
                var hlEnd   = hlStart + input.Length;

                Console.BackgroundColor = i == selectedLine ? DarkGray : defaultBg;

                "".PadLeft(prompt.Length).Write(defaultBg, defaultBg);

                if (hlStart >= 0)
                    DrawLineWithHighlight(line, hlStart, hlEnd);
                else
                    DrawLineWithoutHighlight(line);  // nothing to highlight

                "".PadRight(maxLen - line.Length).WriteLine(defaultBg, defaultBg);
            }

            Console.BackgroundColor = defaultBg;

            void DrawLineWithHighlight(String line, Int32 hlStart, Int32 hlEnd)
            {
                line.Substring(0, hlStart).Write(Cyan);
                line.Substring(hlStart, input.Length).Write(Yellow);
                line.Substring(hlEnd).Write(Cyan);
            }

            void DrawLineWithoutHighlight(String line)
            {
                line.Write(Cyan);
            }
        }
    }

    private static void ShowInput(String inp, String prompt)
    {
        const String instructions = "type to narrow selection...";

        Console.CursorLeft = prompt.Length;

        if (inp.IsNullOrEmpty())
            instructions.WriteLine(Gray);
        else
            inp.PadRight(Math.Max(instructions.Length, inp.Length + 1)).WriteLine(Cyan);
    }


}