using System.Collections.ObjectModel;
using Avalonia.Threading;
using ClawdDotNet.App;
using ClawdDotNet.Core.Storage;
using ClawdDotNet.Desktop.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

// Ohne Alias verweist "App" hier auf ClawdDotNet.Desktop.App, die Anwendungsklasse
// von Avalonia — und nicht auf die gleichnamige Fachschicht.
using AppSettings = ClawdDotNet.App.Settings.AppSettings;

namespace ClawdDotNet.Desktop.ViewModels;

/// <summary>
/// Das Gerüst des Hauptfensters: Seitenleiste, Kopfzeile, Meldungen, Statusleiste.
///
/// <para>Die Menüleiste ist entfallen. Der Entwurf kennt keine, und ihre Einträge hatten
/// ohnehin überall sonst schon einen Platz: Der Instanzwechsel sitzt jetzt als eigener
/// Schalter oben in der Seitenleiste, die Ordner-Befehle stehen dort, wo die Ordner
/// gebraucht werden (Logs-Seite, Info-Seite), und „Ansicht" war eine zweite Navigation
/// neben der Navigation.</para>
/// </summary>
public sealed partial class MainWindowViewModel : ViewModelBase
{
    private readonly AppHost? _host;

    /// <summary>Breite der Seitenleiste, ausgeklappt und eingeklappt (siehe Entwurf).</summary>
    private const double SidebarWidthExpanded = 240;
    private const double SidebarWidthCollapsed = 64;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(PageTitle))]
    private PageViewModel? _selectedPage;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(SidebarWidth))]
    [NotifyPropertyChangedFor(nameof(IsSidebarExpanded))]
    private bool _isSidebarCollapsed;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(ThemeLabel))]
    [NotifyPropertyChangedFor(nameof(ThemeIconKey))]
    private bool _isDarkTheme;

    // ─── Statusleiste ───

    [ObservableProperty]
    private string _instanceName = "—";

    [ObservableProperty]
    private bool _isApiReachable;

    [ObservableProperty]
    private string _apiStatusText = "unbekannt";

    /// <summary>Kurzinfo zu Tokens und Kosten der letzten Stunde bzw. 24 Stunden.</summary>
    [ObservableProperty]
    private string _usageText = "—";

    [ObservableProperty]
    private string _usageTooltip = "";

    [ObservableProperty]
    private string _creditsText = "—";

    /// <summary>Alle Bereiche, in der Reihenfolge der Navigation.</summary>
    public ObservableCollection<PageViewModel> Pages { get; } = [];

    /// <summary>Die Navigation, gruppiert wie im Entwurf.</summary>
    public ObservableCollection<NavGroupViewModel> NavGroups { get; } = [];

    /// <summary>
    /// Instanzübergreifende Hinweise. Bleibt vorerst leer — der Dienst, der Fehler und
    /// aufgebrauchte Guthaben <em>anderer</em> Instanzen sieht, existiert noch nicht
    /// (siehe Implementierungsleitfaden im Mockup-Verzeichnis). Die Leiste blendet sich
    /// aus, solange nichts anliegt, und ist damit heute schon unauffällig richtig.
    /// </summary>
    public ObservableCollection<NotificationViewModel> Notifications { get; } = [];

    public bool HasNotifications => Notifications.Count > 0;

    public double SidebarWidth => IsSidebarCollapsed ? SidebarWidthCollapsed : SidebarWidthExpanded;

    public bool IsSidebarExpanded => !IsSidebarCollapsed;

    public string PageTitle => SelectedPage?.Title ?? "";

    public string ThemeLabel => IsDarkTheme ? "Dunkelmodus" : "Hellmodus";

    /// <summary>Fusszeile der Seitenleiste.</summary>
    public string VersionText => $"ClawdDotNet {AppHost.AppVersion} · Avalonia";

    /// <summary>Sonne im Hellmodus, Mond im Dunkelmodus — beides Namen aus Icons.axaml.</summary>
    public string ThemeIconKey => IsDarkTheme ? "IconMoon" : "IconSun";

    public event Action? RequestExit;
    public event Action? RequestInstanceManagerDialog;

    /// <summary>Für den Entwurfsmodus des Editors — ohne laufenden Aufbau.</summary>
    public MainWindowViewModel() : this(null) { }

    public MainWindowViewModel(AppHost? host)
    {
        _host = host;

        var chat = new ChatPageViewModel(host);
        var agents = new AgentsPageViewModel(host);
        var tasks = new TasksPageViewModel(host);
        var tokens = new TokenUsagePageViewModel(host);
        var agentChats = new AgentChatsPageViewModel(host);
        var backup = new BackupPageViewModel(host);
        var logs = new LogPageViewModel(host);
        var settings = new SettingsPageViewModel(host);
        var info = new InfoPageViewModel(host);

        foreach (var page in new PageViewModel[]
                 { chat, agents, tasks, tokens, agentChats, backup, logs, settings, info })
            Pages.Add(page);

        NavGroups.Add(new NavGroupViewModel("Arbeiten",
        [
            new NavItemViewModel(this, chat, "IconChat"),
            new NavItemViewModel(this, agents, "IconAgents"),
            new NavItemViewModel(this, tasks, "IconTasks")
        ]));

        NavGroups.Add(new NavGroupViewModel("Analyse",
        [
            new NavItemViewModel(this, tokens, "IconChart"),
            new NavItemViewModel(this, agentChats, "IconAgentChats")
        ]));

        NavGroups.Add(new NavGroupViewModel("System",
        [
            new NavItemViewModel(this, backup, "IconBackup"),
            new NavItemViewModel(this, logs, "IconLogs"),
            new NavItemViewModel(this, settings, "IconSettings"),
            new NavItemViewModel(this, info, "IconInfo")
        ]));

        // Die Leiste blendet sich aus, solange nichts anliegt — dafür muss die Ansicht
        // von Zu- und Abgängen erfahren, nicht nur von der Sammlung selbst.
        Notifications.CollectionChanged += (_, _) => OnPropertyChanged(nameof(HasNotifications));

        SelectedPage = chat;

        if (host is null)
        {
            InstanceName = "Entwurfsmodus";
            ApiStatusText = "Entwurf";
            UsageText = "1h: 3.240 Tok · 24h: 168.500 Tok";
            CreditsText = "Credits: $41.86 von $100.00";

            // Zwei Beispielmeldungen, damit die Leiste im Editor sichtbar ist.
            Notifications.Add(new NotificationViewModel(
                this, isError: true, "sandbox", "OpenRouter-Guthaben aufgebraucht — Agenten pausiert."));
            Notifications.Add(new NotificationViewModel(
                this, isError: false, "stock-team", "3 unbehandelte Exceptions in den letzten 15 Minuten."));
            return;
        }

        InstanceName = host.Instance.InstanceName;

        IsSidebarCollapsed = host.Settings.AppSettings.SidebarCollapsed;
        IsDarkTheme = ThemeManager.IsDarkName(host.Settings.AppSettings.Theme);
        ThemeManager.Apply(IsDarkTheme);
        ApplyCollapsedToNav();

        HookStatusService(host);
    }

    /// <summary>
    /// Hängt die Statusleiste an den OpenRouter-Dienst.
    ///
    /// <para>Der Dienst war fertig und ungenutzt — er lieferte seine Texte bislang an
    /// niemanden. <c>OnStatusUpdated</c> kommt vom Hintergrundfaden des
    /// <c>PeriodicTimer</c>, deshalb der Wechsel auf den Oberflächenfaden.</para>
    /// </summary>
    private void HookStatusService(AppHost host)
    {
        if (host.Status is not { } status)
        {
            ApiStatusText = "kein API-Key";
            UsageText = "keine Nutzungsdaten";
            CreditsText = "";
            return;
        }

        void Update()
        {
            IsApiReachable = status.IsApiReachable;
            ApiStatusText = status.IsApiReachable ? "OK" : "nicht erreichbar";
            UsageText = status.CreditsText;
            UsageTooltip = status.CreditsTooltip;

            CreditsText = status.CreditRemaining is { } remaining && status.CreditBalance is { } total
                ? $"Credits: ${remaining:F2} von ${total:F2}"
                : "Credits: —";
        }

        status.OnStatusUpdated += () => Dispatcher.UIThread.Post(Update);
        Update();
    }

    partial void OnSelectedPageChanged(PageViewModel? value)
    {
        foreach (var item in NavGroups.SelectMany(g => g.Items))
            item.IsSelected = ReferenceEquals(item.Page, value);
    }

    partial void OnIsSidebarCollapsedChanged(bool value)
    {
        ApplyCollapsedToNav();
        Persist(s => s.SidebarCollapsed = value);
    }

    partial void OnIsDarkThemeChanged(bool value)
    {
        ThemeManager.Apply(value);
        Persist(s => s.Theme = ThemeManager.ToName(value));
    }

    /// <summary>
    /// Der eingeklappte Zustand steht an jedem Eintrag statt an einer Bindung auf das
    /// Elternfenster: Aus einer DataTemplate heraus zurück auf das Fenster zu binden ist
    /// mit übersetzten Bindungen jedes Mal ein Ausdruck, den niemand mehr liest.
    /// </summary>
    private void ApplyCollapsedToNav()
    {
        foreach (var group in NavGroups)
        {
            group.IsTitleVisible = !IsSidebarCollapsed;

            foreach (var item in group.Items)
                item.IsCollapsed = IsSidebarCollapsed;
        }
    }

    /// <summary>
    /// Einstellungen sind ein Nebeneffekt der Bedienung, kein eigener Vorgang: Wer die
    /// Leiste einklappt, drückt nicht anschließend auf Speichern. Ein Schreibfehler darf
    /// die Bedienung deshalb auch nicht unterbrechen.
    /// </summary>
    private void Persist(Action<AppSettings> change)
    {
        if (_host is null) return;

        try
        {
            change(_host.Settings.AppSettings);
            _host.Settings.Save();
        }
        catch (Exception)
        {
            // Der Zustand gilt für diese Sitzung trotzdem — nur eben nicht für die nächste.
        }
    }

    [RelayCommand]
    private void SelectPage(PageViewModel? page)
    {
        if (page is not null) SelectedPage = page;
    }

    [RelayCommand]
    private void ToggleSidebar() => IsSidebarCollapsed = !IsSidebarCollapsed;

    [RelayCommand]
    private void ToggleTheme() => IsDarkTheme = !IsDarkTheme;

    [RelayCommand]
    private void OpenInstanceFolder()
    {
        if (_host is null) return;
        SystemShell.OpenFolder(_host.InstancePath);
    }

    [RelayCommand]
    private void OpenInstanceManager() => RequestInstanceManagerDialog?.Invoke();

    [RelayCommand]
    private void Exit() => RequestExit?.Invoke();
}

