using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Threading;
using Avalonia.VisualTree;
using ClaudeDo.Ui.ViewModels;
using ClaudeDo.Ui.ViewModels.Islands;
using ClaudeDo.Ui.ViewModels.Modals;
using ClaudeDo.Ui.Views.Controls;
using ClaudeDo.Ui.Views.MissionControl;
using ClaudeDo.Ui.Views.Modals;

namespace ClaudeDo.Ui.Views.Islands;

public partial class TasksIslandView : UserControl
{
    private readonly TaskDragController _drag = new();

    // Custom-drag gesture state. The drag is ARMED on press and BEGINS once the pointer moves
    // past the threshold, so a plain click still selects the row.
    private const double DragThreshold = 4;
    private Point _pressPoint;
    private TaskRowViewModel? _pressRow;
    private Control? _pressControl;
    private bool _dragArmed;
    private bool _dragging;

    // The list row (in the Lists island) currently highlighted as a drop target while dragging.
    private ListNavItemViewModel? _hintedList;

    public TasksIslandView()
    {
        InitializeComponent();
        AddHandler(PointerPressedEvent, OnTunnelPointerPressed, RoutingStrategies.Tunnel);
        AddHandler(PointerMovedEvent, OnPointerMovedDrag, RoutingStrategies.Tunnel);
        AddHandler(PointerReleasedEvent, OnPointerReleasedDrag, RoutingStrategies.Tunnel);
        AddHandler(PointerCaptureLostEvent, OnPointerCaptureLost);
        DataContextChanged += (_, _) =>
        {
            if (DataContext is TasksIslandViewModel vm)
            {
                vm.FocusAddTaskRequested += (_, _) => AddTaskBox.Focus();
                vm.ShowUnfinishedPlanningModal = async (modalVm) =>
                {
                    var owner = TopLevel.GetTopLevel(this) as Window;
                    if (owner is null) { modalVm.CancelCommand.Execute(null); return; }
                    var modal = new UnfinishedPlanningModalView { DataContext = modalVm };
                    // Closing via the OS title-bar (if ever enabled) also resolves the TCS.
                    modal.Closed += (_, _) => modalVm.CancelCommand.Execute(null);
                    await modal.ShowDialog(owner);
                    // ShowDialog completes once the window is closed (CloseAction or OS close).
                };
                vm.ConfirmAsync = ShowConfirmAsync;
                vm.SelectionChanged += (_, _) => ScrollSelectedIntoView();
            }
        };
    }

    // Bring the selected row into view — a programmatic select (e.g. Mission Control's "Open in
    // app") can target a row that isn't currently realized. RowsListBox.ScrollIntoView is
    // virtualization-aware (it realizes the container as needed); a visual-tree search for an
    // existing Button, as this used to do, only finds rows already on screen.
    private void ScrollSelectedIntoView()
    {
        if (DataContext is not TasksIslandViewModel vm || vm.SelectedTask is not { } target) return;
        Dispatcher.UIThread.Post(() => RowsListBox.ScrollIntoView(target), DispatcherPriority.Background);
    }

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

        var tcs = new TaskCompletionSource<bool>();
        var cancel  = new Button { Content = "Cancel",  MinWidth = 90 };
        var confirm = new Button { Content = "Confirm", MinWidth = 90, Classes = { "danger" } };

