using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform.Storage;
using Avalonia.Reactive;
using ClaudeDo.Ui.ViewModels.Islands;
using ClaudeDo.Ui.Views.Modals;

namespace ClaudeDo.Ui.Views.Islands;

public partial class DetailsIslandView : UserControl
{
    // Per-task description height (pixels) once the user drags the splitter.
    // Keyed by task id so each task keeps its own resize; tasks that were
    // never dragged stay dynamic (Auto-sized description).
    private readonly Dictionary<string, double> _descriptionHeights = new();
    private DetailsIslandViewModel? _vm;
    private DiffViewerView? _diffViewerWindow;

    public DetailsIslandView()
    {
        InitializeComponent();
        DataContextChanged += OnDataContextChanged;
        // Keep the row limits proportional to the island height: description
        // capped at 2/3, console floored at 1/3. The GridSplitter honours these
        // row Min/Max during a drag, so the console stops shrinking at 1/3.
        DetailBodyGrid.GetObservable(BoundsProperty)
            .Subscribe(new AnonymousObserver<Rect>(_ => UpdateRowLimits()));

        AddHandler(DragDrop.DragEnterEvent, OnDragEnter);
        AddHandler(DragDrop.DragOverEvent, OnDragOver);
        AddHandler(DragDrop.DragLeaveEvent, OnDragLeave);
        AddHandler(DragDrop.DropEvent, OnDrop);
    }

    private static bool IsFilesDrop(DragEventArgs e)
        => e.DataTransfer?.Contains(DataFormat.File) == true;

    private void OnDragEnter(object? sender, DragEventArgs e)
    {
        if (_vm is { CanAcceptDrop: true } && IsFilesDrop(e))
        {
            e.DragEffects = DragDropEffects.Copy;
            _vm.IsDragOver = true;
        }
        else
        {
            e.DragEffects = DragDropEffects.None;
        }
        e.Handled = true;
    }

    private void OnDragOver(object? sender, DragEventArgs e)
    {
        if (_vm is { CanAcceptDrop: true } && IsFilesDrop(e))
        {
            e.DragEffects = DragDropEffects.Copy;
            _vm.IsDragOver = true;
        }
        else
        {
            e.DragEffects = DragDropEffects.None;
        }
        e.Handled = true;
    }

    private void OnDragLeave(object? sender, RoutedEventArgs e)
    {
        if (_vm != null) _vm.IsDragOver = false;
    }

    private async void OnDrop(object? sender, DragEventArgs e)
    {
        if (_vm != null) _vm.IsDragOver = false;
        if (_vm is not { CanAcceptDrop: true } || !IsFilesDrop(e)) return;
        e.Handled = true;

        var items = e.DataTransfer.TryGetFiles();
        if (items is null) return;

        var files = new List<(string FileName, System.IO.Stream Content)>();
        try
        {
            foreach (var item in items)
            {
                if (item is IStorageFile sf)
                {
                    var stream = await sf.OpenReadAsync();
                    files.Add((sf.Name, stream));
                }
            }
        }
        catch (Exception ex)
        {
            // Opening the dropped stream can fail (shell IO, file lock) before AddFilesAsync
            // ever runs; surface it with the exception type so the next occurrence is analyzable
            // instead of vanishing as an unobserved async-void fault.
            _vm.DropStatus = string.Format(
                ClaudeDo.Ui.Localization.Loc.T("details.attachments.dropReadError"),
                $"{ex.GetType().Name}: {ex.Message}");
            foreach (var (_, s) in files) await s.DisposeAsync();
            return;
        }

        if (files.Count == 0) return;

        try
        {
            await _vm.AddFilesAsync(files);
        }
        finally
        {
            foreach (var (_, s) in files) await s.DisposeAsync();
        }
    }

    private void UpdateRowLimits()
    {
        var h = DetailBodyGrid.Bounds.Height;
        if (h <= 0) return;
        DetailBodyGrid.RowDefinitions[0].MaxHeight = h * 2.0 / 3.0;
        DetailBodyGrid.RowDefinitions[1].MinHeight = h / 3.0;
        // The description sits in an Auto row, which measures its cell with
        // infinite height — so the card's inner ScrollViewer thinks everything
        // fits and never scrolls. Bounding the card itself gives that
        // ScrollViewer a finite measure constraint so it engages once the
        // content exceeds 2/3 of the island. (RowDefinition.MaxHeight above only
        // clamps the drag and the final row height, not the measure constraint.)
        DescriptionCard.MaxHeight = h * 2.0 / 3.0;
    }

