using System.Collections.ObjectModel;
using System.Text;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ClaudeDo.Ui.Helpers;
using ClaudeDo.Ui.Localization;
using ClaudeDo.Ui.Services;

namespace ClaudeDo.Ui.ViewModels.Islands;

public sealed partial class PrepPanelViewModel : ViewModelBase, IDisposable
{
    private readonly IWorkerClient _worker;
    private readonly StreamLineFormatter _formatter = new();
    private readonly StringBuilder _prepClaudeBuf = new();

    private readonly Action _onPrepStartedHandler;
    private readonly Action<string> _onPrepLineHandler;
    private readonly Action<bool> _onPrepFinishedHandler;

    [ObservableProperty] private bool _isPrepRunning;

    // Covers the gap between the click and the worker's PrepStartedEvent broadcast — IsPrepRunning
    // stays false until that event arrives, so without this the button click looks like a no-op.
    public OperationStatus PrepOperation { get; } = new();

    public ObservableCollection<LogLineViewModel> PrepLog { get; } = new();

    public bool ShowPrepEmptyState => !IsPrepRunning && PrepLog.Count == 0;

    // The long-running broadcast-driven state (IsPrepRunning) and the click-to-event gap
    // (PrepOperation.IsRunning) both need to block a second click.
    public bool IsPlanDayEnabled => !IsPrepRunning && !PrepOperation.IsRunning;

    partial void OnIsPrepRunningChanged(bool value)
    {
        OnPropertyChanged(nameof(ShowPrepEmptyState));
        OnPropertyChanged(nameof(IsPlanDayEnabled));
    }

    public PrepPanelViewModel(IWorkerClient worker)
    {
        _worker = worker;
        _onPrepStartedHandler = OnPrepStarted;
        _onPrepLineHandler = OnPrepLine;
        _onPrepFinishedHandler = OnPrepFinished;

        _worker.PrepStartedEvent += _onPrepStartedHandler;
        _worker.PrepLineEvent += _onPrepLineHandler;
        _worker.PrepFinishedEvent += _onPrepFinishedHandler;

        PrepLog.CollectionChanged += (_, _) => OnPropertyChanged(nameof(ShowPrepEmptyState));

        // OperationStatus is a nested ObservableObject — [NotifyCanExecuteChangedFor] can't see
        // into it, so IsRunning changes are relayed by hand.
        PrepOperation.PropertyChanged += (_, e) =>
        {
            if (e.PropertyName != nameof(OperationStatus.IsRunning)) return;
            PlanDayCommand.NotifyCanExecuteChanged();
            OnPropertyChanged(nameof(IsPlanDayEnabled));
        };
    }

    public void Dispose()
    {
        _worker.PrepStartedEvent -= _onPrepStartedHandler;
        _worker.PrepLineEvent -= _onPrepLineHandler;
        _worker.PrepFinishedEvent -= _onPrepFinishedHandler;
    }

    private bool CanPlanDay() => IsPlanDayEnabled;

    [RelayCommand(CanExecute = nameof(CanPlanDay))]
    private async System.Threading.Tasks.Task PlanDayAsync()
    {
        using var op = PrepOperation.Begin(Loc.T("ops.reports.runningDailyPrep"));
        try { await _worker.RunDailyPrepNowAsync(); }
        catch { }
    }

    public async System.Threading.Tasks.Task LoadLastPrepLogIfEmptyAsync()
    {
        if (IsPrepRunning || PrepLog.Count > 0) return;
        string text;
        try { text = await _worker.GetLastPrepLogAsync(); }
        catch { return; }
        if (IsPrepRunning || PrepLog.Count > 0) return;
        foreach (var line in text.Split('\n'))
        {
            var trimmed = line.TrimEnd('\r');
            if (trimmed.Length > 0) AppendStdoutLine(trimmed);
        }
    }

    private void OnPrepStarted()
    {
        PrepLog.Clear();
        IsPrepRunning = true;
    }

    private void OnPrepLine(string line) => AppendStdoutLine(line);

    private void OnPrepFinished(bool success) => IsPrepRunning = false;

    private void AppendStdoutLine(string line)
    {
        var formatted = _formatter.FormatLine(line);
        if (formatted is null) return;
        AppendClaudeText(formatted);
    }

    private void AppendClaudeText(string chunk)
    {
        _prepClaudeBuf.Append(chunk);
        while (true)
        {
            var text = _prepClaudeBuf.ToString();
            var nl = text.IndexOf('\n');
            if (nl < 0) break;
            var piece = text[..nl].TrimEnd('\r');
            if (!string.IsNullOrWhiteSpace(piece))
                PrepLog.Add(new LogLineViewModel { Kind = LogKind.Claude, Text = piece });
            _prepClaudeBuf.Clear();
            _prepClaudeBuf.Append(text[(nl + 1)..]);
        }
    }
}
