@using LazyBear.MCP.Services.Logging
@using LazyBear.MCP.Services.ToolRegistry
@inject ToolRegistryService Registry
@inject InMemoryLogSink LogSink
@inject GlobalKeyboardService KeyboardService
@inject LocalizationService Localization
@inject IHostApplicationLifetime AppLifetime

@implements IDisposable

<Rows Expand="true">
    <Columns Expand="true">
        <Markup Content="@GetHeaderProjectSegment()"
                Foreground="@UiPalette.Text"
                Background="@UiPalette.HeaderBrandBackground"
                Decoration="@Spectre.Console.Decoration.Bold" />
        @foreach (var segment in BuildHeaderSegments())
        {
            <Markup Content="@segment.Content"
                    Foreground="@segment.Foreground"
                    Background="@segment.Background"
                    Decoration="@segment.Decoration" />
        }
    </Columns>

    <Panel BorderColor="@UiPalette.Frame"
           Expand="true"
           Height="@GetPanelHeight()"
           Padding="@(new Spectre.Console.Padding(1, 0, 1, 0))">
        <Rows Expand="true">
            @if (_activeTab == Tab.Overview)
            {
                <OverviewTab Rows="@GetOverviewRows()"
                             SelectedIndex="@_overviewSelection"
                             SelectedIndexChanged="@OnOverviewSelectionChanged"
                             ViewportRows="@GetOverviewViewportRows()"
                             Loc="@Localization.Current" />
            }
            else if (_activeTab == Tab.Logs)
            {
                <LogsTab Entries="@GetFilteredLogEntries()"
                         SelectedIndex="@_logSelection"
                         SelectedIndexChanged="@OnLogSelectionChanged"
                         SelectedFilter="@_logFilters[_logFilterIndex]"
                         ViewportRows="@GetLogsViewportRows()"
                         IsStickyToBottom="@_logsStickToBottom"
                         Loc="@Localization.Current" />
            }
            else
            {
                <SettingsTab Entries="@GetSettingsEntries()"
                             SelectedIndex="@_settingsSelection"
                             SelectedIndexChanged="@OnSettingsSelectionChanged"
                             ViewportRows="@GetSettingsViewportRows()"
                             Loc="@Localization.Current" />
            }
        </Rows>
    </Panel>
    <Markup Content="@BuildStatusBar(UiMetrics.ConsoleWidth)"
            Foreground="@UiPalette.Text"
            Background="@UiPalette.SurfaceMuted" />

    <ModalWindow IsOpened="@_isHelpOpen">
        <Panel Title="@Localization.Current.HelpModalTitle"
               TitleColor="@UiPalette.Accent"
               BorderColor="@UiPalette.AccentSoft"
               Width="@GetHelpModalWidth()"
               Padding="@(new Spectre.Console.Padding(1, 0, 1, 0))">
            <Rows>
                <Markup Content="@Localization.Current.HintBar"
                        Foreground="@UiPalette.Text" />
                <Markup Content=" " />
                <Markup Content="@GetHelpLine(Tab.Overview, Localization.Current.OverviewHint)"
                        Foreground="@UiPalette.TextMuted" />
                <Markup Content="@GetHelpLine(Tab.Logs, Localization.Current.LogsHint)"
                        Foreground="@UiPalette.TextMuted" />
                <Markup Content="@GetHelpLine(Tab.Settings, Localization.Current.SettingsHint)"
                        Foreground="@UiPalette.TextMuted" />
                <Markup Content=" " />
                <Markup Content="@Localization.Current.HelpCloseHint"
                        Foreground="@UiPalette.AccentSoft"
                        Decoration="@Spectre.Console.Decoration.Bold" />
            </Rows>
        </Panel>
    </ModalWindow>
</Rows>

