using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
using Dalamud.Utility;
using HellionChat.Resources;
using HellionChat.Ui.SettingsTabs;
using HellionChat.Util;
using Microsoft.Extensions.Logging;

namespace HellionChat.Ui;

internal enum SettingsView
{
    Overview,
    Detail,
}

public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
{
    internal readonly Plugin Plugin;

    private Configuration Mutable { get; }
    private List<ISettingsTab> Tabs { get; }
    private int CurrentTab;
    private SettingsView View = SettingsView.Overview;

    // Set when a section is freshly entered; the first Draw afterwards reads it
    // and clears it, so each section starts collapsed every time it is opened.
    private bool _sectionJustEntered;
    private readonly SettingsOverview Overview;

    internal SettingsWindow(Plugin plugin, ILoggerFactory loggerFactory)
        : base($"{Language.Settings_Title.Format(Plugin.PluginName)}###chat2-settings")
    {
        Flags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse;

        SizeCondition = ImGuiCond.FirstUseEver;
        SizeConstraints = new WindowSizeConstraints
        {
            MinimumSize = new Vector2(475, 600),
            MaximumSize = new Vector2(float.MaxValue, float.MaxValue),
        };

        Plugin = plugin;
        Mutable = new Configuration();

        Overview = new SettingsOverview(this);

        Tabs =
        [
            new General(Plugin, Mutable),
            new Appearance(Plugin, Mutable, loggerFactory.CreateLogger<Appearance>()),
            new Chat(Plugin, Mutable),
            new SettingsTabs.Window(Plugin, Mutable),
            new SettingsTabs.Tabs(Plugin, Mutable),
            new DataAndPrivacy(Plugin, Mutable, loggerFactory.CreateLogger<DataAndPrivacy>()),
            new About(Plugin, Mutable),
        ];

        RespectCloseHotkey = false;
        DisableWindowSounds = true;

        Initialise();
    }

    public void Dispose()
    {
        // Slash-command + OpenConfigUi tear-down moved to Plugin.TearDownCommands.
    }

    private void Initialise()
    {
        Mutable.UpdateFrom(Plugin.Config, false);
    }

    public override void Draw()
    {
        if (ImGui.IsWindowAppearing())
        {
            Initialise();
            View = SettingsView.Overview;
        }

        // ESC in Detail view returns to Overview. Window focus check is
        // required so ESC doesn't fire when the user targets a different window.
        if (
            View == SettingsView.Detail
            && ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows)
            && ImGui.IsKeyPressed(ImGuiKey.Escape)
        )
        {
            View = SettingsView.Overview;
            return;
        }

        if (View == SettingsView.Overview)
            Overview.Draw();
        else
            DrawDetail();

        ImGui.Separator();
        DrawSaveButtons();
    }

    internal void OpenSection(int tabIndex)
    {
        CurrentTab = tabIndex;
        View = SettingsView.Detail;
        _sectionJustEntered = true;
    }

    internal void OpenOverview()
    {
        View = SettingsView.Overview;
    }

    private void DrawDetail()
    {
        // Breadcrumb header -- accent cyan, clickable, returns to Overview.
        using (ImRaii.PushColor(ImGuiCol.Text, 0xFF00BED2u))
        using (ImRaii.PushColor(ImGuiCol.Button, 0u))
        using (ImRaii.PushColor(ImGuiCol.ButtonHovered, 0x33FFFFFFu))
        using (ImRaii.PushColor(ImGuiCol.ButtonActive, 0x55FFFFFFu))
        {
            if (ImGui.SmallButton("<- Settings"))
            {
                View = SettingsView.Overview;
                return;
            }
        }
        ImGui.SameLine();
        ImGui.TextUnformatted("·");
        ImGui.SameLine();
        ImGui.TextUnformatted(Tabs[CurrentTab].Name.Split("###")[0]);

        ImGui.Spacing();
        ImGui.Separator();
        ImGui.Spacing();

        // Section content fills full width. Navigation back to another
        // section goes via the breadcrumb or ESC.
        var style = ImGui.GetStyle();
        var height =
            ImGui.GetContentRegionAvail().Y
            - style.FramePadding.Y * 2
            - style.ItemSpacing.Y
            - style.ItemInnerSpacing.Y * 2
            - ImGui.CalcTextSize("A").Y;

        using var child = ImRaii.Child("##chat2-settings-detail", new Vector2(-1, height));
        if (child.Success)
        {
            Tabs[CurrentTab].Draw(_sectionJustEntered);
            _sectionJustEntered = false;
        }
    }

    private void DrawSaveButtons()
    {
        var save = ImGui.Button(Language.Settings_Save);

        ImGui.SameLine();

        if (ImGui.Button(Language.Settings_SaveAndClose))
        {
            save = true;
            IsOpen = false;
        }

        ImGui.SameLine();

        if (ImGui.Button(Language.Settings_Discard))
            IsOpen = false;

        const string buttonLabel = "Anna's Ko-fi";
        const string buttonLabel2 = "Infi's Ko-fi";

        using (ImRaii.PushColor(ImGuiCol.Button, ColourUtil.RgbaToAbgr(0xFF5E5BFF)))
        using (ImRaii.PushColor(ImGuiCol.ButtonHovered, ColourUtil.RgbaToAbgr(0xFF7775FF)))
        using (ImRaii.PushColor(ImGuiCol.ButtonActive, ColourUtil.RgbaToAbgr(0xFF4542FF)))
        using (ImRaii.PushColor(ImGuiCol.Text, 0xFFFFFFFF))
        {
            var buttonWidth =
                ImGui.CalcTextSize(buttonLabel).X + ImGui.GetStyle().FramePadding.X * 2;
            var buttonWidth2 =
                ImGui.CalcTextSize(buttonLabel2).X + ImGui.GetStyle().FramePadding.X * 2;
            ImGui.SameLine(
                ImGui.GetContentRegionAvail().X
                    - buttonWidth
                    - buttonWidth2
                    - ImGui.GetStyle().ItemSpacing.X
            );

            if (ImGui.Button(buttonLabel2))
                Plugin.PlatformUtil.OpenLink("https://ko-fi.com/infiii");

            ImGui.SameLine();

            if (ImGui.Button(buttonLabel))
                Plugin.PlatformUtil.OpenLink("https://ko-fi.com/lojewalo");
        }

        if (!save)
            return;

        var hideChanged = !Mutable.HideChat && Mutable.HideChat != Plugin.Config.HideChat;
        var languageChanged = Mutable.LanguageOverride != Plugin.Config.LanguageOverride;

        // v1.5.3: Auto-enable the ExtraGlyphRanges flag matching the new
        // locale so non-Latin scripts render immediately. Without this,
        // a user switching to Korean would see "===" until they manually
        // tick the Korean range in Fonts & Colours.
        if (languageChanged)
        {
            var required = Mutable.LanguageOverride.RequiredGlyphRanges();
            if (required != 0)
                Mutable.ExtraGlyphRanges |= required;
        }

        var fontChanged =
            Mutable.GlobalFontV2 != Plugin.Config.GlobalFontV2
            || Mutable.JapaneseFontV2 != Plugin.Config.JapaneseFontV2
            || Mutable.ItalicFontV2 != Plugin.Config.ItalicFontV2
            || Mutable.ExtraGlyphRanges != Plugin.Config.ExtraGlyphRanges
            || Mutable.UseHellionFont != Plugin.Config.UseHellionFont;
        var fontSizeChanged =
            Math.Abs(Mutable.SymbolsFontSizeV2 - Plugin.Config.SymbolsFontSizeV2) > 0.001
            || Math.Abs(Mutable.FontSizeV2 - Plugin.Config.FontSizeV2) > 0.001;
        var italicStateChanged = Mutable.ItalicEnabled != Plugin.Config.ItalicEnabled;

        // Only refilter when filter-relevant settings changed. Clear+Refilter
        // reloads from the DB and silently drops in-session messages that
        // weren't persisted (Privacy-First blocks most channels). Cosmetic
        // changes (theme, icons, layout) skip the cycle.
        var filtersChanged = HasFilterRelevantChanges();

        Plugin.Config.UpdateFrom(Mutable, true);

        // Defer save by 60 frames to avoid committing changes that cause a crash.
        Plugin.DeferredSaveFrames = 60;
        if (filtersChanged)
        {
            Plugin.MessageManager.ClearAllTabs();
            Plugin.MessageManager.FilterAllTabsAsync();
        }

        if (fontChanged || fontSizeChanged || italicStateChanged)
            Plugin.FontManager.RebuildDelegateFonts();

        if (languageChanged)
            Plugin.LanguageChanged(Plugin.Interface.UiLanguage);

        if (hideChanged)
            GameFunctions.GameFunctions.SetChatInteractable(true);

        if (Plugin.Config.ShowEmotes)
            _ = EmoteCache.LoadData();

        Initialise();
    }

    // Returns true if any filter-relevant setting changed between Plugin.Config
    // and the Mutable copy. Gates Clear+Refilter on Save so cosmetic changes
    // don't wipe in-session chat history.
    private bool HasFilterRelevantChanges()
    {
        if (Mutable.PrivacyFilterEnabled != Plugin.Config.PrivacyFilterEnabled)
            return true;
        if (Mutable.PrivacyPersistUnknownChannels != Plugin.Config.PrivacyPersistUnknownChannels)
            return true;
        if (!Mutable.PrivacyPersistChannels.SetEquals(Plugin.Config.PrivacyPersistChannels))
            return true;

        // FilterIncludePreviousSessions changes the GetMostRecentMessages
        // window and is filter-relevant even outside the Privacy block.
        if (Mutable.FilterIncludePreviousSessions != Plugin.Config.FilterIncludePreviousSessions)
            return true;

        // Compare persistent tabs only -- TempTabs are never refiltered.
        var origPersistent = Plugin.Config.Tabs.Where(t => !t.IsTempTab).ToList();
        var newPersistent = Mutable.Tabs.Where(t => !t.IsTempTab).ToList();

        if (origPersistent.Count != newPersistent.Count)
            return true;

        for (var i = 0; i < origPersistent.Count; i++)
        {
            var orig = origPersistent[i];
            var neu = newPersistent[i];

            // Identifier mismatch means reorder or slot swap -- treat as filter-relevant.
            if (orig.Identifier != neu.Identifier)
                return true;

            if (orig.ExtraChatAll != neu.ExtraChatAll)
                return true;
            if (!orig.ExtraChatChannels.SetEquals(neu.ExtraChatChannels))
                return true;

            if (orig.SelectedChannels.Count != neu.SelectedChannels.Count)
                return true;
            foreach (var pair in orig.SelectedChannels)
            {
                if (!neu.SelectedChannels.TryGetValue(pair.Key, out var nv))
                    return true;
                if (!pair.Value.Equals(nv))
                    return true;
            }
        }

        return false;
    }
}
