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

namespace ClaudeDo.Ui.ViewModels.Islands;

/// <summary>Date separator between the note groups. Rows carry these inline (like the task list's
/// HeaderRow) so one ItemsControl renders today and the history without a nested control per day.</summary>
public sealed class NoteDayHeaderViewModel
{
    public DateOnly Day { get; }
    public string Label { get; }
    public bool IsToday { get; }

    public NoteDayHeaderViewModel(DateOnly day, DateOnly today)
    {
        Day = day;
        IsToday = day == today;
        Label = day == today ? Loc.T("notes.today")
              : day == today.AddDays(-1) ? Loc.T("notes.yesterday")
              : day.ToString("dddd, dd.MM.");
    }
}

public sealed partial class NoteBulletViewModel : ViewModelBase
{
    /// <summary>Empty until the note is persisted — a row the user started but hasn't typed into
    /// yet exists only in the list, so an abandoned Enter leaves nothing behind in the database.</summary>
    public string Id { get; set; }

    public DateOnly Day { get; }
    public bool FromAgent { get; }
    public bool IsNew => Id.Length == 0;

    [ObservableProperty] private string _text;

    public NoteBulletViewModel(string id, DateOnly day, string text, bool fromAgent = false)
    {
        Id = id;
        Day = day;
        _text = text;
        FromAgent = fromAgent;
    }
}

public sealed partial class NotesEditorViewModel : ViewModelBase
{
    /// <summary>How far the history scrolls back. Older notes stay in the database and in the week
    /// report; they are simply not worth the round trip in a day log.</summary>
    public const int HistoryDays = 14;

    private readonly INotesApi _api;

    public NotesEditorViewModel(INotesApi api) => _api = api;

    // Raised when a worker call fails so the host VM can surface it (e.g. via ShowErrorAsync).
    public event Action<string>? ErrorReported;

    /// <summary>Raised after a row is inserted so the view can move the caret into it.</summary>
    public event Action<NoteBulletViewModel>? FocusRequested;

    /// <summary>NoteDayHeaderViewModel | NoteBulletViewModel, newest day first.</summary>
    public ObservableCollection<object> Rows { get; } = new();

    [ObservableProperty] private string _newBulletText = "";
    [ObservableProperty] private bool _isEmpty = true;

    public DateOnly Today { get; private set; } = DateOnly.FromDateTime(DateTime.Today);

    /// <summary>Loads today plus <see cref="HistoryDays"/> days of history.</summary>
    public async Task LoadAsync()
    {
        Today = DateOnly.FromDateTime(DateTime.Today);
        List<DailyNoteDto> notes;
        try
        {
            notes = await _api.ListBetweenAsync(Today.AddDays(-HistoryDays), Today);
        }
        catch (Exception ex)
        {
            ErrorReported?.Invoke(ex.Message);
            return;
        }

        Rows.Clear();
        // Today always gets a header, even with nothing under it — it is where typing lands.
        Rows.Add(new NoteDayHeaderViewModel(Today, Today));
        foreach (var n in notes.Where(n => Day(n) == Today).OrderBy(n => n.SortOrder))
            Rows.Add(Bullet(n));

        foreach (var group in notes.Where(n => Day(n) != Today)
                     .GroupBy(Day).OrderByDescending(g => g.Key))
        {
            Rows.Add(new NoteDayHeaderViewModel(group.Key, Today));
            foreach (var n in group.OrderBy(n => n.SortOrder))
                Rows.Add(Bullet(n));
        }

        RefreshEmpty();
    }

    private static DateOnly Day(DailyNoteDto n) => DateOnly.Parse(n.Date);

    private static NoteBulletViewModel Bullet(DailyNoteDto n) =>
        new(n.Id, Day(n), n.Text, n.FromAgent);

    private void RefreshEmpty() => IsEmpty = !Rows.OfType<NoteBulletViewModel>().Any();

    /// <summary>The box above the list: Enter files the note under today and keeps the box focused,
    /// so a burst of notes is one uninterrupted stream of typing.</summary>
    [RelayCommand]
    private async Task AddBullet()
    {
        var text = NewBulletText.Trim();
        if (text.Length == 0) return;
        try
        {
            var dto = await _api.AddAsync(Today, text);
            if (dto is not null) Rows.Insert(1, Bullet(dto));
            NewBulletText = "";
            RefreshEmpty();
        }
        catch (Exception ex)
        {
            ErrorReported?.Invoke(ex.Message);
        }
    }

    /// <summary>Enter inside a note: save it, then open an empty row underneath and focus it.</summary>
    [RelayCommand]
    private async Task SplitBullet(NoteBulletViewModel? b)
    {
        if (b is null) return;
        await CommitBullet(b);
        if (!Rows.Contains(b)) return;   // committing an emptied note removed it

        var fresh = new NoteBulletViewModel("", b.Day, "");
        Rows.Insert(Rows.IndexOf(b) + 1, fresh);
        FocusRequested?.Invoke(fresh);
    }

    /// <summary>Backspace in an already-empty note: drop it and put the caret in the one above.</summary>
    [RelayCommand]
    private async Task DeleteBullet(NoteBulletViewModel? b)
    {
        if (b is null) return;
        var index = Rows.IndexOf(b);
        await RemoveAsync(b);

        for (var i = index - 1; i >= 0; i--)
            if (Rows[i] is NoteBulletViewModel previous)
            {
                FocusRequested?.Invoke(previous);
                return;
            }
    }

    [RelayCommand]
    private async Task CommitBullet(NoteBulletViewModel? b)
    {
        if (b is null) return;
        var text = b.Text?.Trim() ?? "";
        try
        {
            if (text.Length == 0)
            {
                await RemoveAsync(b);
                return;
            }

            if (b.IsNew)
            {
                var dto = await _api.AddAsync(b.Day, text);
                if (dto is not null) b.Id = dto.Id;
            }
            else
            {
                await _api.UpdateAsync(b.Id, text);
            }
        }
        catch (Exception ex)
        {
            ErrorReported?.Invoke(ex.Message);
        }
    }

    private async Task RemoveAsync(NoteBulletViewModel b)
    {
        if (!b.IsNew) await _api.DeleteAsync(b.Id);
        Rows.Remove(b);
        RefreshEmpty();
    }
}
