using InABox.Core;
using NPOI.DDF;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace InABox.DynamicGrid;

public class ExpressionEditorVariable(string variableName, string display, List<ExpressionEditorVariable> children)
{
    public string VariableName { get; } = variableName;

    public string Display { get; } = display;

    public List<ExpressionEditorVariable> ChildVariables { get; } = children;

    public bool HasChildren => ChildVariables.Count > 0;
}

public class ExpressionEditorVariableTemplateItemStyleSelector : StyleSelector
{
    public override Style? SelectStyle(object item, DependencyObject container)
    {
        if(item is ExpressionEditorVariable variable && container is FrameworkElement element && variable.HasChildren)
        {
            return element.FindResource("variableTemplateItemStyle") as Style;
        }
        return base.SelectStyle(item, container);
    }
}
public class ExpressionEditorVariableTemplateSelector : DataTemplateSelector
{
    public override DataTemplate? SelectTemplate(object item, DependencyObject container)
    {
        if(item is ExpressionEditorVariable variable && container is FrameworkElement element)
        {
            if (variable.HasChildren)
            {
                return element.FindResource("VariableExpanderTemplate") as DataTemplate;
            }
            else
            {
                return element.FindResource("VariableTemplate") as DataTemplate;
            }
        }
        return base.SelectTemplate(item, container);
    }
}

/// <summary>
/// Interaction logic for ExpressionEditorWindow.xaml
/// </summary>
public partial class ExpressionEditorWindow : Window
{
    public static List<Tuple<string, List<FunctionTemplate>>> FunctionTemplates = new();

    public IEnumerable<ExpressionEditorVariable> Variables { get; private set; }

    public string Expression
    {
        get => Editor.Text;
        set => Editor.Text = value;
    }

    private static void RegisterFunctionTemplate(string group, string name, string? description, List<IFunctionParameter> parameters)
    {
        var groupList = FunctionTemplates.FirstOrDefault(x => x.Item1 == group)?.Item2;
        if(groupList is null)
        {
            groupList = new List<FunctionTemplate>();
            FunctionTemplates.Add(new(group, groupList));
        }

        groupList.Add(new FunctionTemplate
        {
            Name = name,
            Parameters = parameters,
            Description = description ?? ""
        });
    }
    private static void RegisterFunctionTemplate(string group, string name, params string[] parameters)
        => RegisterFunctionTemplate(group, name, null, parameters.Select(x =>
        {
            var parts = x.Split(' ');
            if (parts.Length == 1)
            {
                return new FunctionParameter(parts[0], "");
            }
            return new FunctionParameter(parts[1], parts[0]);
        }).ToList<IFunctionParameter>());
    
    private static void RegisterFunctionTemplateDesc(string group, string name, string description, params string[] parameters)
        => RegisterFunctionTemplate(group, name, description, parameters.Select(x =>
        {
            var parts = x.Split(' ');
            if (parts.Length == 1)
            {
                return new FunctionParameter(parts[0], "");
            }
            return new FunctionParameter(parts[1], parts[0]);
        }).ToList<IFunctionParameter>());