    private void OnDataContextChanged(object? sender, EventArgs e)
    {
        if (_vm != null)
            _vm.PropertyChanged -= OnViewModelPropertyChanged;

        if (DataContext is DetailsIslandViewModel vm)
        {
            _vm = vm;
            vm.PropertyChanged += OnViewModelPropertyChanged;
            ApplyResizeStateForCurrentTask();

            // Non-modal: the diff viewer must not block navigating the rest of the app.
            // Reuse the one open window (swap its DataContext + bring to front) instead of
            // stacking a second one when the user re-opens the diff for another task.
            vm.Merge.ShowDiffViewer = (diffVm) =>
            {
                var owner = TopLevel.GetTopLevel(this) as Window;
                if (owner == null) return System.Threading.Tasks.Task.CompletedTask;
                if (_diffViewerWindow is { } existing)
                {
                    existing.DataContext = diffVm;
                    if (existing.WindowState == WindowState.Minimized)
                        existing.WindowState = WindowState.Normal;
                    existing.Activate();
                    return System.Threading.Tasks.Task.CompletedTask;
                }
                var window = new DiffViewerView { DataContext = diffVm };
                window.Closed += (_, _) => _diffViewerWindow = null;
                _diffViewerWindow = window;
                window.Show(owner);
                return System.Threading.Tasks.Task.CompletedTask;
            };

            vm.Merge.ShowMergeModal = async (mergeVm) =>
            {
                var owner = TopLevel.GetTopLevel(this) as Window;
                if (owner == null) return;
                var modal = new MergeModalView { DataContext = mergeVm };
                await modal.ShowDialog(owner);
            };

            vm.ConfirmAsync = ShowConfirmAsync;
            vm.ShowErrorAsync = ShowErrorDialogAsync;
        }
    }

    // Restores the resize state for the currently-selected task: a task the
    // user has dragged before gets its pinned pixel height (cap lifted); a task
    // never dragged falls back to dynamic sizing (Auto row + the bound cap).
    private void OnViewModelPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if (e.PropertyName == nameof(DetailsIslandViewModel.Task))
            ApplyResizeStateForCurrentTask();
    }

    private void ApplyResizeStateForCurrentTask()
    {
        // A task dragged before keeps its pixel height (clamped by the row's
        // 2/3 MaxHeight); a task never dragged stays Auto-sized.
        DetailBodyGrid.RowDefinitions[0].Height = _vm?.Task?.Id is string id && _descriptionHeights.TryGetValue(id, out var h)
            ? new GridLength(h, GridUnitType.Pixel)
            : GridLength.Auto;
    }

    // Pin the (until now Auto-sized) description row to its current pixel
    // height so the splitter resizes smoothly from there.
    private void OnSplitterDragStarted(object? sender, VectorEventArgs e)
    {
        var descRow = DetailBodyGrid.RowDefinitions[0];
        if (descRow.Height.IsAuto)
            descRow.Height = new GridLength(DescriptionCard.Bounds.Height, GridUnitType.Pixel);
    }

    // Remember the dragged height for this task so switching tasks keeps each
    // task's resize independent.
    private void OnSplitterDragCompleted(object? sender, VectorEventArgs e)
    {
        if (_vm?.Task?.Id is string id)
            _descriptionHeights[id] = DetailBodyGrid.RowDefinitions[0].Height.Value;
    }

    private async System.Threading.Tasks.Task ShowErrorDialogAsync(string message)
    {
        var owner = TopLevel.GetTopLevel(this) as Window;
        if (owner == null) return;

        var ok = new Button { Content = "OK", MinWidth = 90 };

        var dialog = new Window
        {
            Title = "Error",
            Width = 360,
            SizeToContent = SizeToContent.Height,
            CanResize = false,
            WindowStartupLocation = WindowStartupLocation.CenterOwner,
            ShowInTaskbar = false,
            Background = this.FindResource("SurfaceBrush") as IBrush,
            Content = new StackPanel
            {
                Spacing = 16,
                Margin = new Thickness(20),
                Children =
                {
                    new TextBlock { Text = message, TextWrapping = TextWrapping.Wrap },
                    new StackPanel
                    {
                        Orientation = Orientation.Horizontal,
                        Spacing = 8,
                        HorizontalAlignment = HorizontalAlignment.Right,
                        Children = { ok }
                    }
                }
            }
        };

        ok.Click += (_, _) => dialog.Close();

        await dialog.ShowDialog(owner);
    }

    private async System.Threading.Tasks.Task<bool> ShowConfirmAsync(string message)
    {
        var owner = TopLevel.GetTopLevel(this) as Window;
        if (owner == null) return false;

        var tcs = new TaskCompletionSource<bool>();

        var cancel = new Button { Content = "Cancel", MinWidth = 90 };
        var confirm = new Button { Content = "Delete", MinWidth = 90, Classes = { "danger" } };

        var dialog = new Window
        {
            Title = "Confirm",
            Width = 360,
            SizeToContent = SizeToContent.Height,
            CanResize = false,
            WindowStartupLocation = WindowStartupLocation.CenterOwner,
            ShowInTaskbar = false,
            Background = this.FindResource("SurfaceBrush") as IBrush,
            Content = new StackPanel
            {
                Spacing = 16,
                Margin = new Thickness(20),
                Children =
                {
                    new TextBlock { Text = message, TextWrapping = TextWrapping.Wrap },
                    new StackPanel
                    {
                        Orientation = Orientation.Horizontal,
                        Spacing = 8,
                        HorizontalAlignment = HorizontalAlignment.Right,
                        Children = { cancel, confirm }
                    }
                }
            }
        };

        cancel.Click += (_, _) => { tcs.TrySetResult(false); dialog.Close(); };
        confirm.Click += (_, _) => { tcs.TrySetResult(true); dialog.Close(); };
        dialog.Closed += (_, _) => tcs.TrySetResult(false);

        _ = dialog.ShowDialog(owner);
        return await tcs.Task;
    }
}
