using System.Globalization;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using HellionChat.Code;
using HellionChat.Resources;
using HellionChat.Util;

namespace HellionChat.Ui;

// Bottom status bar. Slots left to right: channel indicator, privacy badge,
// counts, tells (hidden at 0), version (right-aligned). Updates at 1Hz;
// format strings are cached between updates.
internal sealed class StatusBar
{
    // DPI-aware bar height. The previous fixed 22px constant clipped on
    // Windows display-scaling >100% because ImGui renders the font bigger
    // than the reservation. GetTextLineHeightWithSpacing scales with the
    // current ImGui font; the 2px spacer is GlobalScale-rounded to stay
    // on integer pixel boundaries (same idiom as v1.4.6 F7.2 underline-pill
    // in ChatLogWindow.cs:1639-1653).
    public static float Height =>
        ImGui.GetTextLineHeightWithSpacing() + MathF.Round(2f * ImGuiHelpers.GlobalScale);

    private const long UpdateIntervalMs = 1000;

    // Initially outdated so the first frame always computes fresh.
    private long _lastUpdateMs = -UpdateIntervalMs;
    private string _cachedCountsText = string.Empty;
    private string _cachedTellsText = string.Empty;

    // Pure string logic, testable without ImGui init.
    public static string FormatCounts(int tabs, int messages)
    {
        // InvariantCulture so locale doesn't affect the format (e.g. de_DE "1,2k").
        var msgPart =
            messages >= 1000
                ? string.Format(CultureInfo.InvariantCulture, "{0:0.0}k msg", messages / 1000.0)
                : $"{messages} msg";
        var tabsPart = $"{tabs} {(tabs == 1 ? "tab" : "tabs")}";
        return $"{tabsPart} · {msgPart}";
    }

    // Pure string logic, testable without ImGui init. Returns empty string at 0 tells.
    public static string FormatTells(int count)
    {
        if (count <= 0)
            return string.Empty;
        return $"{count} {(count == 1 ? "tell" : "tells")}";
    }

    // Single-pass replacement for a LINQ Sum+Count pair. Pure helper for unit testing.
    internal static (int messages, int tells) AggregateForStatusBar(IList<Tab> tabs)
    {
        int messages = 0,
            tells = 0;
        foreach (var t in tabs)
        {
            messages += t.Messages.Count;
            if (t.IsTempTab)
                tells++;
        }
        return (messages, tells);
    }

    // Test hook to verify cache logic without a real time source.
    internal (string counts, string tells) SnapshotForTest(
        long now,
        int tabs,
        int messages,
        int tells
    )
    {
        UpdateCacheIfDue(now, tabs, messages, tells);
        return (_cachedCountsText, _cachedTellsText);
    }

    private void UpdateCacheIfDue(long now, int tabs, int messages, int tells)
    {
        if (now - _lastUpdateMs < UpdateIntervalMs)
            return;
        _cachedCountsText = FormatCounts(tabs, messages);
        _cachedTellsText = FormatTells(tells);
        _lastUpdateMs = now;
    }

    public void Draw(Plugin plugin)
    {
        var theme = plugin.ThemeRegistry.Active;
        var now = Environment.TickCount64;

        if (now - _lastUpdateMs >= UpdateIntervalMs)
        {
            var (messages, tells) = AggregateForStatusBar(Plugin.Config.Tabs);
            UpdateCacheIfDue(now, Plugin.Config.Tabs.Count, messages, tells);
        }

        // Border top via DrawList -- ImGui.Separator has too much padding.
        var cursorY = ImGui.GetCursorScreenPos().Y;
        var winLeft = ImGui.GetWindowPos().X;
        var winRight = winLeft + ImGui.GetWindowSize().X;
        ImGui
            .GetWindowDrawList()
            .AddLine(
                new Vector2(winLeft, cursorY),
                new Vector2(winRight, cursorY),
                ColourUtil.RgbaToAbgr(theme.Colors.Border),
                1f
            );

        ImGui.Dummy(new Vector2(0, 2));

        // Slot 1: active channel indicator
        var inputCh = plugin.CurrentTab?.CurrentChannel?.Channel ?? InputChannel.Invalid;
        var hasChannel = inputCh != InputChannel.Invalid;
        var chatType = inputCh.ToChatType();
        var channelName = hasChannel ? chatType.Name() : "—";
        var channelColor = hasChannel
            ? (plugin.Functions.Chat.GetChannelColor(chatType) ?? theme.Colors.TextMuted)
            : theme.Colors.TextMuted;
        DrawDot(channelColor);
        ImGui.SameLine();
        ImGui.TextUnformatted(channelName);

        // Slot 2: privacy badge
        ImGui.SameLine();
        DrawSeparator();
        ImGui.SameLine();
        using (plugin.FontManager.FontAwesome.Push())
        {
            ImGui.TextUnformatted(FontAwesomeIcon.Lock.ToIconString());
        }
        ImGui.SameLine();
        var privacyLabel = Plugin.Config.PrivacyFilterEnabled
            ? HellionStrings.StatusBar_Privacy_Enabled
            : HellionStrings.StatusBar_Privacy_Open;
        ImGui.TextUnformatted(privacyLabel);

        // Slot 3: counts
        ImGui.SameLine();
        DrawSeparator();
        ImGui.SameLine();
        ImGui.TextUnformatted(_cachedCountsText);

        // Slot 4: tells (hidden at 0)
        if (!string.IsNullOrEmpty(_cachedTellsText))
        {
            ImGui.SameLine();
            DrawSeparator();
            ImGui.SameLine();
            ImGui.TextUnformatted(_cachedTellsText);
        }

        // Slot 5: version, right-aligned, muted. Hidden when the window is
        // too narrow to fit all five slots — the other four need ~200 px
        // before the version text starts clipping into them.
        var versionText = $"v{Plugin.Interface.Manifest.AssemblyVersion} · Hellion";
        var versionWidth = ImGui.CalcTextSize(versionText).X;
        var contentRegionMax = ImGui.GetContentRegionMax().X;
        const float MinOtherSlotsWidth = 200f;
        if (contentRegionMax - versionWidth > MinOtherSlotsWidth)
        {
            ImGui.SameLine(contentRegionMax - versionWidth);
            using (ImRaii.PushColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(theme.Colors.TextMuted)))
            {
                ImGui.TextUnformatted(versionText);
            }
        }
    }

    private static void DrawDot(uint rgba)
    {
        var pos = ImGui.GetCursorScreenPos();
        const float radius = 4f;
        ImGui
            .GetWindowDrawList()
            .AddCircleFilled(
                new Vector2(pos.X + radius, pos.Y + ImGui.GetTextLineHeight() / 2f),
                radius,
                ColourUtil.RgbaToAbgr(rgba)
            );
        ImGui.Dummy(new Vector2(radius * 2 + 4, ImGui.GetTextLineHeight()));
    }

    private static void DrawSeparator()
    {
        ImGui.TextDisabled("·");
    }
}
