using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using AxCopilot.SDK;
using AxCopilot.Services;
using AxCopilot.Views;

namespace AxCopilot.Handlers;

public class ScreenCaptureHandler : IActionHandler
{
	private struct RECT
	{
		public int left;

		public int top;

		public int right;

		public int bottom;
	}

	private readonly SettingsService _settings;

	private const int SW_RESTORE = 9;

	private const byte VK_NEXT = 34;

	private const uint WM_VSCROLL = 277u;

	private const uint WM_KEYDOWN = 256u;

	private const uint WM_KEYUP = 257u;

	private const int SB_PAGEDOWN = 3;

	private static readonly (string Key, string Label, string Desc)[] _options = new(string, string, string)[4]
	{
		("region", "영역 선택 캡처", "마우스로 드래그하여 원하는 영역만 캡처  ·  Shift+Enter: 타이머 캡처"),
		("window", "활성 창 캡처", "런처 호출 전 활성 창만 캡처  ·  Shift+Enter: 타이머 캡처"),
		("scroll", "스크롤 캡처", "활성 창을 끝까지 스크롤하며 페이지 전체 캡처  ·  Shift+Enter: 타이머 캡처"),
		("screen", "전체 화면 캡처", "모든 모니터를 포함한 전체 화면  ·  Shift+Enter: 타이머 캡처")
	};

	public string? Prefix => string.IsNullOrWhiteSpace(_settings.Settings.ScreenCapture.Prefix) ? "cap" : _settings.Settings.ScreenCapture.Prefix.Trim();

	public PluginMetadata Metadata => new PluginMetadata("ScreenCapture", "화면 캡처 — cap screen/window/scroll/region", "1.0", "AX");

	internal int ScrollDelayMs => Math.Max(50, _settings.Settings.ScreenCapture.ScrollDelayMs);

	public ScreenCaptureHandler(SettingsService settings)
	{
		_settings = settings;
	}

	[DllImport("user32.dll")]
	private static extern bool GetWindowRect(nint hWnd, out RECT lpRect);

	[DllImport("user32.dll")]
	private static extern bool IsWindow(nint hWnd);

	[DllImport("user32.dll")]
	private static extern bool SetForegroundWindow(nint hWnd);

	[DllImport("user32.dll")]
	private static extern bool ShowWindow(nint hWnd, int nCmdShow);

	[DllImport("user32.dll")]
	private static extern bool PrintWindow(nint hwnd, nint hdcBlt, uint nFlags);

	[DllImport("user32.dll")]
	private static extern bool IsWindowVisible(nint hWnd);

	[DllImport("user32.dll")]
	private static extern nint SendMessage(nint hWnd, uint Msg, nint wParam, nint lParam);

	[DllImport("user32.dll")]
	private static extern nint FindWindowEx(nint parent, nint child, string? className, string? windowText);

	public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
	{
		string q = query.Trim().ToLowerInvariant();
		string saveHint = "클립보드에 복사";
		IEnumerable<(string, string, string)> enumerable;
		if (!string.IsNullOrWhiteSpace(q))
		{
			enumerable = _options.Where(((string Key, string Label, string Desc) o) => o.Key.StartsWith(q) || o.Label.Contains(q));
		}
		else
		{
			IEnumerable<(string, string, string)> options = _options;
			enumerable = options;
		}
		IEnumerable<(string, string, string)> source = enumerable;
		List<LauncherItem> list = source.Select<(string, string, string), LauncherItem>(((string Key, string Label, string Desc) o) => new LauncherItem(o.Label, o.Desc + "  ·  " + saveHint, null, o.Key, null, "\ue722")).ToList();
		if (!list.Any())
		{
			list.Add(new LauncherItem("알 수 없는 캡처 모드: " + q, "screen / window / scroll / region", null, null, null, "\ue7ba"));
		}
		return Task.FromResult((IEnumerable<LauncherItem>)list);
	}

