using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows;
using MarcusLaw.OutlookAddin.Core.Configuration;
using MarcusLaw.OutlookAddin.Core.Logging;
using MarcusLaw.OutlookAddin.Core.Models;
using MarcusLaw.OutlookAddin.Core.Security;
using MarcusLaw.OutlookAddin.Core.Services;
using System.Windows.Interop;
using MarcusLaw.OutlookAddin.UI.Dialogs;
using MarcusLaw.OutlookAddin.UI.ViewModels;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Office.Tools;
using Serilog;
using Outlook = Microsoft.Office.Interop.Outlook;

namespace OutlookAddin.Services
{
    /// <summary>
    /// Composition root for the add-in. Owns the long-lived services
    /// (logger, settings, CRM client, filing service, retry processor)
    /// and exposes high-level commands the ribbon callbacks call into.
    /// </summary>
    public sealed class AddInHost : IDisposable
    {
        private const int RecentEntitiesLimit = 10;

        public ILogger Logger { get; }
        public AppSettings Settings { get; }
        public SettingsManager SettingsManager { get; }
        public DpapiCredentialStore CredentialStore { get; }
        public IRetryQueue RetryQueue { get; }

        public IEspoCrmClient? CrmClient { get; private set; }
        public IFilingService? FilingService { get; private set; }
        public IMatchingService? MatchingService { get; private set; }
        public IAttachmentSaveService? AttachmentSaveService { get; private set; }
        public ComposeService? ComposeService { get; private set; }

        private readonly Outlook.Application _outlookApp;
        private readonly MemoryCache _matchCache = new MemoryCache(new MemoryCacheOptions());
        private RetryQueueProcessor? _retryProcessor;
        private HttpClient? _httpClient;
        private SidebarController? _sidebarController;
        private SentItemsWatcher? _sentItemsWatcher;
        private NamedPipeListener? _pipeListener;
        private FolderResolver? _folderResolver;
        private FolderWatcher? _folderWatcher;

        public AddInHost(Outlook.Application outlookApp)
        {
            _outlookApp = outlookApp ?? throw new ArgumentNullException(nameof(outlookApp));

            // .NET Framework 4.8 defaults to SystemDefault for TLS negotiation,
            // which in Outlook's host process can end up as SSL3/TLS1.0 and trip
            // "Could not create SSL/TLS secure channel" on modern EspoCRM servers.
            // Force TLS 1.2 (always present on Win10+) and try TLS 1.3 (Win10
            // 1809+ only — wrap in try/catch for older Windows).
            EnableModernTls();

            Logger = LoggerFactory.Init();
            Logger.Information("AddInHost starting up");

            SettingsManager = new SettingsManager();
            Settings = SettingsManager.Load();

            // Apply the chosen UI locale before any view-model touches resource strings.
            MarcusLaw.OutlookAddin.UI.Services.LocalizationManager.SetCulture(Settings.Locale);

            CredentialStore = new DpapiCredentialStore();
            RetryQueue = new DiskRetryQueue(Logger);

            TryInitializeCrm();
            InitializeSentItemsWatcher();
            InitializeNamedPipeListener();
            HookItemSend();
        }

        [DllImport("user32.dll")]
        private static extern IntPtr GetForegroundWindow();

        [DllImport("user32.dll")]
        private static extern bool SetForegroundWindow(IntPtr hWnd);

        /// <summary>
        /// Wires a WPF <see cref="Window"/> to be a modal child of whichever
        /// Outlook window is currently in the foreground (Explorer or
        /// Inspector). Without this, ShowDialog() launches the dialog with
        /// no owner — it sinks behind other windows and the user sees
        /// Outlook as "frozen" until they alt-tab back to find it.
        /// </summary>
        private void AttachOwnerToOutlook(Window dialog)
        {
            try
            {
                var hwnd = GetForegroundWindow();
                if (hwnd == IntPtr.Zero)
                {
                    hwnd = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle;
                }
                if (hwnd == IntPtr.Zero) return;

                new WindowInteropHelper(dialog).Owner = hwnd;
                dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;
                dialog.ShowInTaskbar = false;
                dialog.Topmost = false;

                // First chance to get focus once shown.
                dialog.Loaded += (_, __) =>
                {
                    try
                    {
                        var helper = new WindowInteropHelper(dialog);
                        if (helper.Handle != IntPtr.Zero) SetForegroundWindow(helper.Handle);
                        dialog.Activate();
                    }
                    catch { /* ignore */ }
                };
            }
            catch (Exception ex)
            {
                Logger.Warning(ex, "AttachOwnerToOutlook failed");
            }
        }