    static ExpressionEditorWindow()
    {
        RegisterFunctionTemplate("Math", "Abs", "double x");
        RegisterFunctionTemplate("Math", "Acos", "double x");
        RegisterFunctionTemplate("Math", "Asin", "double x");
        RegisterFunctionTemplate("Math", "Atan", "double x");
        RegisterFunctionTemplate("Math", "Ceiling", "double x");
        RegisterFunctionTemplate("Math", "Cos", "double x");
        RegisterFunctionTemplate("Math", "Count", "...");
        RegisterFunctionTemplate("Math", "Exp", "double exp");
        RegisterFunctionTemplate("Math", "Floor", "double x");
        RegisterFunctionTemplate("Math", "IEEERemainder", "double x", "double y");
        RegisterFunctionTemplate("Math", "Log10", "double x");
        RegisterFunctionTemplate("Math", "Log", "double x", "double base");
        RegisterFunctionTemplate("Math", "Max", "double x", "double y");
        RegisterFunctionTemplate("Math", "Min", "double x", "double y");
        RegisterFunctionTemplateDesc("Math", "Pow", "Returns {base} ^ {exp}.", "double base", "double exp");
        RegisterFunctionTemplate("Math", "Random");
        RegisterFunctionTemplateDesc("Math", "Round", "Rounds a number to a particular number of decimal places, given by {precision}.", "double x", "double precision");
        RegisterFunctionTemplate("Math", "Sign", "double x");
        RegisterFunctionTemplate("Math", "Sin", "double x");
        RegisterFunctionTemplate("Math", "Sqrt", "double x");
        RegisterFunctionTemplate("Math", "Tan", "double x");
        RegisterFunctionTemplate("Math", "Truncate", "double x");

        RegisterFunctionTemplateDesc("Logical", "If", "If {condition} is true or non-zero, returns {exp1}; otherwise, returns {exp2}.", "bool condition", "exp1", "exp2");
        RegisterFunctionTemplateDesc("Logical", "In", "Returns true if {value} is in the list of values.", "value", "...");

        RegisterFunctionTemplateDesc("Statistical", "Average", "Takes the average of a list of numbers.", "double ...");
        RegisterFunctionTemplateDesc("Statistical", "Mean", "Calculates the mean for a list of numbers.", "double ...");
        RegisterFunctionTemplateDesc("Statistical", "Median", "Calculates the median for a list of numbers.", "double ...");
        RegisterFunctionTemplateDesc("Statistical", "Mode", "Calculates the mode for a list of numbers.", "double ...");
        RegisterFunctionTemplateDesc("Statistical", "Sum", "Calculates the sum of a list of numbers.", "double ...");

        RegisterFunctionTemplate("String", "Contains", "string text", "str value");
        RegisterFunctionTemplate("String", "EndsWith", "string text", "str value");
        RegisterFunctionTemplate("String", "Length", "string value");
        RegisterFunctionTemplate("String", "PadLeft", "string value", "int length", "char character");
        RegisterFunctionTemplate("String", "PadRight", "string value", "int length", "char character");
        RegisterFunctionTemplate("String", "StartsWith", "string text", "string value");
        RegisterFunctionTemplate("String", "Substring", "string text", "int startIndex", "int length");

        RegisterFunctionTemplate("Date", "AddYears", "date date", "int years");
        RegisterFunctionTemplate("Date", "AddMonths", "date date", "int months");
        RegisterFunctionTemplate("Date", "AddDays", "date date", "int days");
        RegisterFunctionTemplate("Date", "AddHours", "date date", "int hrs");
        RegisterFunctionTemplate("Date", "AddMinutes", "date date", "int mins");
        RegisterFunctionTemplate("Date", "AddSeconds", "date date", "int secs");
        RegisterFunctionTemplate("Date", "AddMilliseconds", "date date", "int ms");
        RegisterFunctionTemplate("Date", "YearOf", "date date");
        RegisterFunctionTemplate("Date", "MonthOf", "date date");
        RegisterFunctionTemplate("Date", "DayOf", "date date");
        RegisterFunctionTemplate("Date", "HourOf", "date date");
        RegisterFunctionTemplate("Date", "MinuteOf", "date date");
        RegisterFunctionTemplate("Date", "SecondOf", "date date");
        RegisterFunctionTemplate("Date", "MillisecondOf", "date date");
        RegisterFunctionTemplate("Date", "DaysBetween", "date date", "date date");
        RegisterFunctionTemplate("Date", "HoursBetween", "date date", "date date");
        RegisterFunctionTemplate("Date", "MinutesBetween", "date date", "date date");
        RegisterFunctionTemplate("Date", "SecondsBetween", "date date", "date date");
        RegisterFunctionTemplate("Date", "MillisecondsBetween", "date date", "date date");

        RegisterFunctionTemplate("Conversion", "Date", "value");
        RegisterFunctionTemplate("Conversion", "Date", "value", "string format");
        RegisterFunctionTemplate("Conversion", "Decimal", "value");
        RegisterFunctionTemplate("Conversion", "Double", "value");
        RegisterFunctionTemplate("Conversion", "Integer", "value");
        RegisterFunctionTemplate("Conversion", "Long", "value");
        RegisterFunctionTemplate("Conversion", "String", "value");

        foreach(var function in CoreExpression.Functions)
        {
            RegisterFunctionTemplateDesc(
                function.Group,
                function.Name,
                function.Description,
                function.Parameters);
        }
    }

    public ExpressionEditorWindow(IEnumerable<string> variables)
    {
        var parentColumns = new Dictionary<string, ExpressionEditorVariable>();
        var items = new List<ExpressionEditorVariable>();
        foreach (var variable in variables)
        {
            var display = variable;
            var lastDot = variable.LastIndexOf('.');
            if(lastDot != -1)
            {
                display = variable[(lastDot + 1)..];
            }
            var item = new ExpressionEditorVariable(variable, display, []);

            var props = variable.Split('.');

            var vars = items;
            string? parent = null;
            for (int i = 0; i < props.Length - 1; ++i)
            {
                if (parent is null)
                {
                    parent = props[i];
                }
                else
                {
                    parent = $"{parent}.{props[i]}";
                }
                if(!parentColumns.TryGetValue(parent, out var parentVar))
                {
                    parentVar = new ExpressionEditorVariable(parent, props[i], []);
                    parentColumns.Add(parent, parentVar);
                    vars.Add(parentVar);
                }
                vars = parentVar.ChildVariables;
            }
            vars.Add(item);
        }
        SortVariables(items);

        Variables = items;

        InitializeComponent();
    }

