#nullable enable

using System;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

using WpfUserControl = System.Windows.Controls.UserControl;

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

using Microsoft.CodeAnalysis; // SourceCodeKind
using RoslynPad.Editor;       // RoslynCodeEditor, ClassificationHighlightColors
using RoslynPad.Roslyn;       // RoslynHost, RoslynHostReferences

namespace MyCodeEditor.HDEUtils
{
    /// <summary>
    /// WPF view that hosts RoslynPad's RoslynCodeEditor.
    /// Initializes editor asynchronously (RoslynPad 4.4+ uses InitializeAsync).
    /// </summary>
    public sealed class RoslynPadEditorView : WpfUserControl, IDisposable
    {
        private readonly RoslynCodeEditor _editor;

        private RoslynHost? _host;
        private bool _initialized;

        private string _workingDirectory = Directory.GetCurrentDirectory();
        private string _documentFilePath = string.Empty;

        public RoslynPadEditorView()
        {
            _editor = new RoslynCodeEditor
            {
                ShowLineNumbers = true
            };

            Content = _editor;

            // Initialize when the control is loaded (WPF lifetime safe).
            Loaded += (_, __) => _ = EnsureInitializedAsync();
        }

        public void SetWorkingDirectory(string? workingDirectory)
        {
            _workingDirectory = string.IsNullOrWhiteSpace(workingDirectory)
                ? Directory.GetCurrentDirectory()
                : workingDirectory!;
        }

        public void SetVirtualDocumentPath(string? documentFilePath)
        {
            _documentFilePath = documentFilePath ?? string.Empty;
        }

        public void SetText(string? text)
        {
            // AvalonEdit document is not thread-safe; ensure on UI thread if needed.
            if (!Dispatcher.CheckAccess())
            {
                Dispatcher.Invoke(() => _editor.Text = text ?? string.Empty);
                return;
            }

            _editor.Text = text ?? string.Empty;
        }

        public string GetText()
        {
            if (!Dispatcher.CheckAccess())
                return Dispatcher.Invoke(() => _editor.Text ?? string.Empty);

            return _editor.Text ?? string.Empty;
        }

        public void ApplyHostColors(System.Drawing.Color backColor, System.Drawing.Color foreColor)
        {
            // Basic theme bridge: WinForms host colors -> WPF brushes.
            var bg = System.Windows.Media.Color.FromArgb(backColor.A, backColor.R, backColor.G, backColor.B);
            var fg = System.Windows.Media.Color.FromArgb(foreColor.A, foreColor.R, foreColor.G, foreColor.B);

            var bgBrush = new SolidColorBrush(bg);
            var fgBrush = new SolidColorBrush(fg);

            if (!Dispatcher.CheckAccess())
            {
                Dispatcher.Invoke(() =>
                {
                    Background = bgBrush;
                    _editor.Background = bgBrush;
                    _editor.Foreground = fgBrush;
                });
                return;
            }

            Background = bgBrush;
            _editor.Background = bgBrush;
            _editor.Foreground = fgBrush;
        }

        private async Task EnsureInitializedAsync()
        {
            if (_initialized)
                return;

            _initialized = true;

            // Due to Roslyn version incompatibility (Gamma uses 4.11.0, RoslynPad 4.8.0 requires internal
            // types that were removed), we use the editor in "basic mode" - without full Roslyn services.
            // This provides: syntax highlighting, line numbers, basic editing
            // But not: IntelliSense, diagnostics, code completion
            
            // Try to initialize with full Roslyn services
            try
            {
                _host = CreateHostWithCommonReferences();

                // RoslynPad 4.4+ uses InitializeAsync.
                await _editor.InitializeAsync(
                    _host,
                    new ClassificationHighlightColors(),
                    _workingDirectory,
                    _documentFilePath,
                    SourceCodeKind.Regular
                );
                
                // Success! Full IntelliSense available
                return;
            }
            catch (Exception)
            {
                // Roslyn initialization failed - fall back to basic AvalonEdit mode
                // The editor will still work, just without IntelliSense
                _host = null;
            }
            
            // Basic mode: just set up syntax highlighting for C#
            try
            {
                SetupBasicCSharpHighlighting();
            }
            catch
            {
                // Even basic highlighting failed - editor will work as plain text
            }
        }
        
        /// <summary>
        /// Sets up basic C# syntax highlighting using AvalonEdit's built-in support.
        /// This is a fallback when RoslynHost fails to initialize.
        /// </summary>
        private void SetupBasicCSharpHighlighting()
        {
            // Try to load AvalonEdit's built-in C# highlighting
            var highlightingManager = ICSharpCode.AvalonEdit.Highlighting.HighlightingManager.Instance;
            var csharpHighlighting = highlightingManager.GetDefinition("C#");
            
            if (csharpHighlighting != null)
            {
                _editor.SyntaxHighlighting = csharpHighlighting;
            }
        }

