using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform.Storage;
using Inspectron.Settings.Attributes;

namespace Inspectron.Settings.Avalonia.Configuration;

public class DefaultControlFactory : IControlFactory
{
    public virtual Control CreateControl(string name, string description, PropertyInfo propertyInfo,
        object initialValue, Action<object> valueChangedCallback, OptionsWindow optionsWindow)
    {
        var type = propertyInfo.PropertyType;

        var settingDescriptionAttribute = propertyInfo.GetCustomAttribute<SettingDescriptionAttribute>();
        string? settingDescription = settingDescriptionAttribute?.Description;

        var previewAttribute = propertyInfo.GetCustomAttribute<SettingPreviewAttribute>();
        TextBlock? previewLabel = null;
        Func<object, string>? getPreview = null;

        if (previewAttribute != null)
        {
            var method = previewAttribute.PreviewClass.GetMethod(previewAttribute.PreviewFunction, BindingFlags.Public | BindingFlags.Static);
            if (method != null)
            {
                getPreview = value => (string)method.Invoke(null, new[] { value })!;
                previewLabel = new TextBlock
                {
                    Width = 600,
                    Height = 30,
                    Padding = new Thickness(15, 0, 0, 0),
                    FontStyle = FontStyle.Italic,
                    FontSize = 13,
                    Text = getPreview(initialValue)
                };
            }
        }

        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
        {
            return CreateListControl(name, description, type, initialValue, valueChangedCallback, optionsWindow, settingDescription, previewLabel, getPreview);
        }
        else if (type == typeof(string) && propertyInfo.GetCustomAttribute<FileAttribute>() != null)
        {
            return CreateFileControl(name, description, initialValue, valueChangedCallback, optionsWindow, settingDescription, previewLabel, getPreview, propertyInfo.GetCustomAttribute<FileAttribute>()!.Filter);
        }
        else if (type == typeof(string) && name.EndsWith("Path", StringComparison.OrdinalIgnoreCase))
        {
            return CreatePathControl(name, description, initialValue, valueChangedCallback, optionsWindow, settingDescription, previewLabel, getPreview);
        }
        else if (type == typeof(string))
        {
            return CreateStringControl(description, initialValue, valueChangedCallback, settingDescription, previewLabel, getPreview);
        }
        else if (type == typeof(int))
        {
            return CreateIntControl(description, initialValue, valueChangedCallback, settingDescription, previewLabel, getPreview);
        }
        else if (type == typeof(bool))
        {
            return CreateBoolControl(description, initialValue, valueChangedCallback, settingDescription, previewLabel, getPreview);
        }
        else if (type.IsEnum)
        {
            return CreateEnumControl(description, type, initialValue, valueChangedCallback, settingDescription, previewLabel, getPreview);
        }
        else
        {
            return new TextBlock { Text = $"Unsupported type: {type.Name}" };
        }
    }

    private Control CreateListControl(string name, string description, Type listType, object initialValue,
        Action<object> valueChangedCallback, OptionsWindow optionsWindow, string? settingDescription,
        TextBlock? previewLabel, Func<object, string>? getPreview)
    {
        var elementType = listType.GetGenericArguments()[0];
        var list = (IList)initialValue ?? (IList)Activator.CreateInstance(listType)!;

        var label = new TextBlock { Text = description, Padding = new Thickness(0, 5, 0, 0) };
        var textBox = new TextBox
        {
            Text = GetListDisplayText(list),
            Width = 500,
            Height = 60,
            IsReadOnly = true,
            AcceptsReturn = true,
            TextWrapping = TextWrapping.Wrap
        };
        var editButton = new Button { Content = "Edit" };

        editButton.Click += async (s, e) =>
        {
            var listEditor = new ListEditorDialog(elementType, list, this, optionsWindow, name);
            var result = await listEditor.ShowDialog<IList?>(optionsWindow);
            if (result != null)
            {
                list.Clear();
                foreach (object o in result)
                {
                    list.Add(o);
                }

                textBox.Text = GetListDisplayText(list);
                valueChangedCallback(list);
                if (previewLabel != null && getPreview != null)
                    previewLabel.Text = getPreview(list);
            }
        };

        return CreateOuterPanel(settingDescription, previewLabel, label, textBox, editButton);
    }

    private string GetListDisplayText(IList list)
    {
        if (list == null || list.Count == 0)
            return "(empty list)";

        var items = new List<string>();
        foreach (var item in list)
        {
            items.Add(item?.ToString() ?? "(null)");
        }
        return string.Join(Environment.NewLine, items);
    }

    private Control CreatePathControl(string name, string description, object initialValue, Action<object> valueChangedCallback,
        OptionsWindow optionsWindow, string? settingDescription, TextBlock? previewLabel, Func<object, string>? getPreview)
    {
        var textBox = new TextBox { Text = initialValue as string, Width = 400 };
        var label = new TextBlock { Text = description, Padding = new Thickness(0, 5, 0, 0) };
        var browseButton = new Button { Content = "Browse" };

        browseButton.Click += async (s, e) =>
        {
            var storageProvider = optionsWindow.StorageProvider;
            var result = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
            {
                AllowMultiple = false,
                Title = "Select Folder"
            });
            if (result.Count > 0)
            {
                var path = result[0].Path.LocalPath;
                textBox.Text = path;
                valueChangedCallback(path);
                if (previewLabel != null && getPreview != null)
                    previewLabel.Text = getPreview(path);
            }
        };

        textBox.TextChanged += (s, e) =>
        {
            valueChangedCallback(textBox.Text ?? "");
            if (previewLabel != null && getPreview != null)
                previewLabel.Text = getPreview(textBox.Text ?? "");
        };

