using System.Collections.ObjectModel;
using ClaudeDo.Data.Models;
using ClaudeDo.Localization;
using ClaudeDo.Ui.Localization;
using ClaudeDo.Ui.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace ClaudeDo.Ui.ViewModels.Modals.Settings;

public sealed partial class GeneralSettingsTabViewModel : ViewModelBase
{
    private readonly ILocalizer? _localizer;
    private readonly Action<string>? _persist;

    [ObservableProperty] private string _defaultClaudeInstructions = "";
    [ObservableProperty] private string _defaultModel = ModelRegistry.DefaultAlias;
    [ObservableProperty] private int _defaultMaxTurns = 40;
    // Hard ceiling every resolved max-turns value is clamped to before a run starts. Drives the
    // clamp hints on the preset rows below and on AgentConfigEditorViewModel.
    [ObservableProperty] private int _maxTurnsCeiling = 80;
    [ObservableProperty] private string _defaultPermissionMode = PermissionModeRegistry.DefaultMode;
    [ObservableProperty] private int _maxParallelExecutions = 1;
    // Percentage of the 5h/7d Claude usage window at which the autonomous queue pauses. 0 = gate off.
    [ObservableProperty] private int _usageGateFiveHourPct = 80;
    [ObservableProperty] private int _usageGateSevenDayPct = 90;
    // Off by default: auto-continues a usage-limit-failed task once the 5h window resets, and
    // wakes a gate-blocked queue exactly at that reset time instead of only the 30s backstop.
    [ObservableProperty] private bool _autoContinueOnUsageLimit;
    // Newline-separated path prefixes excluded from the weekly report.
    [ObservableProperty] private string _reportExcludedPaths = @"C:\Private";
    // 0=Sunday..6=Saturday (System.DayOfWeek); default Wednesday.
    [ObservableProperty] private int _standupWeekday = (int)DayOfWeek.Wednesday;

    public IReadOnlyList<string> Models { get; } = ModelRegistry.Aliases;
    public IReadOnlyList<string> PermissionModes { get; } = PermissionModeRegistry.Modes;

    public ObservableCollection<AccentPresetSwatchViewModel> AccentPresetSwatches { get; } = new();
    private Action<string>? _persistAccent;

    public void InitAccentPresets(string saved, Action<string> persist)
    {
        _persistAccent = persist;
        var current = AccentPresets.Find(saved);
        AccentPresetSwatches.Clear();
        foreach (var p in AccentPresets.All)
            AccentPresetSwatches.Add(new AccentPresetSwatchViewModel(p, p.Name == current.Name));
    }

    [RelayCommand]
    private void SelectAccentPreset(AccentPreset preset)
    {
        foreach (var s in AccentPresetSwatches)
            s.IsSelected = s.Preset.Name == preset.Name;
        AccentPresetService.Apply(preset);
        _persistAccent?.Invoke(preset.Name);
    }

    /// <summary>One editable row per model alias: the effort and turn budget a run gets under that
    /// model. Supplies the global defaults; list- and task-level max-turns overrides still win.</summary>
    public ObservableCollection<ModelPresetRowViewModel> ModelPresets { get; } = new();

    public void LoadModelPresets(IReadOnlyCollection<ModelPresetDto>? presets, int ceiling)
    {
        ModelPresets.Clear();
        var source = presets is { Count: > 0 }
            ? presets.Select(p => new ModelPreset(p.Model, p.Effort, p.MaxTurns)).ToList()
            : Data.Models.ModelPresets.Defaults.ToList();
        foreach (var p in Data.Models.ModelPresets.Parse(Data.Models.ModelPresets.Serialize(source)))
            ModelPresets.Add(new ModelPresetRowViewModel(p, ceiling));
    }

    public List<ModelPresetDto> ModelPresetDtos()
        => ModelPresets.Select(r => new ModelPresetDto(r.Model, r.Effort, (int)r.MaxTurns)).ToList();

    public GeneralSettingsTabViewModel() { }

    public GeneralSettingsTabViewModel(ILocalizer localizer, Action<string> persist)
    {
        _localizer = localizer;
        _persist = persist;
        Languages = localizer.AvailableLanguages;
        _selectedLanguage = Languages.FirstOrDefault(l => l.Code == localizer.CurrentCode);
    }

    public IReadOnlyList<LanguageOption> Languages { get; } = Array.Empty<LanguageOption>();

    [ObservableProperty] private LanguageOption? _selectedLanguage;

    partial void OnSelectedLanguageChanged(LanguageOption? value)
    {
        if (value is null || _localizer is null) return;
        _localizer.SetLanguage(value.Value.Code);
        _persist?.Invoke(value.Value.Code);
    }

    public string? Validate()
    {
        if (MaxTurnsCeiling < 1)
            return Loc.T("settings.general.maxTurnsCeilingInvalid");
        if (MaxParallelExecutions < 1 || MaxParallelExecutions > 20)
            return "Max parallel executions must be between 1 and 20.";
        if (UsageGateFiveHourPct < 0 || UsageGateFiveHourPct > 100)
            return "Usage gate (5h) must be between 0 and 100.";
        if (UsageGateSevenDayPct < 0 || UsageGateSevenDayPct > 100)
            return "Usage gate (7d) must be between 0 and 100.";
        foreach (var row in ModelPresets)
            if (row.MaxTurns is < 1 or > 200)
                return $"Max turns for {row.Model} must be between 1 and 200.";
        return null;
    }
}

/// <summary>One row of the per-model preset table. Single consumer, so it lives here.</summary>
public sealed partial class ModelPresetRowViewModel : ViewModelBase
{
    public string Model { get; }
    public IReadOnlyList<string> EffortLevels { get; } = EffortRegistry.Levels;

    [ObservableProperty] private string _effort;
    // decimal so it binds straight to a NumericUpDown, like the other numeric settings.
    [ObservableProperty] private decimal _maxTurns;
    [ObservableProperty] private int _ceiling;

    public ModelPresetRowViewModel(ModelPreset preset, int ceiling)
    {
        Model = preset.Model;
        _effort = preset.Effort;
        _maxTurns = preset.MaxTurns;
        _ceiling = ceiling;
    }

    public string ClampHint => MaxTurns > Ceiling
        ? Loc.T("settings.turnsCeilingHint", Ceiling)
        : "";

    partial void OnMaxTurnsChanged(decimal value) => OnPropertyChanged(nameof(ClampHint));
    partial void OnCeilingChanged(int value) => OnPropertyChanged(nameof(ClampHint));
}