@code {
    private const char Nbsp = '\u00A0';

    private enum Tab
    {
        Overview,
        Logs,
        Settings
    }

    private readonly record struct HeaderSegment(
        string Content,
        Spectre.Console.Color Foreground,
        Spectre.Console.Color Background,
        Spectre.Console.Decoration Decoration);

    private static readonly Tab[] _tabs = [Tab.Overview, Tab.Logs, Tab.Settings];
    private static readonly string[] _logFilters = ["All", "Info", "Warn", "Error"];
    private readonly HashSet<string> _expandedModules = new(StringComparer.Ordinal);

    private Tab _activeTab = Tab.Overview;
    private int _overviewSelection;
    private int _logFilterIndex;
    private int _logSelection;
    private int _settingsSelection;
    private bool _logsStickToBottom = true;
    private bool _isHelpOpen;

    private static readonly string _mcpEndpoint =
        Environment.GetEnvironmentVariable("ASPNETCORE_URLS") ?? "http://localhost:5000";

    private static int GetPanelHeight() => Math.Max(UiMetrics.ConsoleHeight - 4, 10);
    private static int GetOverviewViewportRows() => Math.Max(GetPanelHeight() - 6, 3);
    private static int GetLogsViewportRows() => Math.Max(GetPanelHeight() - 10, 5);
    private static int GetSettingsViewportRows() => Math.Max(GetPanelHeight() - 7, 5);
    private static int GetHelpModalWidth() => Math.Max(Math.Min(UiMetrics.ConsoleWidth - 6, 84), 36);

    protected override void OnInitialized()
    {
        Registry.StateChanged += OnRegistryChanged;
        LogSink.OnLog += OnNewLog;
        KeyboardService.OnKeyPressed += OnConsoleKeyPressed;
        Localization.OnChanged += OnLocaleChanged;
    }

    // Конвертация ConsoleKeyInfo → KeyboardEventArgs для переиспользования существующей логики
    private static KeyboardEventArgs ConvertKey(ConsoleKeyInfo key)
    {
        var name = key.Key switch
        {
            ConsoleKey.UpArrow    => "ArrowUp",
            ConsoleKey.DownArrow  => "ArrowDown",
            ConsoleKey.LeftArrow  => "ArrowLeft",
            ConsoleKey.RightArrow => "ArrowRight",
            ConsoleKey.Enter      => "Enter",
            ConsoleKey.Spacebar   => " ",
            ConsoleKey.Home       => "Home",
            ConsoleKey.End        => "End",
            ConsoleKey.PageUp     => "PageUp",
            ConsoleKey.PageDown   => "PageDown",
            ConsoleKey.Tab        => "Tab",
            ConsoleKey.Escape     => "Escape",
            _                     => key.KeyChar == '\0' ? string.Empty : key.KeyChar.ToString()
        };

        return new KeyboardEventArgs
        {
            Key      = name,
            ShiftKey = (key.Modifiers & ConsoleModifiers.Shift) != 0
        };
    }

    private void OnConsoleKeyPressed(ConsoleKeyInfo key)
    {
        var args = ConvertKey(key);
        if (string.IsNullOrEmpty(args.Key))
        {
            return;
        }

        InvokeAsync(() =>
        {
            HandleKeyDown(args);
            StateHasChanged();
        });
    }

    private void OnLocaleChanged() =>
        InvokeAsync(StateHasChanged);

    private void HandleKeyDown(KeyboardEventArgs args)
    {
        if (args.Key is "h" or "H")
        {
            _isHelpOpen = !_isHelpOpen;
            return;
        }

        if (_isHelpOpen)
        {
            if (args.Key == "Escape")
            {
                _isHelpOpen = false;
            }

            return;
        }

        if (string.Equals(args.Key, "Tab", StringComparison.Ordinal))
        {
            ChangeTab(args.ShiftKey ? -1 : 1);
            return;
        }

        if (args.Key is "l" or "L")
        {
            Localization.SwitchNext();
            return; // StateHasChanged вызовет OnLocaleChanged
        }

        if (args.Key is "q" or "Q")
        {
            AppLifetime.StopApplication();
            return;
        }

        switch (_activeTab)
        {
            case Tab.Overview:
                HandleOverviewKey(args);
                break;
            case Tab.Logs:
                HandleLogsKey(args);
                break;
            case Tab.Settings:
                HandleSettingsKey(args);
                break;
        }
    }

    private Task OnOverviewSelectionChanged(int value)
    {
        var rows = GetOverviewRows();
        _overviewSelection = rows.Count == 0 ? 0 : Math.Clamp(value, 0, rows.Count - 1);
        return Task.CompletedTask;
    }

    private Task OnLogSelectionChanged(int value)
    {
        var entries = GetFilteredLogEntries();
        _logSelection = entries.Count == 0 ? 0 : Math.Clamp(value, 0, entries.Count - 1);
        _logsStickToBottom = entries.Count == 0 || _logSelection >= entries.Count - 1;
        return Task.CompletedTask;
    }

    private Task OnSettingsSelectionChanged(int value)
    {
        var entries = GetSettingsEntries();
        _settingsSelection = entries.Count == 0 ? 0 : Math.Clamp(value, 0, entries.Count - 1);
        return Task.CompletedTask;
    }

    private void ChangeTab(int step)
    {
        var currentIndex = Array.IndexOf(_tabs, _activeTab);
        if (currentIndex < 0)
        {
            currentIndex = 0;
        }

        var nextIndex = (currentIndex + step + _tabs.Length) % _tabs.Length;
        _activeTab = _tabs[nextIndex];
        ClampSelections();
    }

    private void HandleOverviewKey(KeyboardEventArgs args)
    {
        var rows = GetOverviewRows();
        if (rows.Count == 0)
        {
            _overviewSelection = 0;
            return;
        }

        _overviewSelection = Math.Clamp(_overviewSelection, 0, rows.Count - 1);

        switch (args.Key)
        {
            case "ArrowUp":
                _overviewSelection = Math.Max(0, _overviewSelection - 1);
                break;
            case "ArrowDown":
                _overviewSelection = Math.Min(rows.Count - 1, _overviewSelection + 1);
                break;
            case "Home":
                _overviewSelection = 0;
                break;
            case "End":
                _overviewSelection = rows.Count - 1;
                break;
            case "Enter":
                _activeTab = Tab.Settings;
                SelectSettingsModule(rows[_overviewSelection].ModuleName);
                break;
        }
    }

    private void HandleLogsKey(KeyboardEventArgs args)
    {
        switch (args.Key)
        {
            case "ArrowLeft":
                _logFilterIndex = (_logFilterIndex - 1 + _logFilters.Length) % _logFilters.Length;
                ResetLogsSelectionToBottom();
                return;
            case "ArrowRight":
                _logFilterIndex = (_logFilterIndex + 1) % _logFilters.Length;
                ResetLogsSelectionToBottom();
                return;
        }

        var entries = GetFilteredLogEntries();
        if (entries.Count == 0)
        {
            _logSelection = 0;
            _logsStickToBottom = true;
            return;
        }

        _logSelection = Math.Clamp(_logSelection, 0, entries.Count - 1);
        var page = Math.Max(GetLogsViewportRows() - 1, 1);

        switch (args.Key)
        {
            case "ArrowUp":
                _logSelection = Math.Max(0, _logSelection - 1);
                break;
            case "ArrowDown":
                _logSelection = Math.Min(entries.Count - 1, _logSelection + 1);
                break;
            case "PageUp":
                _logSelection = Math.Max(0, _logSelection - page);
                break;
            case "PageDown":
            case " ":
            case "Spacebar":
                _logSelection = Math.Min(entries.Count - 1, _logSelection + page);
                break;
            case "Home":
                _logSelection = 0;
                break;
            case "End":
                _logSelection = entries.Count - 1;
                break;
        }

        _logsStickToBottom = _logSelection >= entries.Count - 1;
    }

    private void HandleSettingsKey(KeyboardEventArgs args)
    {
        var entries = GetSettingsEntries();
        if (entries.Count == 0)
        {
            _settingsSelection = 0;
            return;
        }

        _settingsSelection = Math.Clamp(_settingsSelection, 0, entries.Count - 1);
        var selected = entries[_settingsSelection];
        var page = Math.Max(GetSettingsViewportRows() - 1, 1);

        switch (args.Key)
        {
            case "ArrowUp":
                _settingsSelection = Math.Max(0, _settingsSelection - 1);
                return;
            case "ArrowDown":
                _settingsSelection = Math.Min(entries.Count - 1, _settingsSelection + 1);
                return;
            case "PageUp":
                _settingsSelection = Math.Max(0, _settingsSelection - page);
                return;
            case "PageDown":
                _settingsSelection = Math.Min(entries.Count - 1, _settingsSelection + page);
                return;
            case "Home":
                _settingsSelection = 0;
                return;
            case "End":
                _settingsSelection = entries.Count - 1;
                return;
            case "ArrowRight":
                ExpandModule(selected);
                return;
            case "ArrowLeft":
                CollapseModuleOrFocusParent(selected);
                return;
            case "Enter":
                ToggleExpansion(selected);
                return;
            case " ":
            case "Spacebar":
                ToggleSetting(selected);
                return;
        }
    }

    private void ExpandModule(SettingsEntry entry)
    {
        if (entry.Kind != SettingsEntryKind.Module || entry.IsExpanded)
        {
            return;
        }

        _expandedModules.Add(entry.ModuleName);
        ClampSelections();
    }

    private void CollapseModuleOrFocusParent(SettingsEntry entry)
    {
        if (entry.Kind == SettingsEntryKind.Module)
        {
            if (_expandedModules.Remove(entry.ModuleName))
            {
                ClampSelections();
            }

            return;
        }

        _expandedModules.Remove(entry.ModuleName);
        SelectSettingsModule(entry.ModuleName);
    }

    private void ToggleExpansion(SettingsEntry entry)
    {
        if (entry.Kind != SettingsEntryKind.Module)
        {
            ToggleSetting(entry);
            return;
        }

        if (_expandedModules.Contains(entry.ModuleName))
        {
            _expandedModules.Remove(entry.ModuleName);
        }
        else
        {
            _expandedModules.Add(entry.ModuleName);
        }

        SelectSettingsModule(entry.ModuleName);
    }

    private void ToggleSetting(SettingsEntry entry)
    {
        if (entry.Kind == SettingsEntryKind.Module)
        {
            Registry.ToggleModule(entry.ModuleName);
            return;
        }

        if (!string.IsNullOrWhiteSpace(entry.ToolName))
        {
            Registry.ToggleTool(entry.ModuleName, entry.ToolName);
        }
    }

    private void SelectSettingsModule(string moduleName)
    {
        var entries = GetSettingsEntries();
        var index = entries.FindIndex(entry =>
            entry.Kind == SettingsEntryKind.Module &&
            string.Equals(entry.ModuleName, moduleName, StringComparison.Ordinal));

        _settingsSelection = index >= 0 ? index : 0;
    }

    private void ResetLogsSelectionToBottom()
    {
        var entries = GetFilteredLogEntries();
        _logSelection = Math.Max(entries.Count - 1, 0);
        _logsStickToBottom = true;
    }

    private List<OverviewRow> GetOverviewRows() =>
        Registry.GetModules()
            .Select(module =>
            {
                var (configuredTools, totalTools) = Registry.GetConfiguredToolCounts(module.ModuleName);
                return new OverviewRow(
                    module.ModuleName,
                    module.Description,
                    Registry.IsModuleEnabled(module.ModuleName),
                    configuredTools,
                    totalTools);
            })
            .ToList();

    private List<SettingsEntry> GetSettingsEntries()
    {
        var entries = new List<SettingsEntry>();

        foreach (var module in Registry.GetModules())
        {
            var isModuleEnabled = Registry.IsModuleEnabled(module.ModuleName);
            var isExpanded = _expandedModules.Contains(module.ModuleName);

            entries.Add(new SettingsEntry(
                SettingsEntryKind.Module,
                module.ModuleName,
                null,
                module.ModuleName,
                module.Description,
                isModuleEnabled,
                isModuleEnabled,
                isExpanded,
                0));

            if (!isExpanded)
            {
                continue;
            }

            foreach (var toolName in module.ToolNames)
            {
                var isConfigured = Registry.IsToolConfiguredEnabled(module.ModuleName, toolName);
                entries.Add(new SettingsEntry(
                    SettingsEntryKind.Tool,
                    module.ModuleName,
                    toolName,
                    toolName,
                    isModuleEnabled
                        ? $"{module.ModuleName} / {toolName}"
                        : $"{module.ModuleName} / {toolName} {Localization.Current.ModuleOffPreserved}",
                    isConfigured,
                    isModuleEnabled,
                    false,
                    1));
            }
        }

        return entries;
    }

    private List<LogEntry> GetFilteredLogEntries()
    {
        var entries = LogSink.GetEntries();
        return _logFilters[_logFilterIndex] switch
        {
            "Info" => entries.Where(IsInfoLevel).ToList(),
            "Warn" => entries.Where(entry => entry.Level == LogLevel.Warning).ToList(),
            "Error" => entries.Where(entry => entry.Level is LogLevel.Error or LogLevel.Critical).ToList(),
            _ => entries.ToList()
        };
    }

    private void ClampSelections()
    {
        var overviewRows = GetOverviewRows();
        _overviewSelection = overviewRows.Count == 0
            ? 0
            : Math.Clamp(_overviewSelection, 0, overviewRows.Count - 1);

        var logEntries = GetFilteredLogEntries();
        _logSelection = logEntries.Count == 0
            ? 0
            : Math.Clamp(_logSelection, 0, logEntries.Count - 1);

        var settingsEntries = GetSettingsEntries();
        _settingsSelection = settingsEntries.Count == 0
            ? 0
            : Math.Clamp(_settingsSelection, 0, settingsEntries.Count - 1);
    }

    private void OnRegistryChanged()
    {
        InvokeAsync(() =>
        {
            ClampSelections();
            StateHasChanged();
        });
    }

    private void OnNewLog(LogEntry entry)
    {
        InvokeAsync(() =>
        {
            if (_logsStickToBottom && MatchesCurrentLogFilter(entry))
            {
                var filteredEntries = GetFilteredLogEntries();
                _logSelection = Math.Max(filteredEntries.Count - 1, 0);
            }
            else
            {
                ClampSelections();
            }

            StateHasChanged();
        });
    }

    private bool MatchesCurrentLogFilter(LogEntry entry) =>
        _logFilters[_logFilterIndex] switch
        {
            "Info" => IsInfoLevel(entry),
            "Warn" => entry.Level == LogLevel.Warning,
            "Error" => entry.Level is LogLevel.Error or LogLevel.Critical,
            _ => true
        };

    private static bool IsInfoLevel(LogEntry entry) =>
        entry.Level is LogLevel.Information or LogLevel.Debug or LogLevel.Trace;

    private string GetTabLabel(Tab tab) => tab switch
    {
        Tab.Overview => Localization.Current.TabOverview,
        Tab.Logs     => Localization.Current.TabLogs,
        _            => Localization.Current.TabSettings
    };

    private HeaderSegment[] BuildHeaderSegments()
    {
        var width = Math.Max(UiMetrics.ConsoleWidth, 1);
        var projectSegmentWidth = GetHeaderProjectSegment().Length;
        var availableTabsWidth = Math.Max(width - projectSegmentWidth, 0);
        if (availableTabsWidth <= 0)
        {
            return [];
        }

        var labels = _tabs.Select(GetTabLabel).ToArray();
        var labelWidths = labels.Select(label => label.Length).ToArray();
        var availableLabelWidth = availableTabsWidth - (_tabs.Length * 2);
        if (availableLabelWidth < _tabs.Length)
        {
            return
            [
                new HeaderSegment(
                    FillBar(availableTabsWidth),
                    UiPalette.Text,
                    UiPalette.HeaderTabsBackground,
                    Spectre.Console.Decoration.None)
            ];
        }

        while (labelWidths.Sum() > availableLabelWidth)
        {
            var largestIndex = Array.IndexOf(labelWidths, labelWidths.Max());
            if (largestIndex < 0 || labelWidths[largestIndex] <= 1)
            {
                break;
            }

            labelWidths[largestIndex]--;
        }

        var segments = new List<HeaderSegment>(_tabs.Length + 1);
        var usedWidth = 0;

        for (var i = 0; i < _tabs.Length; i++)
        {
            var tab = _tabs[i];
            var label = FitInline(labels[i], labelWidths[i]);
            var content = WrapBarSegment(label);

            segments.Add(new HeaderSegment(
                content,
                tab == _activeTab ? UiPalette.SelectionForeground : UiPalette.Text,
                tab == _activeTab ? UiPalette.SelectionBackground : UiPalette.HeaderTabsBackground,
                tab == _activeTab ? Spectre.Console.Decoration.Bold : Spectre.Console.Decoration.None));

            usedWidth += content.Length;
        }

        var fillerWidth = Math.Max(availableTabsWidth - usedWidth, 0);
        if (fillerWidth > 0)
        {
            segments.Add(new HeaderSegment(
                FillBar(fillerWidth),
                UiPalette.Text,
                UiPalette.HeaderTabsBackground,
                Spectre.Console.Decoration.None));
        }

        return [.. segments];
    }

    private string GetHeaderProjectSegment() => WrapBarSegment(Localization.Current.ProjectTitle);

    private string GetHelpLine(Tab tab, string hint) => $"{GetTabLabel(tab)}: {hint}";

    private string GetStatusBarLeftText() => $"{Localization.Label} | {Localization.Current.HelpStatusHint}";

    private string GetStatusBarRightText() => $"{Localization.Current.DashboardEndpointLabel}: {_mcpEndpoint}";

    private string BuildStatusBar(int width)
    {
        var left = GetStatusBarLeftText();
        var right = GetStatusBarRightText();
        width = Math.Max(width, 1);

        if (right.Length >= width)
        {
            return right[^width..];
        }

        var leftWidth = Math.Max(width - right.Length - 1, 0);
        var fittedLeft = leftWidth == 0 ? string.Empty : PadRightVisible(FitInline(left, leftWidth), leftWidth);

        return leftWidth == 0
            ? PadLeftVisible(right, width)
            : $"{fittedLeft} {right}";
    }

    private static string FillBar(int width) =>
        width <= 0 ? string.Empty : new string(Nbsp, width);

    private static string WrapBarSegment(string text) => $"{Nbsp}{text}{Nbsp}";

    private static string PadRightVisible(string text, int width)
    {
        if (width <= 0) return string.Empty;
        if (text.Length >= width) return text[..width];
        return text + FillBar(width - text.Length);
    }

    private static string PadLeftVisible(string text, int width)
    {
        if (width <= 0) return string.Empty;
        if (text.Length >= width) return text[^width..];
        return FillBar(width - text.Length) + text;
    }

    private static string FitInline(string text, int width)
    {
        if (width <= 0) return string.Empty;
        if (text.Length <= width) return text;
        if (width <= 3) return text[..width];
        return text[..(width - 3)] + "...";
    }

    public void Dispose()
    {
        Registry.StateChanged -= OnRegistryChanged;
        LogSink.OnLog -= OnNewLog;
        KeyboardService.OnKeyPressed -= OnConsoleKeyPressed;
        Localization.OnChanged -= OnLocaleChanged;
    }
}