        private static string BuildDiagnosticMessage(Exception ex)
        {
            var sb = new System.Text.StringBuilder();
            sb.AppendLine("// ========================================");
            sb.AppendLine("// RoslynPad Initialization Failed");
            sb.AppendLine("// ========================================");
            sb.AppendLine();
            
            // Main exception
            sb.AppendLine($"// Exception Type: {ex.GetType().FullName}");
            sb.AppendLine($"// Message: {ex.Message}");
            sb.AppendLine();
            
            // Full stack trace
            sb.AppendLine("// Stack Trace:");
            sb.AppendLine("/*");
            sb.AppendLine(ex.ToString());
            sb.AppendLine("*/");
            sb.AppendLine();
            
            // Inner exception
            if (ex.InnerException != null)
            {
                sb.AppendLine("// Inner Exception:");
                sb.AppendLine($"//   Type: {ex.InnerException.GetType().FullName}");
                sb.AppendLine($"//   Message: {ex.InnerException.Message}");
                sb.AppendLine();
                sb.AppendLine("// Inner Exception Stack Trace:");
                sb.AppendLine("/*");
                sb.AppendLine(ex.InnerException.ToString());
                sb.AppendLine("*/");
                sb.AppendLine();
            }
            
            // ReflectionTypeLoadException specific
            if (ex is ReflectionTypeLoadException rtle)
            {
                sb.AppendLine("// ReflectionTypeLoadException LoaderExceptions:");
                if (rtle.LoaderExceptions != null)
                {
                    for (int i = 0; i < rtle.LoaderExceptions.Length; i++)
                    {
                        var le = rtle.LoaderExceptions[i];
                        if (le != null)
                        {
                            sb.AppendLine($"//   [{i}] {le.GetType().Name}: {le.Message}");
                            if (le.InnerException != null)
                            {
                                sb.AppendLine($"//       Inner: {le.InnerException.GetType().Name}: {le.InnerException.Message}");
                            }
                        }
                    }
                }
                sb.AppendLine();
            }
            
            // Loaded assemblies diagnostics
            sb.AppendLine("// ========================================");
            sb.AppendLine("// Loaded Assemblies (Microsoft.CodeAnalysis.*, RoslynPad.*, System.Composition.*)");
            sb.AppendLine("// ========================================");
            sb.AppendLine();
            
            var relevantAssemblies = AppDomain.CurrentDomain.GetAssemblies()
                .Where(a => 
                {
                    var name = a.GetName().Name ?? "";
                    return name.StartsWith("Microsoft.CodeAnalysis", StringComparison.OrdinalIgnoreCase) ||
                           name.StartsWith("RoslynPad", StringComparison.OrdinalIgnoreCase) ||
                           name.StartsWith("System.Composition", StringComparison.OrdinalIgnoreCase);
                })
                .OrderBy(a => a.GetName().Name)
                .ToList();
            
            if (relevantAssemblies.Count == 0)
            {
                sb.AppendLine("// No relevant assemblies found in AppDomain.");
            }
            else
            {
                foreach (var asm in relevantAssemblies)
                {
                    var asmName = asm.GetName();
                    sb.AppendLine($"// {asmName.Name}");
                    sb.AppendLine($"//   Version: {asmName.Version}");
                    sb.AppendLine($"//   Location: {asm.Location}");
                    sb.AppendLine($"//   FullName: {asmName.FullName}");
                    sb.AppendLine();
                }
            }
            
            // Check for missing dependencies in output directory
            sb.AppendLine("// ========================================");
            sb.AppendLine("// Output Directory Check");
            sb.AppendLine("// ========================================");
            sb.AppendLine();
            
            var outputDir = Path.GetDirectoryName(typeof(RoslynPadEditorView).Assembly.Location);
            if (!string.IsNullOrEmpty(outputDir))
            {
                sb.AppendLine($"// Output Directory: {outputDir}");
                sb.AppendLine();
                
                var expectedDlls = new[]
                {
                    "Microsoft.CodeAnalysis.dll",
                    "Microsoft.CodeAnalysis.CSharp.dll",
                    "RoslynPad.Roslyn.dll",
                    "RoslynPad.Editor.dll",
                    "System.Composition.dll"
                };
                
                sb.AppendLine("// Expected DLLs:");
                foreach (var dll in expectedDlls)
                {
                    var path = Path.Combine(outputDir, dll);
                    var exists = File.Exists(path);
                    sb.AppendLine($"//   {dll}: {(exists ? "✓ Found" : "✗ Missing")}");
                    if (exists)
                    {
                        try
                        {
                            var fileInfo = new FileInfo(path);
                            var version = System.Diagnostics.FileVersionInfo.GetVersionInfo(path);
                            sb.AppendLine($"//      Size: {fileInfo.Length} bytes");
                            sb.AppendLine($"//      Version: {version.FileVersion ?? "N/A"}");
                        }
                        catch { }
                    }
                }
            }
            
            return sb.ToString();
        }

        private static RoslynHost CreateHostWithCommonReferences()
        {
            // Additional assemblies typically required by RoslynPad host.
            // Using typeof(...).Assembly is more robust than Assembly.Load(...) in embedded scenarios.
            var additionalAssemblies = new[]
            {
                typeof(RoslynHost).Assembly,
                typeof(RoslynCodeEditor).Assembly,
                TryLoad("RoslynPad.Roslyn.Windows"),
                TryLoad("RoslynPad.Editor.Windows")
            }.Where(a => a is not null).Cast<Assembly>().Distinct().ToArray();

            // Minimal references for basic C# IntelliSense.
            // You will likely extend this later with Gamma/VL/Stride assemblies you want to expose to the user.
            var refs = RoslynHostReferences.NamespaceDefault.With(
                assemblyReferences: new[]
                {
                    typeof(object).Assembly,
                    typeof(Uri).Assembly,
                    typeof(Enumerable).Assembly,
                    typeof(Task).Assembly,
                    typeof(Microsoft.CodeAnalysis.CSharp.CSharpCompilation).Assembly,
                },
                typeNamespaceImports: new[]
                {
                    typeof(object),
                    typeof(Uri),
                    typeof(Enumerable),
                    typeof(Task),
                });

            // RoslynPad 4.8.0 uses 2-parameter constructor
            return new RoslynHost(
                additionalAssemblies: additionalAssemblies, 
                references: refs);
        }

        private static Assembly? TryLoad(string name)
        {
            try { return Assembly.Load(name); }
            catch { return null; }
        }

        public void Dispose()
        {
            if (_host is IDisposable d)
                d.Dispose();
        }
    }
}