	public IEnumerable<LauncherItem> GetDelayItems(string mode)
	{
		string text = _options.FirstOrDefault(((string Key, string Label, string Desc) o) => o.Key == mode).Label ?? mode;
		return new LauncherItem[3]
		{
			new LauncherItem("3초 후 " + text, "Shift+Enter로 지연 캡처", null, "delay:" + mode + ":3", null, "\ue916"),
			new LauncherItem("5초 후 " + text, "Shift+Enter로 지연 캡처", null, "delay:" + mode + ":5", null, "\ue916"),
			new LauncherItem("10초 후 " + text, "Shift+Enter로 지연 캡처", null, "delay:" + mode + ":10", null, "\ue916")
		};
	}

	public async Task ExecuteAsync(LauncherItem item, CancellationToken ct)
	{
		object data = item.Data;
		if (!(data is string data2))
		{
			return;
		}
		if (data2.StartsWith("delay:"))
		{
			string[] parts = data2.Split(':');
			if (parts.Length == 3 && int.TryParse(parts[2], out var delaySec))
			{
				await ExecuteDelayedCaptureAsync(parts[1], delaySec, ct);
				return;
			}
		}
		await Task.Delay(150, ct);
		await CaptureDirectAsync(data2, ct);
	}

	private async Task ExecuteDelayedCaptureAsync(string mode, int delaySec, CancellationToken ct)
	{
		await Task.Delay(200, ct);
		for (int i = delaySec; i > 0; i--)
		{
			ct.ThrowIfCancellationRequested();
			await Task.Delay(1000, ct);
		}
		await CaptureDirectAsync(mode, ct);
	}

	public async Task CaptureDirectAsync(string mode, CancellationToken ct = default(CancellationToken))
	{
		try
		{
			string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
			switch (mode)
			{
			case "screen":
				await CaptureScreenAsync(timestamp);
				break;
			case "window":
				await CaptureWindowAsync(timestamp);
				break;
			case "scroll":
				await CaptureScrollAsync(timestamp, ct);
				break;
			case "region":
				await CaptureRegionAsync(timestamp, ct);
				break;
			}
		}
		catch (Exception ex)
		{
			Exception ex2 = ex;
			LogService.Error("캡처 실패: " + ex2.Message);
			NotificationService.Notify("AX Copilot", "캡처 실패: " + ex2.Message);
		}
	}