        var dialog = new Window
        {
            Title = "Confirm",
            Width = 380,
            SizeToContent = SizeToContent.Height,
            CanResize = false,
            WindowStartupLocation = WindowStartupLocation.CenterOwner,
            ShowInTaskbar = false,
            Background = this.FindResource("SurfaceBrush") as IBrush,
            Content = new StackPanel
            {
                Margin  = new Thickness(20),
                Spacing = 16,
                Children =
                {
                    new TextBlock { Text = message, TextWrapping = TextWrapping.Wrap },
                    new StackPanel
                    {
                        Orientation = Orientation.Horizontal,
                        HorizontalAlignment = HorizontalAlignment.Right,
                        Spacing = 8,
                        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;
    }

    // ── Custom ghost drag ────────────────────────────────────────────────────
    // Replaces both the OLE DoDragDropAsync reorder and the OLE drop-to-queue path: a hand-built
    // drag (pointer capture + a transparent topmost ghost window) is the only way to get a
    // translucent follower that crosses from this window into the separate Mission Control window.

    private void OnTunnelPointerPressed(object? sender, PointerPressedEventArgs e)
    {
        ResetPressState();
        if (DataContext is not TasksIslandViewModel) return;
        if (e.Source is not Visual src) return;

        var button = src as Button ?? src.FindAncestorOfType<Button>();
        if (button?.DataContext is not TaskRowViewModel row) return;
        if (!e.GetCurrentPoint(button).Properties.IsLeftButtonPressed) return;

        // Select now so the details pane updates whether the gesture becomes a click or a drag.
        if (DataContext is TasksIslandViewModel vm) vm.SelectFrom(row, "pointer-press");

        // If the click landed on a nested Button (e.g. the done-toggle checkbox or star),
        // don't start a drag — that would capture the pointer and swallow the inner Click.
        var nestedInsideButton = button.Parent is Visual parentVisual
            && parentVisual.FindAncestorOfType<Button>() is not null;
        if (nestedInsideButton) return;

        // Running tasks can be neither reordered nor re-queued.
        if (row.IsRunning) return;

        // Arm the drag for ANY list kind so drag-to-queue works everywhere; reorder-on-drop is
        // still gated on CanReorder (user lists only).
        _pressPoint = e.GetPosition(this);
        _pressRow = row;
        _pressControl = button;
        _dragArmed = true;
    }

    private void OnPointerMovedDrag(object? sender, PointerEventArgs e)
    {
        if (!_dragArmed && !_dragging) return;
        if (TopLevel.GetTopLevel(this) is not { } topLevel) return;

        if (_dragArmed && !_dragging)
        {
            var p = e.GetPosition(this);
            if (Math.Abs(p.X - _pressPoint.X) < DragThreshold && Math.Abs(p.Y - _pressPoint.Y) < DragThreshold)
                return;
            BeginDrag(e, topLevel);
        }

        if (_dragging)
        {
            _drag.MoveTo(this.PointToScreen(e.GetPosition(this)));
            UpdateReorderHint(e, topLevel);
            UpdateListDropHint(e, topLevel);
        }
    }

    private void BeginDrag(PointerEventArgs e, TopLevel topLevel)
    {
        if (_pressControl is null || _pressRow is null) return;
        // Snapshot the row BEFORE applying the "grabbed" style so the ghost stays crisp.
        _drag.Begin(_pressControl, e.GetPosition(_pressControl), topLevel.RenderScaling);
        _pressRow.IsDragging = true;
        _dragging = true;
        e.Pointer.Capture(this);
    }

    private async void OnPointerReleasedDrag(object? sender, PointerReleasedEventArgs e)
    {
        if (!_dragArmed && !_dragging) return;

        var wasDragging = _dragging;
        var row = _pressRow;
        var topLevel = TopLevel.GetTopLevel(this);
        var screen = wasDragging && topLevel is not null
            ? this.PointToScreen(e.GetPosition(this))
            : default;

        EndDrag(e);

        if (!wasDragging || row is null || topLevel is null) return;

        // 1) Released over the Mission Control window → queue the task.
        if (MissionControlUnder(screen) is { } mc)
        {
            await mc.EnqueueTaskAsync(row.Id);
            return;
        }

        // 2) Released over another user list in the Lists island → move the task there.
        if (DataContext is TasksIslandViewModel vmMove
            && ListItemUnder(e, topLevel) is { } targetList
            && targetList.Kind == ListKind.User
            && targetList.Id != vmMove.CurrentListId)
        {
            await vmMove.MoveTaskToListAsync(row, targetList);
            return;
        }

        // 3) Released over another row in the same user list → reorder.
        if (DataContext is TasksIslandViewModel vm && vm.CanReorder)
        {
            var targetButton = RowButtonAt(e, topLevel);
            if (targetButton?.DataContext is TaskRowViewModel target
                && !ReferenceEquals(target, row) && !target.IsRunning)
            {
                var placeBelow = e.GetPosition(targetButton).Y > targetButton.Bounds.Height / 2;
                await vm.ReorderAsync(row, target, placeBelow);
                return;
            }
        }

        // 4) Anywhere else → cancel; EndDrag already restored the source row.
    }

    private void OnPointerCaptureLost(object? sender, PointerCaptureLostEventArgs e)
    {
        // We just took capture ourselves (stealing it from the row Button when the drag began) —
        // that is not a real loss, so don't tear the drag down.
        if (ReferenceEquals(e.Pointer.Captured, this)) return;
        if (!_dragArmed && !_dragging) return;
        if (_pressRow is not null) _pressRow.IsDragging = false;
        if (DataContext is TasksIslandViewModel vm) vm.ClearDropHints();
        ClearListDropHint();
        _drag.End();
        ResetPressState();
    }

    private void EndDrag(PointerEventArgs e)
    {
        if (_pressRow is not null) _pressRow.IsDragging = false;
        if (DataContext is TasksIslandViewModel vm) vm.ClearDropHints();
        ClearListDropHint();
        _drag.End();
        if (_dragging) e.Pointer.Capture(null);
        ResetPressState();
    }

    private void ResetPressState()
    {
        _dragArmed = false;
        _dragging = false;
        _pressRow = null;
        _pressControl = null;
    }

    // Live drop-hint while dragging over rows in the source (user) list.
    private void UpdateReorderHint(PointerEventArgs e, TopLevel topLevel)
    {
        if (DataContext is not TasksIslandViewModel vm) return;
        if (!vm.CanReorder) { vm.ClearDropHints(); return; }

        var targetButton = RowButtonAt(e, topLevel);
        if (targetButton?.DataContext is not TaskRowViewModel target
            || target.IsRunning || ReferenceEquals(target, _pressRow))
        {
            vm.ClearDropHints();
            return;
        }

        var placeBelow = e.GetPosition(targetButton).Y > targetButton.Bounds.Height / 2;

        // Canonicalize: "drop below X" == "drop above X+1". Render the indicator above X+1 when
        // there is one; only the last row in a section shows a below-line.
        TaskRowViewModel hintRow = target;
        bool hintBelow = false;
        if (placeBelow)
        {
            var next = FindNextInSameSection(vm, target);
            if (next is not null && !next.IsRunning) { hintRow = next; hintBelow = false; }
            else { hintRow = target; hintBelow = true; }
        }

        // A hint that lands right where the dragged row already sits is a no-op.
        if (_pressRow is not null && hintRow.Id == _pressRow.Id) { vm.ClearDropHints(); return; }

        vm.SetDropHint(hintRow, hintBelow);
    }

    // The row-level Button under the cursor, found by geometric hit-test on the source window
    // (works while the pointer is captured to this control).
    private static Button? RowButtonAt(PointerEventArgs e, TopLevel topLevel)
    {
        var pt = e.GetPosition((Visual)topLevel);
        if (topLevel.InputHitTest(pt) is not Visual hit) return null;
        var button = hit as Button ?? hit.FindAncestorOfType<Button>();
        while (button is not null && button.DataContext is not TaskRowViewModel)
            button = (button.Parent as Visual)?.FindAncestorOfType<Button>();
        return button?.DataContext is TaskRowViewModel ? button : null;
    }

    // The list-row Border under the cursor (Lists island), found the same way as RowButtonAt but
    // walking up to a Border with the "list-item" style class (mirrors ListsIslandView's own
    // FindListItemBorder) — the task drag has the pointer captured here, so a DragEventArgs never
    // reaches the Lists island's own DragDrop.DragOver/Drop handlers.
    private static ListNavItemViewModel? ListItemUnder(PointerEventArgs e, TopLevel topLevel)
    {
        var pt = e.GetPosition((Visual)topLevel);
        Visual? v = topLevel.InputHitTest(pt) as Visual;
        while (v is not null)
        {
            if (v is Border b && b.Classes.Contains("list-item"))
                return b.DataContext as ListNavItemViewModel;
            v = v.GetVisualParent();
        }
        return null;
    }

    // Live drop-hint while dragging over a candidate target list row.
    private void UpdateListDropHint(PointerEventArgs e, TopLevel topLevel)
    {
        if (DataContext is not TasksIslandViewModel vm) { ClearListDropHint(); return; }

        var target = ListItemUnder(e, topLevel);
        if (target is null || target.Kind != ListKind.User || target.Id == vm.CurrentListId)
            target = null;

        if (ReferenceEquals(_hintedList, target)) return;
        if (_hintedList is not null) _hintedList.IsTaskDropTarget = false;
        _hintedList = target;
        if (_hintedList is not null) _hintedList.IsTaskDropTarget = true;
    }

    private void ClearListDropHint()
    {
        if (_hintedList is not null) _hintedList.IsTaskDropTarget = false;
        _hintedList = null;
    }

    // The Mission Control view model whose window contains the release point, if any.
    private static MissionControlViewModel? MissionControlUnder(PixelPoint screen)
    {
        if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
            return null;
        foreach (var w in desktop.Windows)
        {
            if (w is not MissionControlWindow mc || !mc.IsVisible) continue;
            if (DragHitTest.WindowContains(mc.Position, mc.ClientSize, mc.RenderScaling, screen))
                return mc.DataContext as MissionControlViewModel;
        }
        return null;
    }

    // The next row in Rows is only "in the same section" while it's a TaskRowViewModel —
    // hitting a HeaderRow (or the end of the list) means row was the last one in its section.
    private static TaskRowViewModel? FindNextInSameSection(TasksIslandViewModel vm, TaskRowViewModel row)
    {
        var idx = vm.Rows.IndexOf(row);
        if (idx < 0 || idx + 1 >= vm.Rows.Count) return null;
        return vm.Rows[idx + 1] as TaskRowViewModel;
    }
}
