using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using Clipboard = System.Windows.Forms.Clipboard;

// Author: uye (owner of the MaaAssistantArknights team)
// Original Source: MaaAssistantArknights project - https://github.com/MaaAssistantArknights/MaaAssistantArknights
// License: Apache License 2.0 (this file only)
// 
// This file is based on work originally developed in the MaaAssistantArknights project,
// which is licensed under the GNU AGPL v3.0 only.
// 
// As the original author of this code, I am re-licensing this specific file under
// the Apache License 2.0 for use in PCL2-CE.
// 
// Description:
// Implements a WPF clipboard fix to handle OpenClipboard failures in TextBox,
// RichTextBox, and DataGrid, typically caused by focus issues or external hooks.
// 
// Date: 2025-07-03

namespace PCL.Controls.Behaviors;

public sealed class ClipboardInterceptor
{
    public static readonly DependencyProperty EnableSafeClipboardProperty =
        DependencyProperty.RegisterAttached("EnableSafeClipboard", typeof(bool), typeof(ClipboardInterceptor),
            new PropertyMetadata(false, OnEnableSafeClipboardChanged));

    private ClipboardInterceptor()
    {
    }

    public static void SetEnableSafeClipboard(DependencyObject element, bool value)
    {
        element.SetValue(EnableSafeClipboardProperty, value);
    }

    public static bool GetEnableSafeClipboard(DependencyObject element)
    {
        return (bool)element.GetValue(EnableSafeClipboardProperty);
    }

    private static void OnEnableSafeClipboardChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (!(bool)e.NewValue)
            return;

        switch (d)
        {
            case TextBox box:
                AddCommandBindingsToTextBox(box);
                break;
            case RichTextBox box:
                AddCommandBindingsToRichTextBox(box);
                break;
            case DataGrid grid:
                AddCommandBindingsToDataGrid(grid);
                break;
        }
    }

    private static void AddCommandBindingsToTextBox(TextBox tb)
    {
        tb.CommandBindings.Add(new CommandBinding(ApplicationCommands.Copy, OnCopyTextBox));
        tb.CommandBindings.Add(new CommandBinding(ApplicationCommands.Cut, OnCutTextBox));
        tb.CommandBindings.Add(new CommandBinding(ApplicationCommands.Paste, OnPasteTextBox));
    }

    private static void AddCommandBindingsToRichTextBox(RichTextBox rtb)
    {
        rtb.CommandBindings.Add(new CommandBinding(ApplicationCommands.Copy, OnCopyRichTextBox));
        rtb.CommandBindings.Add(new CommandBinding(ApplicationCommands.Cut, OnCutRichTextBox));
        rtb.CommandBindings.Add(new CommandBinding(ApplicationCommands.Paste, OnPasteRichTextBox));
    }

    private static void AddCommandBindingsToDataGrid(DataGrid dg)
    {
        dg.CommandBindings.Add(new CommandBinding(ApplicationCommands.Copy, OnCopyDataGrid));
    }

    private static void OnCopyTextBox(object sender, ExecutedRoutedEventArgs e)
    {
        var tb = sender as TextBox;
        if (tb is null || tb.SelectionLength <= 0)
            return;

        TrySetClipboardText(tb.SelectedText);
        e.Handled = true;
    }

    private static void OnCutTextBox(object sender, ExecutedRoutedEventArgs e)
    {
        var tb = sender as TextBox;
        if (tb is null || tb.SelectionLength <= 0)
            return;

        TrySetClipboardText(tb.SelectedText);

        tb.SelectedText = string.Empty;
        e.Handled = true;
    }

    private static void OnPasteTextBox(object sender, ExecutedRoutedEventArgs e)
    {
        var tb = sender as TextBox;
        if (tb is null)
            return;

        if (Clipboard.ContainsText())
        {
            var pasteText = Clipboard.GetText();

            if (!tb.AcceptsReturn)
                pasteText = pasteText.Replace("\r\n", " ").Replace("\r", " ")
                    .Replace("\n", " ");

            var start = tb.SelectionStart;

            tb.SelectedText = pasteText;
            tb.CaretIndex = start + pasteText.Length;
            tb.SelectionLength = 0;
        }

        e.Handled = true;
    }

    private static void OnCopyRichTextBox(object sender, ExecutedRoutedEventArgs e)
    {
        var rtb = sender as RichTextBox;
        if (rtb is null)
            return;

        var textRange = new TextRange(rtb.Selection.Start, rtb.Selection.End);
        if (string.IsNullOrEmpty(textRange.Text))
            return;

        TrySetClipboardText(textRange.Text);
        e.Handled = true;
    }

    private static void OnCutRichTextBox(object sender, ExecutedRoutedEventArgs e)
    {
        var rtb = sender as RichTextBox;
        if (rtb is null)
            return;

        var selection = new TextRange(rtb.Selection.Start, rtb.Selection.End);
        if (string.IsNullOrEmpty(selection.Text))
            return;

        TrySetClipboardText(selection.Text);

        selection.Text = string.Empty;
        e.Handled = true;
    }

    private static void OnPasteRichTextBox(object sender, ExecutedRoutedEventArgs e)
    {
        var rtb = sender as RichTextBox;
        if (rtb is null)
            return;

        if (!Clipboard.ContainsText())
            return;

        var pasteText = Clipboard.GetText();
        var selection = rtb.Selection;

        selection.Text = pasteText;

        var caretPos = selection.End;
        rtb.CaretPosition = caretPos;
        rtb.Selection.Select(caretPos, caretPos);

        e.Handled = true;
    }

    private static void OnCopyDataGrid(object sender, ExecutedRoutedEventArgs e)
    {
        var dg = sender as DataGrid;
        if (dg is null || dg.SelectedCells is null || dg.SelectedCells.Count == 0)
            return;

        var sb = new StringBuilder();
        var rowGroups = dg.SelectedCells.GroupBy(c => c.Item);

        foreach (var row in rowGroups)
        {
            var rowText = string.Join("\t", row.Select(cell =>
            {
                var tb = cell.Column.GetCellContent(cell.Item) as TextBlock;
                return tb is not null ? tb.Text : "";
            }));
            sb.AppendLine(rowText);
        }

        TrySetClipboardText(sb.ToString().TrimEnd('\r', '\n'));
        e.Handled = true;
    }

    private static bool TrySetClipboardText(string text)
    {
        try
        {
            Clipboard.Clear();
            Clipboard.SetDataObject(text, true);
            return true;
        }
        catch
        {
            return false;
        }
    }
}