	private async Task CaptureScreenAsync(string timestamp)
	{
		Rectangle bounds = GetAllScreenBounds();
		using Bitmap bmp = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb);
		using Graphics g = Graphics.FromImage(bmp);
		g.CopyFromScreen(bounds.X, bounds.Y, 0, 0, bounds.Size, CopyPixelOperation.SourceCopy);
		CopyToClipboard(bmp);
		await Task.Delay(10);
		NotificationService.Notify("화면 캡처 완료", "클립보드에 복사되었습니다");
	}

	private async Task CaptureWindowAsync(string timestamp)
	{
		nint hwnd = WindowTracker.PreviousWindow;
		if (hwnd == IntPtr.Zero || !IsWindow(hwnd))
		{
			NotificationService.Notify("AX Copilot", "캡처할 창이 없습니다. 런처 호출 전 창을 확인하세요.");
			return;
		}
		nint launcherHwnd = GetLauncherHwnd();
		if (launcherHwnd != IntPtr.Zero)
		{
			for (int i = 0; i < 10; i++)
			{
				if (!IsWindowVisible(launcherHwnd))
				{
					break;
				}
				await Task.Delay(50);
			}
		}
		ShowWindow(hwnd, 9);
		SetForegroundWindow(hwnd);
		await Task.Delay(150);
		if (!GetWindowRect(hwnd, out var rect))
		{
			return;
		}
		int w = rect.right - rect.left;
		int h = rect.bottom - rect.top;
		if (w <= 0 || h <= 0)
		{
			return;
		}
		using Bitmap bmp = CaptureWindow(hwnd, w, h, rect);
		CopyToClipboard(bmp);
		NotificationService.Notify("창 캡처 완료", "클립보드에 복사되었습니다");
	}

	private async Task CaptureScrollAsync(string timestamp, CancellationToken ct)
	{
		nint hwnd = WindowTracker.PreviousWindow;
		if (hwnd == IntPtr.Zero || !IsWindow(hwnd))
		{
			NotificationService.Notify("AX Copilot", "캡처할 창이 없습니다.");
			return;
		}
		nint launcherHwnd = GetLauncherHwnd();
		if (launcherHwnd != IntPtr.Zero)
		{
			for (int i = 0; i < 10; i++)
			{
				if (!IsWindowVisible(launcherHwnd))
				{
					break;
				}
				await Task.Delay(50, ct);
			}
		}
		ShowWindow(hwnd, 9);
		SetForegroundWindow(hwnd);
		await Task.Delay(200, ct);
		if (!GetWindowRect(hwnd, out var rect))
		{
			return;
		}
		int w = rect.right - rect.left;
		int h = rect.bottom - rect.top;
		if (w <= 0 || h <= 0)
		{
			return;
		}
		nint scrollTarget = FindScrollableChild(hwnd);
		List<Bitmap> frames = new List<Bitmap> { CaptureWindow(hwnd, w, h, rect) };
		for (int j = 0; j < 14; j++)
		{
			ct.ThrowIfCancellationRequested();
			if (scrollTarget != IntPtr.Zero)
			{
				SendMessage(scrollTarget, 277u, new IntPtr(3), IntPtr.Zero);
			}
			else
			{
				SendPageDown(hwnd);
			}
			await Task.Delay(ScrollDelayMs, ct);
			if (!GetWindowRect(hwnd, out var newRect))
			{
				break;
			}
			Bitmap frame = CaptureWindow(hwnd, w, h, newRect);
			int num;
			if (frames.Count > 0)
			{
				num = (AreSimilar(frames[frames.Count - 1], frame) ? 1 : 0);
			}
			else
			{
				num = 0;
			}
			if (num != 0)
			{
				frame.Dispose();
				break;
			}
			frames.Add(frame);
		}
		using Bitmap stitched = StitchFrames(frames, h);
		foreach (Bitmap f in frames)
		{
			f.Dispose();
		}
		CopyToClipboard(stitched);
		NotificationService.Notify("스크롤 캡처 완료", $"{stitched.Height}px  ·  클립보드에 복사되었습니다");
	}

	private static Bitmap CaptureWindow(nint hwnd, int w, int h, RECT rect)
	{
		Bitmap bitmap = new Bitmap(w, h, PixelFormat.Format32bppArgb);
		using Graphics graphics = Graphics.FromImage(bitmap);
		nint hdc = graphics.GetHdc();
		bool flag = PrintWindow(hwnd, hdc, 2u);
		graphics.ReleaseHdc(hdc);
		if (!flag)
		{
			graphics.CopyFromScreen(rect.left, rect.top, 0, 0, new Size(w, h), CopyPixelOperation.SourceCopy);
		}
		return bitmap;
	}

	private static Rectangle GetAllScreenBounds()
	{
		Screen[] allScreens = Screen.AllScreens;
		int num = allScreens.Min((Screen s) => s.Bounds.X);
		int num2 = allScreens.Min((Screen s) => s.Bounds.Y);
		int num3 = allScreens.Max((Screen s) => s.Bounds.Right);
		int num4 = allScreens.Max((Screen s) => s.Bounds.Bottom);
		return new Rectangle(num, num2, num3 - num, num4 - num2);
	}

	private static nint FindScrollableChild(nint hwnd)
	{
		string[] array = new string[7] { "Internet Explorer_Server", "Chrome_RenderWidgetHostHWND", "MozillaWindowClass", "RichEdit20W", "RICHEDIT50W", "TextBox", "EDIT" };
		foreach (string className in array)
		{
			nint num = FindWindowEx(hwnd, IntPtr.Zero, className, null);
			if (num != IntPtr.Zero)
			{
				return num;
			}
		}
		return IntPtr.Zero;
	}

	private static void SendPageDown(nint hwnd)
	{
		SendMessage(hwnd, 256u, new IntPtr(34), IntPtr.Zero);
		SendMessage(hwnd, 257u, new IntPtr(34), IntPtr.Zero);
	}

	private unsafe static bool AreSimilar(Bitmap a, Bitmap b)
	{
		if (a.Width != b.Width || a.Height != b.Height)
		{
			return false;
		}
		int num = (int)((double)a.Height * 0.8);
		int width = a.Width;
		int height = a.Height;
		Rectangle rect = new Rectangle(0, num, width, height - num);
		Rectangle rect2 = new Rectangle(0, num, width, height - num);
		BitmapData bitmapData = a.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
		BitmapData bitmapData2 = b.LockBits(rect2, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
		try
		{
			int num2 = 0;
			int num3 = 0;
			int stride = bitmapData.Stride;
			int num4 = width / 16 + 1;
			int num5 = (height - num) / 8 + 1;
			byte* ptr = (byte*)((IntPtr)bitmapData.Scan0).ToPointer();
			byte* ptr2 = (byte*)((IntPtr)bitmapData2.Scan0).ToPointer();
			for (int i = 0; i < num5; i++)
			{
				int num6 = i * 8;
				if (num6 >= height - num)
				{
					break;
				}
				for (int j = 0; j < num4; j++)
				{
					int num7 = j * 16;
					if (num7 >= width)
					{
						break;
					}
					int num8 = num6 * stride + num7 * 4;
					if (Math.Abs(ptr[num8] - ptr2[num8]) < 5 && Math.Abs(ptr[num8 + 1] - ptr2[num8 + 1]) < 5 && Math.Abs(ptr[num8 + 2] - ptr2[num8 + 2]) < 5)
					{
						num2++;
					}
					num3++;
				}
			}
			return num3 > 0 && (double)num2 / (double)num3 > 0.97;
		}
		finally
		{
			a.UnlockBits(bitmapData);
			b.UnlockBits(bitmapData2);
		}
	}

	private static Bitmap StitchFrames(List<Bitmap> frames, int windowHeight)
	{
		if (frames.Count == 0)
		{
			return new Bitmap(1, 1);
		}
		if (frames.Count == 1)
		{
			return new Bitmap(frames[0]);
		}
		int width = frames[0].Width;
		List<int> list = new List<int>();
		List<int> list2 = new List<int>();
		int num = windowHeight;
		for (int i = 1; i < frames.Count; i++)
		{
			int num2 = FindOverlap(frames[i - 1], frames[i]);
			int num3 = ((num2 > 0) ? num2 : (windowHeight / 5));
			int num4 = windowHeight - num3;
			if (num4 <= 0)
			{
				num4 = windowHeight / 4;
				num3 = windowHeight - num4;
			}
			list.Add(num3);
			list2.Add(num4);
			num += num4;
		}
		Bitmap bitmap = new Bitmap(width, num, PixelFormat.Format32bppArgb);
		using Graphics graphics = Graphics.FromImage(bitmap);
		graphics.DrawImage(frames[0], 0, 0, width, windowHeight);
		int num5 = windowHeight;
		for (int j = 1; j < frames.Count; j++)
		{
			int y = list[j - 1];
			int num6 = list2[j - 1];
			graphics.DrawImage(srcRect: new Rectangle(0, y, width, num6), destRect: new Rectangle(0, num5, width, num6), image: frames[j], srcUnit: GraphicsUnit.Pixel);
			num5 += num6;
		}
		return bitmap;
	}

	private unsafe static int FindOverlap(Bitmap prev, Bitmap next)
	{
		int num = Math.Min(prev.Width, next.Width);
		int height = prev.Height;
		if (height < 16 || num < 16)
		{
			return 0;
		}
		int num2 = (int)((double)height * 0.7);
		BitmapData bitmapData = prev.LockBits(new Rectangle(0, 0, prev.Width, prev.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
		BitmapData bitmapData2 = next.LockBits(new Rectangle(0, 0, next.Width, next.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
		try
		{
			int stride = bitmapData.Stride;
			int stride2 = bitmapData2.Stride;
			int result = 0;
			byte* ptr = (byte*)((IntPtr)bitmapData.Scan0).ToPointer();
			byte* ptr2 = (byte*)((IntPtr)bitmapData2.Scan0).ToPointer();
			for (int num3 = num2; num3 > 8; num3 -= 2)
			{
				int num4 = height - num3;
				if (num4 >= 0)
				{
					int num5 = 0;
					int num6 = 0;
					for (int i = 0; i < 8; i++)
					{
						int num7 = i * (num3 / 8);
						int num8 = num4 + num7;
						int num9 = num7;
						if (num8 >= height || num9 >= next.Height)
						{
							continue;
						}
						for (int j = 4; j < num - 4; j += 12)
						{
							int num10 = num8 * stride + j * 4;
							int num11 = num9 * stride2 + j * 4;
							if (num10 + 2 < bitmapData.Height * stride && num11 + 2 < bitmapData2.Height * stride2)
							{
								if (Math.Abs(ptr[num10] - ptr2[num11]) < 10 && Math.Abs(ptr[num10 + 1] - ptr2[num11 + 1]) < 10 && Math.Abs(ptr[num10 + 2] - ptr2[num11 + 2]) < 10)
								{
									num5++;
								}
								num6++;
							}
						}
					}
					if (num6 > 0 && (double)num5 / (double)num6 > 0.8)
					{
						result = num3;
						break;
					}
				}
			}
			return result;
		}
		finally
		{
			prev.UnlockBits(bitmapData);
			next.UnlockBits(bitmapData2);
		}
	}

	private async Task CaptureRegionAsync(string timestamp, CancellationToken ct)
	{
		Rectangle bounds = GetAllScreenBounds();
		Bitmap fullBmp = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb);
		try
		{
			using (Graphics g = Graphics.FromImage(fullBmp))
			{
				g.CopyFromScreen(bounds.X, bounds.Y, 0, 0, bounds.Size, CopyPixelOperation.SourceCopy);
			}
			Rectangle? selected = null;
			System.Windows.Application current = System.Windows.Application.Current;
			if (current != null)
			{
				((DispatcherObject)current).Dispatcher.Invoke((Action)delegate
				{
					RegionSelectWindow regionSelectWindow = new RegionSelectWindow(fullBmp, bounds);
					regionSelectWindow.ShowDialog();
					selected = regionSelectWindow.SelectedRect;
				});
			}
			if (!selected.HasValue || selected.Value.Width < 4 || selected.Value.Height < 4)
			{
				NotificationService.Notify("AX Copilot", "영역 선택이 취소되었습니다.");
				return;
			}
			Rectangle r = selected.Value;
			using Bitmap crop = new Bitmap(r.Width, r.Height, PixelFormat.Format32bppArgb);
			using (Graphics g2 = Graphics.FromImage(crop))
			{
				g2.DrawImage(fullBmp, new Rectangle(0, 0, r.Width, r.Height), r, GraphicsUnit.Pixel);
			}
			CopyToClipboard(crop);
			NotificationService.Notify("영역 캡처 완료", $"{r.Width}×{r.Height}  ·  클립보드에 복사되었습니다");
			await Task.CompletedTask;
		}
		finally
		{
			if (fullBmp != null)
			{
				((IDisposable)fullBmp).Dispose();
			}
		}
	}

	private static void CopyToClipboard(Bitmap bmp)
	{
		try
		{
			System.Windows.Application current = System.Windows.Application.Current;
			if (current == null)
			{
				return;
			}
			((DispatcherObject)current).Dispatcher.Invoke((Action)delegate
			{
				using MemoryStream memoryStream = new MemoryStream();
				bmp.Save(memoryStream, ImageFormat.Bmp);
				memoryStream.Position = 0L;
				BitmapImage bitmapImage = new BitmapImage();
				bitmapImage.BeginInit();
				bitmapImage.StreamSource = memoryStream;
				bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
				bitmapImage.EndInit();
				((Freezable)bitmapImage).Freeze();
				System.Windows.Clipboard.SetImage(bitmapImage);
			});
		}
		catch (Exception ex)
		{
			LogService.Warn("클립보드 이미지 복사 실패: " + ex.Message);
		}
	}

	private static nint GetLauncherHwnd()
	{
		try
		{
			nint hwnd = IntPtr.Zero;
			System.Windows.Application current = System.Windows.Application.Current;
			if (current != null)
			{
				((DispatcherObject)current).Dispatcher.Invoke((Action)delegate
				{
					Window window = System.Windows.Application.Current.Windows.OfType<Window>().FirstOrDefault((Window w) => ((object)w).GetType().Name == "LauncherWindow");
					if (window != null)
					{
						hwnd = new WindowInteropHelper(window).Handle;
					}
				});
			}
			return hwnd;
		}
		catch
		{
			return IntPtr.Zero;
		}
	}
}