        var row = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 };
        row.Children.Add(textBox);
        row.Children.Add(browseButton);

        return CreateOuterPanel(settingDescription, previewLabel, label, row);
    }

    private Control CreateFileControl(string name, string description, object initialValue, Action<object> valueChangedCallback,
        OptionsWindow optionsWindow, string? settingDescription, TextBlock? previewLabel, Func<object, string>? getPreview, string filter)
    {
        var textBox = new TextBox { Text = initialValue as string, Width = 400 };
        var label = new TextBlock { Text = description, Padding = new Thickness(0, 5, 0, 0) };
        var browseButton = new Button { Content = "Browse" };

        browseButton.Click += async (s, e) =>
        {
            var storageProvider = optionsWindow.StorageProvider;
            var result = await storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
            {
                AllowMultiple = false,
                Title = "Select File",
                FileTypeFilter = new[] { new FilePickerFileType(filter) { Patterns = new[] { filter } } }
            });
            if (result.Count > 0)
            {
                var path = result[0].Path.LocalPath;
                textBox.Text = path;
                valueChangedCallback(path);
                if (previewLabel != null && getPreview != null)
                    previewLabel.Text = getPreview(path);
            }
        };

        textBox.TextChanged += (s, e) =>
        {
            valueChangedCallback(textBox.Text ?? "");
            if (previewLabel != null && getPreview != null)
                previewLabel.Text = getPreview(textBox.Text ?? "");
        };

        var row = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 };
        row.Children.Add(textBox);
        row.Children.Add(browseButton);

        return CreateOuterPanel(settingDescription, previewLabel, label, row);
    }

    private Control CreateStringControl(string description, object initialValue, Action<object> valueChangedCallback,
        string? settingDescription, TextBlock? previewLabel, Func<object, string>? getPreview)
    {
        var textBox = new TextBox { Text = initialValue as string, Width = 600 };
        var label = new TextBlock { Text = description, Padding = new Thickness(0, 5, 0, 0) };

        textBox.TextChanged += (s, e) =>
        {
            valueChangedCallback(textBox.Text ?? "");
            if (previewLabel != null && getPreview != null)
                previewLabel.Text = getPreview(textBox.Text ?? "");
        };

        return CreateOuterPanel(settingDescription, previewLabel, label, textBox);
    }

    private Control CreateIntControl(string description, object initialValue, Action<object> valueChangedCallback,
        string? settingDescription, TextBlock? previewLabel, Func<object, string>? getPreview)
    {
        var label = new TextBlock { Text = description, Padding = new Thickness(0, 5, 0, 0) };
        var numericUpDown = new NumericUpDown
        {
            Minimum = 0,
            Maximum = 99999999999,
            Value = Convert.ToDecimal(initialValue),
            Width = 200,
            FormatString = "0"
        };

        numericUpDown.ValueChanged += (s, e) =>
        {
            valueChangedCallback(Convert.ToInt32(numericUpDown.Value));
            if (previewLabel != null && getPreview != null)
                previewLabel.Text = getPreview(Convert.ToInt32(numericUpDown.Value));
        };

        return CreateOuterPanel(settingDescription, previewLabel, label, numericUpDown);
    }

    private Control CreateBoolControl(string description, object initialValue, Action<object> valueChangedCallback,
        string? settingDescription, TextBlock? previewLabel, Func<object, string>? getPreview)
    {
        var checkBox = new CheckBox { Content = description, IsChecked = (bool)initialValue };

        checkBox.IsCheckedChanged += (s, e) =>
        {
            var isChecked = checkBox.IsChecked == true;
            valueChangedCallback(isChecked);
            if (previewLabel != null && getPreview != null)
                previewLabel.Text = getPreview(isChecked);
        };

        return CreateOuterPanel(settingDescription, previewLabel, checkBox);
    }

    private Control CreateEnumControl(string description, Type enumType, object initialValue, Action<object> valueChangedCallback,
        string? settingDescription, TextBlock? previewLabel, Func<object, string>? getPreview)
    {
        var label = new TextBlock { Text = description, Padding = new Thickness(0, 5, 0, 0) };
        var comboBox = new ComboBox { Width = 200 };

        var enumValues = Enum.GetValues(enumType);
        foreach (var value in enumValues)
        {
            comboBox.Items.Add(value);
        }

        comboBox.SelectedItem = initialValue;

        comboBox.SelectionChanged += (s, e) =>
        {
            if (comboBox.SelectedItem != null)
            {
                valueChangedCallback(comboBox.SelectedItem);
                if (previewLabel != null && getPreview != null)
                    previewLabel.Text = getPreview(comboBox.SelectedItem);
            }
        };

        return CreateOuterPanel(settingDescription, previewLabel, label, comboBox);
    }

    private Control CreateOuterPanel(string? settingDescription, TextBlock? previewLabel, params Control[] controls)
    {
        var outerPanel = new StackPanel { Orientation = Orientation.Vertical, Spacing = 4, Margin = new Thickness(0, 25, 0, 0), HorizontalAlignment = HorizontalAlignment.Left };

        if (!string.IsNullOrEmpty(settingDescription))
        {
            var descriptionLabel = new TextBlock
            {
                Text = settingDescription,
                FontStyle = FontStyle.Italic,
                Padding = new Thickness(0, 5, 0, 0)
            };
            outerPanel.Children.Add(descriptionLabel);
        }

        foreach (var control in controls)
        {
            outerPanel.Children.Add(control);
        }

        if (previewLabel != null)
            outerPanel.Children.Add(previewLabel);

        return outerPanel;
    }
}