    private void SortVariables(List<ExpressionEditorVariable> vars)
    {
        vars.Sort((a, b) =>
        {
            if (a.HasChildren && !b.HasChildren)
            {
                return -1;
            }
            else if (!a.HasChildren && b.HasChildren)
            {
                return 1;
            }
            return a.Display.CompareTo(b.Display);
        });
        foreach(var v in vars)
        {
            SortVariables(v.ChildVariables);
        }
    }

    private void ExpressionWindow_Loaded(object sender, RoutedEventArgs e)
    {
        Editor.Focus();
    }

    private void InsertText(string text)
    {
        var newCaret = Editor.CaretIndex + text.Length;
        Editor.Text = Editor.Text.Insert(Editor.CaretIndex, text);
        Editor.CaretIndex = newCaret;
    }
    private void AppendText(string text)
    {
        var oldCaret = Editor.CaretIndex;
        Editor.Text = Editor.Text.Insert(Editor.CaretIndex, text);
        Editor.CaretIndex = oldCaret;
    }
    private void DeleteText(int start, int length)
    {
        var oldCaret = Editor.CaretIndex;
        Editor.Text = Editor.Text.Remove(start, length);
        if(oldCaret >= start && oldCaret < start + length)
        {
            Editor.CaretIndex = start;
        }
        else
        {
            Editor.CaretIndex = oldCaret - length;
        }
    }

    private static Regex tagRegex = new("\\{\\S+\\}");
    private static Regex backTagRegex = new("\\{\\S+\\}", RegexOptions.RightToLeft);

    private bool DeletePlaceholder()
    {
        if (Editor.CaretIndex >= Editor.Text.Length) return false;

        var tagStart = Editor.Text.LastIndexOf('{', Editor.CaretIndex);
        if (tagStart == -1) return false;

        var tagMatch = tagRegex.Match(Editor.Text, tagStart);
        if (!tagMatch.Success) return false;

        var startIndex = tagMatch.Index;

        if (startIndex != tagStart) return false;

        var length = tagMatch.Length;

        if (Editor.CaretIndex >= startIndex + length) return false;

        DeleteText(startIndex, length);

        return true;
    }
    private void JumpPlaceholderBackwards()
    {
        if (Editor.CaretIndex == 0) return;

        var tagMatch = backTagRegex.Match(Editor.Text, Editor.CaretIndex - 1);
        if (!tagMatch.Success) return;

        Editor.CaretIndex = tagMatch.Index;
    }
    private void JumpPlaceholder(bool allowStart = false)
    {
        if (Editor.CaretIndex >= Editor.Text.Length) return;

        var start = allowStart ? Editor.CaretIndex : Editor.CaretIndex + 1;

        var tagMatch = tagRegex.Match(Editor.Text, start);
        if (!tagMatch.Success) return;

        Editor.CaretIndex = tagMatch.Index;
    }

    private void FunctionTemplate_Click(object sender, MouseButtonEventArgs e)
    {
        if ((sender as FrameworkElement)!.Tag is not FunctionTemplate template) return;

        var placeholder = DeletePlaceholder();
        InsertText($"{template.Name}(");
        AppendText(string.Join(", ", template.Parameters.Select(x => $"{{{x.Name}}}")) + ")");
        if (placeholder)
            JumpPlaceholder(true);
    }

    private void Variable_Click(object sender, MouseButtonEventArgs e)
    {
        if ((sender as FrameworkElement)!.Tag is not string str) return;

        DeletePlaceholder();
        InsertText($"[{str}]");
    }

    private void Editor_PreviewLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
    {
        e.Handled = true;
    }

    private void Editor_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        DeletePlaceholder();
    }

    private void Editor_KeyDown(object sender, KeyEventArgs e)
    {
        if(e.Key == Key.Tab)
        {
            if((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
            {
                JumpPlaceholderBackwards();
            }
            else
            {
                JumpPlaceholder();
            }
            e.Handled = true;
        }
    }

    private void OK_Click(object sender, RoutedEventArgs e)
    {
        DialogResult = true;
    }

}