/// <summary>Eine Gruppe der Navigation („Arbeiten", „Analyse", „System").</summary>
public sealed partial class NavGroupViewModel(string title, IReadOnlyList<NavItemViewModel> items)
    : ObservableObject
{
    public string Title { get; } = title;
    public IReadOnlyList<NavItemViewModel> Items { get; } = items;

    /// <summary>Im eingeklappten Zustand entfällt die Gruppenüberschrift.</summary>
    [ObservableProperty]
    private bool _isTitleVisible = true;
}

/// <summary>
/// Ein Eintrag der Navigation.
///
/// <para>Der Befehl sitzt am Eintrag selbst und nicht am Fenster: Aus einer DataTemplate
/// heraus über <c>$parent[…]</c> auf einen Befehl des Fensters zu binden funktioniert
/// zwar, ist aber bei übersetzten Bindungen ein Ausdruck mit Typumwandlung, den man beim
/// nächsten Lesen erst wieder entziffern muss.</para>
/// </summary>
public sealed partial class NavItemViewModel(
    MainWindowViewModel owner, PageViewModel page, string iconKey) : ObservableObject
{
    public PageViewModel Page { get; } = page;
    public string Title => Page.Title;

    /// <summary>Name der Geometrie in <c>Styles/Icons.axaml</c>.</summary>
    public string IconKey { get; } = iconKey;

    [ObservableProperty]
    private bool _isSelected;

    [ObservableProperty]
    private bool _isCollapsed;

    [RelayCommand]
    private void Select() => owner.SelectedPage = Page;
}

/// <summary>Eine Zeile der Benachrichtigungsleiste.</summary>
public sealed partial class NotificationViewModel(
    MainWindowViewModel owner, bool isError, string instanceName, string text) : ObservableObject
{
    public bool IsError { get; } = isError;
    public bool IsWarning => !IsError;
    public string InstanceName { get; } = instanceName;
    public string Text { get; } = text;

    [RelayCommand]
    private void Dismiss() => owner.Notifications.Remove(this);
}

/// <summary>Ein Bereich des Hauptfensters.</summary>
public abstract partial class PageViewModel(string title) : ViewModelBase
{
    public string Title { get; } = title;
}