        private static void EnableModernTls()
        {
            try
            {
                System.Net.ServicePointManager.SecurityProtocol |= System.Net.SecurityProtocolType.Tls12;
            }
            catch (NotSupportedException) { /* extremely old Windows — ignore */ }

            try
            {
                // Tls13 enum is present in .NET Framework 4.8, but the OS schannel
                // only honours it on Windows 10 1809+ / Server 2022+.
                System.Net.ServicePointManager.SecurityProtocol |= (System.Net.SecurityProtocolType)12288;
            }
            catch (NotSupportedException) { /* older Windows — TLS 1.2 only is fine */ }
        }

        private void InitializeSentItemsWatcher()
        {
            try
            {
                _sentItemsWatcher = new SentItemsWatcher(_outlookApp, Logger);
                _sentItemsWatcher.MailMatched += OnSentMailMatched;
                _sentItemsWatcher.Initialize();
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "AddInHost: SentItemsWatcher init failed");
            }
        }

        private void InitializeNamedPipeListener()
        {
            try
            {
                _pipeListener = new NamedPipeListener(System.Windows.Threading.Dispatcher.CurrentDispatcher, Logger);
                _pipeListener.MessageReceived += OnPipeMessage;
                _pipeListener.Start();
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "AddInHost: NamedPipeListener init failed");
            }
        }

        private void HookItemSend()
        {
            try
            {
                _outlookApp.ItemSend += OnItemSend;
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "AddInHost: ItemSend hook failed");
            }
        }

        private void OnItemSend(object item, ref bool cancel)
        {
            try
            {
                if (item is not Outlook.MailItem mail) return;
                var guid = ComposeService.ReadAddInGuid(mail);
                var caseId = ComposeService.ReadEspoCaseId(mail);
                if (string.IsNullOrWhiteSpace(guid) || string.IsNullOrWhiteSpace(caseId)) return;
                var name = ComposeService.ReadEspoCaseName(mail) ?? caseId!;
                var target = new FilingTarget("Case", caseId!, name);
                _sentItemsWatcher?.Track(guid!, target);
                Logger.Information("Outgoing mail tagged for auto-file (guid={Guid}, caseId={CaseId})", guid, caseId);
            }
            catch (Exception ex)
            {
                Logger.Warning(ex, "AddInHost.OnItemSend handler failed");
            }
        }

        private async void OnSentMailMatched(object? sender, SentMailMatchedEventArgs e)
        {
            try
            {
                if (FilingService == null)
                {
                    Logger.Warning("Auto-file aborted — FilingService not configured");
                    return;
                }
                var envelope = MailItemExtractor.Extract(e.Mail);
                var summary = await FilingService.FileAsync(new[] { envelope }, e.Target).ConfigureAwait(true);
                if (summary.FiledCount == 1)
                {
                    StampSingleCategory(e.Mail, "Filed: " + e.Target.ParentName);
                    Logger.Information("Auto-filed sent mail to {Type}/{Id}", e.Target.ParentType, e.Target.ParentId);
                }
                else if (summary.QueuedCount == 1)
                {
                    Logger.Information("Auto-file queued for retry (target={Name})", e.Target.ParentName);
                }
                else
                {
                    Logger.Warning("Auto-file failed for sent mail (target={Name})", e.Target.ParentName);
                }
            }
            catch (Exception ex)
            {
                Logger.Warning(ex, "OnSentMailMatched handler failed");
            }
        }

        private void StampSingleCategory(Outlook.MailItem mail, string categoryName)
        {
            try
            {
                EnsureCategoryExists(categoryName);
                var existing = mail.Categories ?? string.Empty;
                if (existing.IndexOf(categoryName, StringComparison.Ordinal) >= 0) return;
                mail.Categories = string.IsNullOrEmpty(existing) ? categoryName : existing + "; " + categoryName;
                mail.Save();
            }
            catch (Exception ex)
            {
                Logger.Warning(ex, "StampSingleCategory failed");
            }
        }

        private void OnPipeMessage(object? sender, string message)
        {
            // Message format (UTF-8 single line):
            //   COMPOSE <caseId>
            // Future commands may follow.
            try
            {
                if (string.IsNullOrWhiteSpace(message)) return;
                var trimmed = message.Trim();
                if (trimmed.StartsWith("COMPOSE ", StringComparison.OrdinalIgnoreCase))
                {
                    var caseId = trimmed.Substring("COMPOSE ".Length).Trim();
                    if (!string.IsNullOrWhiteSpace(caseId))
                    {
                        _ = LaunchComposeFromCaseByIdAsync(caseId);
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Warning(ex, "OnPipeMessage handler failed for {Message}", message);
            }
        }

        /// <summary>
        /// Called by ThisAddIn_Startup once the VSTO task-pane collection is
        /// available. Spawns the right-hand "Case Info" pane on every Explorer.
        /// </summary>
        public void InitializeSidebar(CustomTaskPaneCollection taskPanes)
        {
            if (taskPanes == null) throw new ArgumentNullException(nameof(taskPanes));
            if (_sidebarController != null) return;

            if (MatchingService == null)
            {
                Logger.Information("AddInHost: sidebar deferred — MatchingService not yet available");
                CrmClientChanged += OnCrmClientChangedForSidebar;
                _pendingTaskPanes = taskPanes;
                return;
            }

            StartSidebar(taskPanes);
        }

        private CustomTaskPaneCollection? _pendingTaskPanes;

        private void OnCrmClientChangedForSidebar(object? sender, EventArgs e)
        {
            if (MatchingService == null || _pendingTaskPanes == null || _sidebarController != null) return;
            StartSidebar(_pendingTaskPanes);
            CrmClientChanged -= OnCrmClientChangedForSidebar;
            _pendingTaskPanes = null;
        }

        private void StartSidebar(CustomTaskPaneCollection taskPanes)
        {
            try
            {
                _sidebarController = new SidebarController(
                    taskPanes,
                    _outlookApp,
                    MatchingService!,
                    this,
                    System.Windows.Threading.Dispatcher.CurrentDispatcher,
                    Logger);
                _sidebarController.Initialize();
                Logger.Information("AddInHost: sidebar attached");
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "AddInHost: sidebar attach failed");
            }
        }

        public bool IsConfigured => CrmClient != null && FilingService != null;

        public event EventHandler? CrmClientChanged;

        private void TryInitializeCrm()
        {
            DisposeCrmStack();

            var creds = CredentialStore.Load();
            if (creds == null || string.IsNullOrWhiteSpace(creds.ApiKey))
            {
                Logger.Information("AddInHost: no stored API key — CRM features disabled until first-run setup");
                CrmClientChanged?.Invoke(this, EventArgs.Empty);
                return;
            }

            if (!Uri.TryCreate(Settings.EspoCrmUrl, UriKind.Absolute, out var baseUrl))
            {
                Logger.Warning("AddInHost: settings.EspoCrmUrl is not a valid URL: {Url}", Settings.EspoCrmUrl);
                CrmClientChanged?.Invoke(this, EventArgs.Empty);
                return;
            }

            try
            {
                _httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
                CrmClient = new EspoCrmClient(_httpClient, baseUrl.ToString(), creds.Username, creds.ApiKey, Logger);
                FilingService = new FilingService(CrmClient, RetryQueue, Logger);
                MatchingService = new MatchingService(CrmClient, _matchCache, Logger);
                ComposeService = new ComposeService(CrmClient, Logger);
                AttachmentSaveService = new AttachmentSaveService(CrmClient, Logger);
                _folderResolver ??= new FolderResolver(_outlookApp, Logger);
                _folderWatcher = new FolderWatcher(
                    _outlookApp,
                    _folderResolver,
                    MatchingService,
                    FilingService,
                    System.Windows.Threading.Dispatcher.CurrentDispatcher,
                    Logger);
                _folderWatcher.Apply(Settings.WatchedFolders);
                _retryProcessor = new RetryQueueProcessor(RetryQueue, CrmClient, Logger);
                _retryProcessor.Start();
                Logger.Information("AddInHost: CRM client initialized for {BaseUrl}", baseUrl);
                CrmClientChanged?.Invoke(this, EventArgs.Empty);
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "AddInHost: failed to initialize CRM client");
                DisposeCrmStack();
                CrmClientChanged?.Invoke(this, EventArgs.Empty);
            }
        }

        private void DisposeCrmStack()
        {
            try { _folderWatcher?.Dispose(); } catch { /* ignore */ }
            _folderWatcher = null;
            try { _retryProcessor?.Dispose(); } catch { /* ignore */ }
            _retryProcessor = null;
            try { _httpClient?.Dispose(); } catch { /* ignore */ }
            _httpClient = null;
            CrmClient = null;
            FilingService = null;
            MatchingService = null;
            ComposeService = null;
            AttachmentSaveService = null;
        }

        /// <summary>
        /// Entry point invoked by the Inspector "Compose from Case" ribbon
        /// button. The MailItem comes from the open Inspector — populate it
        /// with case details and stamp the tracking UserProperties.
        /// </summary>
        public async Task LaunchComposeFromCaseAsync(Outlook.MailItem mail)
        {
            if (mail == null) return;
            if (!IsConfigured)
            {
                MessageBox.Show(
                    "התוסף עדיין לא מוגדר. פתח את ההגדרות תחילה.",
                    "Marcus-Law OutlookAddin",
                    MessageBoxButton.OK,
                    MessageBoxImage.Information);
                return;
            }

            var vm = new ComposeFromCaseViewModel(CrmClient!, Logger);
            var dialog = new ComposeFromCaseDialog(vm);
            AttachOwnerToOutlook(dialog);
            var ok = dialog.ShowDialog();
            if (ok != true || vm.Result == null) return;

            try
            {
                await ComposeService!.ApplyCaseToMailAsync(mail, vm.Result.Id, Settings.EspoCrmUrl).ConfigureAwait(true);
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "LaunchComposeFromCaseAsync failed");
                MessageBox.Show(
                    "טעינת התיק נכשלה: " + ex.Message,
                    "Marcus-Law OutlookAddin",
                    MessageBoxButton.OK,
                    MessageBoxImage.Error);
            }
        }

        /// <summary>
        /// Save-attachments-only flow. The user clicks the ribbon's "שמור
        /// קבצים" button while standing on a mail with one or more
        /// attachments. We extract the attachments (no Email entity
        /// created), let the user pick a Case + optional DocumentFolder,
        /// then upload each attachment + create a Document linked to the
        /// case.
        /// </summary>
        public async Task LaunchSaveAttachmentsAsync(Outlook.MailItem mail)
        {
            if (mail == null) return;
            if (!IsConfigured)
            {
                var result = MessageBox.Show(
                    "התוסף עדיין לא מוגדר. לפתוח את חלון ההגדרות עכשיו?",
                    "Marcus-Law OutlookAddin",
                    MessageBoxButton.YesNo,
                    MessageBoxImage.Information);
                if (result == MessageBoxResult.Yes) LaunchSettings();
                if (!IsConfigured) return;
            }

            // Extract on the STA — MailItemExtractor handles all COM access
            // and gives us a self-contained envelope.
            MailEnvelope envelope;
            try
            {
                envelope = MailItemExtractor.Extract(mail);
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "LaunchSaveAttachmentsAsync: extract failed");
                MessageBox.Show(
                    "קריאת הקבצים המצורפים נכשלה: " + ex.Message,
                    "Marcus-Law OutlookAddin",
                    MessageBoxButton.OK,
                    MessageBoxImage.Error);
                return;
            }

            if (envelope.Attachments.Count == 0)
            {
                MessageBox.Show(
                    "אין במייל הזה קבצים מצורפים לשמירה.",
                    "Marcus-Law OutlookAddin",
                    MessageBoxButton.OK,
                    MessageBoxImage.Information);
                return;
            }

            var vm = new SaveAttachmentsViewModel(CrmClient!, Logger, envelope.Attachments);
            // Subfolders are populated lazily once the user picks a case;
            // the default static set is already pre-loaded.

            var dialog = new SaveAttachmentsDialog(vm);
            AttachOwnerToOutlook(dialog);
            var ok = dialog.ShowDialog();
            if (ok != true) return;

            var picked = vm.PickedCase;
            var selected = vm.SelectedAttachments;
            if (picked == null || string.IsNullOrEmpty(picked.Id) || selected.Count == 0) return;

            // Prefer the path the VM resolved when the user picked the case
            // (it already fetched the primary contact and applied the
            // server's naming convention). Fall back to the no-contact
            // resolve only if that step somehow didn't run.
            var caseFolder = !string.IsNullOrWhiteSpace(vm.ResolvedCaseFolderPath)
                ? vm.ResolvedCaseFolderPath!
                : SaveAttachmentsViewModel.ResolveCaseFolderPath(picked);
            if (string.IsNullOrEmpty(caseFolder))
            {
                MessageBox.Show(
                    "לא ניתן לחשב את נתיב התיקייה של התיק. ייתכן שהתיק עוד לא הוגדר ב-Klear.",
                    "Marcus-Law OutlookAddin",
                    MessageBoxButton.OK,
                    MessageBoxImage.Warning);
                return;
            }
            var subfolder = vm.ChosenSubfolderName;
            var targetFolderPath = string.IsNullOrEmpty(subfolder)
                ? caseFolder
                : caseFolder + "/" + subfolder;

            try
            {
                Logger.Information(
                    "Saving {Count} attachment(s) to Case/{Id} ({Name}) → {Path}",
                    selected.Count, picked.Id, picked.Name, targetFolderPath);

                // Safety net: ensure the case folder + defaults exist on
                // disk before the per-file uploads (the dialog may have
                // been submitted before the prior ensure call finished).
                try { await CrmClient!.EnsureCaseSubfoldersAsync(picked.Id).ConfigureAwait(true); }
                catch (Exception ex) { Logger.Warning(ex, "Pre-upload ensure failed; continuing anyway"); }

                var summary = await AttachmentSaveService!.SaveAsync(selected, targetFolderPath).ConfigureAwait(true);
                ShowAttachmentSummary(summary, picked.Name ?? picked.Id, targetFolderPath);
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "LaunchSaveAttachmentsAsync failed");
                MessageBox.Show(
                    "שמירת הקבצים נכשלה: " + ex.Message,
                    "Marcus-Law OutlookAddin",
                    MessageBoxButton.OK,
                    MessageBoxImage.Error);
            }
        }

        private static void ShowAttachmentSummary(AttachmentSaveSummary summary, string caseLabel, string targetPath)
        {
            string message;
            MessageBoxImage icon;
            if (summary.AuthRequired)
            {
                message = "מפתח ה-API נדחה. יש לעדכן את ההגדרות ולנסות שוב.";
                icon = MessageBoxImage.Warning;
            }
            else
            {
                var parts = new List<string>();
                if (summary.SavedCount > 0) parts.Add($"נשמרו {summary.SavedCount} קבצים אל \"{caseLabel}\" ({targetPath})");
                if (summary.FailedCount > 0) parts.Add($"{summary.FailedCount} נכשלו");
                message = parts.Count == 0 ? "לא נשמרו פריטים." : string.Join(" · ", parts);
                icon = summary.FailedCount > 0 ? MessageBoxImage.Warning : MessageBoxImage.Information;
            }
            MessageBox.Show(message, "Marcus-Law OutlookAddin", MessageBoxButton.OK, icon);
        }

        /// <summary>
        /// Entry point for the URL-protocol handler (outlookaddin://compose?caseId=…).
        /// Creates a brand new MailItem, populates it with case details, then
        /// shows the Inspector so the user can compose+send.
        /// </summary>
        public async Task LaunchComposeFromCaseByIdAsync(string caseId)
        {
            if (!IsConfigured)
            {
                MessageBox.Show(
                    "התוסף עדיין לא מוגדר. פתח את ההגדרות תחילה.",
                    "Marcus-Law OutlookAddin",
                    MessageBoxButton.OK,
                    MessageBoxImage.Information);
                return;
            }

            Outlook.MailItem? mail = null;
            try
            {
                mail = (Outlook.MailItem)_outlookApp.CreateItem(Outlook.OlItemType.olMailItem);
                await ComposeService!.ApplyCaseToMailAsync(mail, caseId, Settings.EspoCrmUrl).ConfigureAwait(true);
                mail.Display(false);
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "LaunchComposeFromCaseByIdAsync failed for caseId={CaseId}", caseId);
                if (mail != null) try { Marshal.ReleaseComObject(mail); } catch { }
                MessageBox.Show(
                    "פתיחת המייל מתיק נכשלה: " + ex.Message,
                    "Marcus-Law OutlookAddin",
                    MessageBoxButton.OK,
                    MessageBoxImage.Error);
            }
        }

        /// <summary>
        /// Opens the Settings dialog. On Save, tears down and rebuilds the CRM
        /// client/filing/retry stack so the new credentials take effect immediately.
        /// </summary>
        /// <summary>
        /// Re-opens the right-hand sidebar for the active Explorer. Used by
        /// the "פרטי תיק" ribbon button so the user can recover after
        /// clicking the pane's X.
        /// </summary>
        public void ShowSidebar()
        {
            _sidebarController?.ShowForActiveExplorer();
        }

        public bool LaunchSettings()
        {
            var vm = new SettingsViewModel(Settings, SettingsManager, CredentialStore, Logger);
            try
            {
                var tree = FolderTreeBuilder.BuildFlatList(_outlookApp, Logger);
                vm.LoadFolderTree(tree);
            }
            catch (Exception ex)
            {
                Logger.Warning(ex, "LaunchSettings: folder tree load failed");
            }
            var dialog = new SettingsDialog(vm);
            AttachOwnerToOutlook(dialog);
            var ok = dialog.ShowDialog();
            if (ok != true) return false;

            // Re-read settings from disk in case fields were normalized.
            var refreshed = SettingsManager.Load();
            Settings.EspoCrmUrl = refreshed.EspoCrmUrl;
            Settings.Locale = refreshed.Locale;
            Settings.WatchedFolders.Clear();
            Settings.WatchedFolders.AddRange(refreshed.WatchedFolders);
            TryInitializeCrm();
            return true;
        }

        /// <summary>
        /// Entry point invoked by the ribbon "File to EspoCRM" button. Reads the
        /// current Explorer selection, opens the search dialog, files on success,
        /// and stamps categories on the chosen MailItems.
        /// </summary>
        public Task LaunchFileToCaseAsync() => LaunchFileToCaseAsync(null);

        /// <summary>
        /// Same as <see cref="LaunchFileToCaseAsync()"/>, but when the sidebar
        /// already resolved a single linked case for the sender we skip the
        /// search dialog and file directly to that case.
        /// </summary>
        public async Task LaunchFileToCaseAsync(MatchResult? preselectedMatch)
        {
            if (!IsConfigured)
            {
                var result = MessageBox.Show(
                    "התוסף עדיין לא מוגדר. לפתוח את חלון ההגדרות עכשיו?",
                    "Marcus-Law OutlookAddin",
                    MessageBoxButton.YesNo,
                    MessageBoxImage.Information);
                if (result == MessageBoxResult.Yes)
                {
                    LaunchSettings();
                }
                if (!IsConfigured) return;
            }

            var mailItems = ReadCurrentSelection();
            if (mailItems.Count == 0)
            {
                MessageBox.Show(
                    "לא נבחר מייל לתיוק. סמן מייל אחד או יותר ונסה שוב.",
                    "Marcus-Law OutlookAddin",
                    MessageBoxButton.OK,
                    MessageBoxImage.Information);
                return;
            }

            try
            {
                var envelopes = mailItems.Select(MailItemExtractor.Extract).ToList();

                FilingTarget? target = TryResolveSidebarAutoTarget(preselectedMatch);
                if (target != null)
                {
                    Logger.Information(
                        "Auto-filing {Count} mail item(s) from sidebar to {Type}/{Id} ({Name})",
                        envelopes.Count, target.ParentType, target.ParentId, target.ParentName);
                }
                else
                {
                    var firstEnvelope = envelopes[0];

                    var vm = new FileToCaseViewModel(CrmClient!, Settings, Logger)
                    {
                        SenderEmail = firstEnvelope.From,
                        SenderName = TryGetSenderName(mailItems[0])
                    };

                    var dialog = new FileToCaseDialog(vm);
                    AttachOwnerToOutlook(dialog);
                    var ok = dialog.ShowDialog();
                    Logger.Information(
                        "FileToCase dialog closed (ok={Ok}, selected id={Id}, entityType={Type})",
                        ok, vm.SelectedTarget?.Id, vm.SelectedTarget?.EntityType);
                    if (ok != true) return;

                    target = vm.GetSelectedFilingTarget();
                    if (target == null)
                    {
                        Logger.Warning(
                            "Filing skipped — selected EspoEntityRef has no usable Id/EntityType (id={Id}, name={Name}, type={Type})",
                            vm.SelectedTarget?.Id, vm.SelectedTarget?.Name, vm.SelectedTarget?.EntityType);
                        MessageBox.Show(
                            "התיק שנבחר לא הוחזר עם סוג ישות (entityType) תקין. ייתכן ש-Klear שלך מחזיר שדה אחר; הלוג מכיל את הפרטים. שלח לאדמין.",
                            "Marcus-Law OutlookAddin",
                            MessageBoxButton.OK,
                            MessageBoxImage.Warning);
                        return;
                    }

                    Logger.Information(
                        "Filing {Count} mail item(s) to {Type}/{Id} ({Name})",
                        envelopes.Count, target.ParentType, target.ParentId, target.ParentName);
                }

                var summary = await FilingService!.FileAsync(envelopes, target).ConfigureAwait(true);

                StampCategories(mailItems, envelopes, summary, target);
                PromoteRecentEntity(target);
                ShowSummary(summary, target);
            }
            finally
            {
                foreach (var item in mailItems)
                {
                    Release(item);
                }
            }
        }

        // Sidebar's "תייק לתיק הזה" makes a hard promise about which case
        // gets the mail — only honour it when the match resolved a single
        // linked case. With 0 or 2+ cases there is no unambiguous "this",
        // and the caller falls back to the manual search dialog.
        private static FilingTarget? TryResolveSidebarAutoTarget(MatchResult? match)
        {
            if (match?.RecentCases == null || match.RecentCases.Count != 1) return null;
            var c = match.RecentCases[0];
            if (string.IsNullOrWhiteSpace(c.Id)) return null;
            var label = !string.IsNullOrWhiteSpace(c.Number)
                ? $"[{c.Number}] {c.Name}"
                : (c.Name ?? c.Id);
            return new FilingTarget("Case", c.Id, label);
        }

        private List<Outlook.MailItem> ReadCurrentSelection()
        {
            var result = new List<Outlook.MailItem>();
            Outlook.Explorer? explorer = null;
            Outlook.Selection? selection = null;
            try
            {
                explorer = _outlookApp.ActiveExplorer();
                if (explorer == null) return result;
                selection = explorer.Selection;
                if (selection == null) return result;
                for (int i = 1; i <= selection.Count; i++)
                {
                    var entry = selection[i];
                    if (entry is Outlook.MailItem mail)
                    {
                        result.Add(mail);
                    }
                    else
                    {
                        Release(entry);
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Warning(ex, "ReadCurrentSelection failed");
            }
            finally
            {
                Release(selection);
                Release(explorer);
            }
            return result;
        }

        private void StampCategories(
            IList<Outlook.MailItem> mailItems,
            IList<MailEnvelope> envelopes,
            FilingBatchSummary summary,
            FilingTarget target)
        {
            var categoryName = "Filed: " + target.ParentName;
            EnsureCategoryExists(categoryName);

            for (int i = 0; i < mailItems.Count && i < envelopes.Count; i++)
            {
                var result = summary.Items.Count > i ? summary.Items[i] : null;
                if (result?.Outcome != FilingOutcome.Filed) continue;

                try
                {
                    var existing = mailItems[i].Categories ?? string.Empty;
                    if (existing.IndexOf(categoryName, StringComparison.Ordinal) >= 0) continue;
                    mailItems[i].Categories = string.IsNullOrEmpty(existing)
                        ? categoryName
                        : existing + "; " + categoryName;
                    mailItems[i].Save();
                }
                catch (Exception ex)
                {
                    Logger.Warning(ex, "Category stamp failed for item {Index}", i);
                }
            }
        }

        private void EnsureCategoryExists(string name)
        {
            Outlook.NameSpace? session = null;
            Outlook.Categories? categories = null;
            try
            {
                session = _outlookApp.Session;
                categories = session?.Categories;
                if (categories == null) return;

                for (int i = 1; i <= categories.Count; i++)
                {
                    var cat = categories[i];
                    try
                    {
                        if (string.Equals(cat.Name, name, StringComparison.Ordinal)) return;
                    }
                    finally
                    {
                        Release(cat);
                    }
                }
                categories.Add(name, Outlook.OlCategoryColor.olCategoryColorBlue);
            }
            catch (Exception ex)
            {
                Logger.Warning(ex, "EnsureCategoryExists({Name}) failed", name);
            }
            finally
            {
                Release(categories);
                Release(session);
            }
        }

        private void PromoteRecentEntity(FilingTarget target)
        {
            try
            {
                var entry = new EspoEntityRef
                {
                    Id = target.ParentId,
                    EntityType = target.ParentType,
                    Name = target.ParentName
                };

                Settings.RecentEntities.RemoveAll(r =>
                    string.Equals(r.Id, entry.Id, StringComparison.Ordinal) &&
                    string.Equals(r.EntityType, entry.EntityType, StringComparison.Ordinal));
                Settings.RecentEntities.Insert(0, entry);
                if (Settings.RecentEntities.Count > RecentEntitiesLimit)
                {
                    Settings.RecentEntities.RemoveRange(
                        RecentEntitiesLimit,
                        Settings.RecentEntities.Count - RecentEntitiesLimit);
                }

                SettingsManager.Save(Settings);
            }
            catch (Exception ex)
            {
                Logger.Warning(ex, "PromoteRecentEntity failed");
            }
        }

        private static void ShowSummary(FilingBatchSummary summary, FilingTarget target)
        {
            string message;
            MessageBoxImage icon;
            if (summary.AuthRequired)
            {
                message = "מפתח ה-API נדחה. יש לעדכן את ההגדרות ולנסות שוב.";
                icon = MessageBoxImage.Warning;
            }
            else
            {
                var parts = new List<string>();
                if (summary.FiledCount > 0) parts.Add($"תויקו {summary.FiledCount} מיילים אל \"{target.ParentName}\"");
                if (summary.QueuedCount > 0) parts.Add($"{summary.QueuedCount} בתור לניסיון חוזר");
                if (summary.FailedCount > 0) parts.Add($"{summary.FailedCount} נכשלו");
                message = parts.Count == 0
                    ? "לא נשלחו פריטים."
                    : string.Join(" · ", parts);
                icon = summary.FailedCount > 0 ? MessageBoxImage.Warning : MessageBoxImage.Information;
            }

            MessageBox.Show(message, "Marcus-Law OutlookAddin", MessageBoxButton.OK, icon);
        }

        private static string? TryGetSenderName(Outlook.MailItem item)
        {
            try { return item.SenderName; } catch { return null; }
        }

        private static void Release(object? com)
        {
            if (com != null && Marshal.IsComObject(com))
            {
                try { Marshal.ReleaseComObject(com); } catch { /* ignore */ }
            }
        }

        public void Dispose()
        {
            try { _outlookApp.ItemSend -= OnItemSend; } catch { }
            try { _sentItemsWatcher?.Dispose(); } catch { }
            try { _pipeListener?.Dispose(); } catch { }
            try { _sidebarController?.Dispose(); } catch { }
            try { _retryProcessor?.Dispose(); } catch { }
            try { _httpClient?.Dispose(); } catch { }
            try { _matchCache.Dispose(); } catch { }
            try
            {
                Logger.Information("AddInHost shutting down");
                Serilog.Log.CloseAndFlush();
            }
            catch { /* ignore */ }
        }
    }
}
