using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
using AxCopilot.Core;
using AxCopilot.Handlers;
using AxCopilot.Models;
using AxCopilot.SDK;
using AxCopilot.Services;

namespace AxCopilot.ViewModels;

public class LauncherViewModel : INotifyPropertyChanged
{
	private sealed class FavJson
	{
		[JsonPropertyName("name")]
		public string Name { get; set; } = "";

		[JsonPropertyName("path")]
		public string Path { get; set; } = "";
	}

	private readonly CommandResolver _resolver;

	private readonly SettingsService _settings;

	private string _inputText = "";

	private LauncherItem? _selectedItem;

	private bool _isLoading;

	private CancellationTokenSource? _searchCts;

	private Timer? _debounceTimer;

	private const int DebounceMs = 30;

	private string _lastSearchQuery = "";

	private bool _isActionMode;

	private LauncherItem? _actionSourceItem;

	private string _savedQuery = "";

	private readonly HashSet<ClipboardEntry> _mergeQueue = new HashSet<ClipboardEntry>();

	private string _placeholderText = L10n.Get("placeholder");

	private static readonly Dictionary<string, (string Label, string Symbol, string ColorHex)> PrefixMap = new Dictionary<string, (string, string, string)>
	{
		{
			"@",
			("URL", "\ue774", "#0078D4")
		},
		{
			"~",
			("워크", "\ue8a1", "#C50F1F")
		},
		{
			">",
			("명령", "\ue756", "#323130")
		},
		{
			"$",
			("클립", "\ue77f", "#8764B8")
		},
		{
			"cd",
			("폴더", "\ue8b7", "#107C10")
		},
		{
			"#",
			("히스", "\ue81c", "#B7791F")
		},
		{
			";",
			("스닛", "\ue70b", "#0F6CBD")
		},
		{
			"=",
			("계산", "\ue8ef", "#4B5EFC")
		},
		{
			"!",
			("AI", "\ue8bd", "#8B2FC9")
		},
		{
			"?",
			("검색", "\ue774", "#006EAF")
		},
		{
			"/",
			("시스템", "\ue7e8", "#4A4A4A")
		},
		{
			"kill ",
			("킬", "\uea39", "#CC2222")
		},
		{
			"media ",
			("미디어", "\ue768", "#1A6B3C")
		},
		{
			"info ",
			("시스템", "\ue7f4", "#5B4E7E")
		},
		{
			"port",
			("포트", "\ue968", "#006699")
		},
		{
			"emoji",
			("이모지", "\ue76e", "#F59E0B")
		},
		{
			"color",
			("색상", "\ue771", "#EC4899")
		},
		{
			"recent",
			("최근", "\ue81c", "#059669")
		},
		{
			"note",
			("메모", "\ue70b", "#7C3AED")
		},
		{
			"uninstall",
			("제거", "\ue74d", "#DC2626")
		},
		{
			"env",
			("환경변수", "\ue8d7", "#0D9488")
		},
		{
			"json",
			("JSON", "\ue930", "#D97706")
		},
		{
			"encode ",
			("인코딩", "\ue8cb", "#6366F1")
		},
		{
			"snap",
			("스냅", "\ue8a0", "#B45309")
		},
		{
			"cap",
			("캡처", "\ue722", "#BE185D")
		},
		{
			"help",
			("도움말", "\ue946", "#6B7280")
		}
	};

	public BulkObservableCollection<LauncherItem> Results { get; } = new BulkObservableCollection<LauncherItem>();

	public string InputText
	{
		get
		{
			return _inputText;
		}
		set
		{
			if (_inputText == value)
			{
				return;
			}
			_inputText = value;
			OnPropertyChanged("InputText");
			OnPropertyChanged("HasActivePrefix");
			OnPropertyChanged("ActivePrefixLabel");
			OnPropertyChanged("ActivePrefixSymbol");
			OnPropertyChanged("ActivePrefixBrush");
			OnPropertyChanged("IsClipboardMode");
			OnPropertyChanged("ShowMergeHint");
			OnPropertyChanged("MergeHintText");
			_searchCts?.Cancel();
			_debounceTimer?.Dispose();
			if (string.IsNullOrWhiteSpace(value))
			{
				Results.Clear();
				return;
			}
			string captured = value;
			_debounceTimer = new Timer(delegate(object? _)
			{
				Application current = Application.Current;
				if (current != null)
				{
					((DispatcherObject)current).Dispatcher.InvokeAsync<object>((Func<object>)(() => _ = SearchAsync(captured)));
				}
			}, null, 30, -1);
		}
	}

	public LauncherItem? SelectedItem
	{
		get
		{
			return _selectedItem;
		}
		set
		{
			_selectedItem = value;
			OnPropertyChanged("SelectedItem");
		}
	}

	public bool IsLoading
	{
		get
		{
			return _isLoading;
		}
		set
		{
			_isLoading = value;
			OnPropertyChanged("IsLoading");
		}
	}

	public string PlaceholderText
	{
		get
		{
			return _placeholderText;
		}
		private set
		{
			_placeholderText = value;
			OnPropertyChanged("PlaceholderText");
		}
	}

	public bool EnableIconAnimation => _settings.Settings.Launcher.EnableIconAnimation;

	public bool EnableRainbowGlow => _settings.Settings.Launcher.EnableRainbowGlow;

	public bool EnableSelectionGlow => _settings.Settings.Launcher.EnableSelectionGlow;

	public bool ShowLauncherBorder => _settings.Settings.Launcher.ShowLauncherBorder;

	public string ThemeSetting => _settings.Settings.Launcher.Theme;

	public CustomThemeColors? CustomThemeColors => _settings.Settings.Launcher.CustomTheme;

	public string WindowPosition => _settings.Settings.Launcher.Position;

	public bool ShowNumberBadges => _settings.Settings.Launcher.ShowNumberBadges;

	public bool CloseOnFocusLost => _settings.Settings.Launcher.CloseOnFocusLost;

	public bool EnableActionMode => _settings.Settings.Launcher.EnableActionMode;

	public bool HasActivePrefix => _settings.Settings.Launcher.ShowPrefixBadge && _inputText.Length > 0 && PrefixMap.Keys.Any((string k) => (k != "!" || IsAiEnabled()) && _inputText.StartsWith(k, StringComparison.OrdinalIgnoreCase));

	public string? ActivePrefix => PrefixMap.Keys.FirstOrDefault((string k) => (k != "!" || IsAiEnabled()) && _inputText.StartsWith(k, StringComparison.OrdinalIgnoreCase));

	public string ActivePrefixLabel
	{
		get
		{
			object result;
			if (ActivePrefix != null && PrefixMap.TryGetValue(ActivePrefix, out (string, string, string) value))
			{
				(result, _, _) = value;
			}
			else
			{
				result = "";
			}
			return (string)result;
		}
	}

	public string ActivePrefixSymbol
	{
		get
		{
			(string, string, string) value;
			return (ActivePrefix != null && PrefixMap.TryGetValue(ActivePrefix, out value)) ? value.Item2 : "\ue721";
		}
	}

	public SolidColorBrush ActivePrefixBrush
	{
		get
		{
			if (ActivePrefix != null && PrefixMap.TryGetValue(ActivePrefix, out (string, string, string) value))
			{
				Color color = (Color)ColorConverter.ConvertFromString(value.Item3);
				return new SolidColorBrush(color);
			}
			return new SolidColorBrush(Color.FromRgb(75, 94, 252));
		}
	}

	public bool IsActionMode
	{
		get
		{
			return _isActionMode;
		}
		private set
		{
			_isActionMode = value;
			OnPropertyChanged("IsActionMode");
			OnPropertyChanged("ShowActionModeBar");
			OnPropertyChanged("ActionModeBreadcrumb");
		}
	}

	public bool ShowActionModeBar => IsActionMode;

	public string ActionModeBreadcrumb => _actionSourceItem?.Title ?? "";

	public bool IsClipboardMode => _inputText.StartsWith("#", StringComparison.Ordinal);

	public int MergeCount => _mergeQueue.Count;

	public bool ShowMergeHint => _mergeQueue.Count > 0 && IsClipboardMode;

	public string MergeHintText => (_mergeQueue.Count > 0) ? $"{_mergeQueue.Count}개 선택됨  ·  Shift+Enter로 합치기  ·  Esc로 취소" : "";

	public event EventHandler? CloseRequested;

	public event EventHandler<string>? NotificationRequested;

	public event PropertyChangedEventHandler? PropertyChanged;

	public void RefreshPlaceholder()
	{
		PlaceholderText = (_settings.Settings.Launcher.EnableRandomPlaceholder ? L10n.GetRandomPlaceholder() : L10n.Get("placeholder"));
	}

	private static bool IsAiEnabled()
	{
		return (Application.Current as App)?.SettingsService?.Settings.AiEnabled ?? true;
	}

	public bool CanEnterActionMode()
	{
		return !IsActionMode && SelectedItem?.Data is IndexEntry;
	}

	public bool IsItemMarkedForMerge(LauncherItem item)
	{
		return item.Data is ClipboardEntry item2 && _mergeQueue.Contains(item2);
	}

	public LauncherViewModel(CommandResolver resolver, SettingsService settings)
	{
		_resolver = resolver;
		_settings = settings;
	}

	public void OnShown()
	{
		if (IsActionMode)
		{
			IsActionMode = false;
			_actionSourceItem = null;
		}
		Results.Clear();
		_lastSearchQuery = "";
		ClearMerge();
	}

	internal Task TriggerImeSearchAsync(string text)
	{
		string text2 = text.Trim().ToLowerInvariant();
		if (text2 == _lastSearchQuery && Results.Count > 0)
		{
			return Task.CompletedTask;
		}
		return SearchAsync(text);
	}

	private async Task SearchAsync(string query)
	{
		_searchCts = new CancellationTokenSource();
		CancellationToken ct = _searchCts.Token;
		string queryKey = query.Trim().ToLowerInvariant();
		bool isSameQuery = queryKey == _lastSearchQuery && Results.Count > 0;
		if (!isSameQuery)
		{
			Results.Clear();
		}
		if (string.IsNullOrWhiteSpace(query))
		{
			_lastSearchQuery = "";
		}
		else
		{
			if ((!_settings.Settings.Launcher.EnableFavorites && query.StartsWith("fav", StringComparison.OrdinalIgnoreCase)) || (!_settings.Settings.Launcher.EnableRecent && query.StartsWith("recent", StringComparison.OrdinalIgnoreCase)))
			{
				return;
			}
			LauncherItem prevSelected = (isSameQuery ? SelectedItem : null);
			IsLoading = true;
			try
			{
				IEnumerable<LauncherItem> items = await _resolver.ResolveAsync(query, ct);
				if (ct.IsCancellationRequested)
				{
					return;
				}
				Results.ReplaceAll(items);
				_lastSearchQuery = queryKey;
				if (isSameQuery && prevSelected != null)
				{
					LauncherItem restored = Results.FirstOrDefault((LauncherItem r) => r.Data == prevSelected.Data || r.Title == prevSelected.Title);
					SelectedItem = restored ?? Results.FirstOrDefault();
				}
				else
				{
					SelectedItem = Results.FirstOrDefault();
				}
			}
			catch (OperationCanceledException)
			{
			}
			catch (Exception ex2)
			{
				Exception ex3 = ex2;
				LogService.Error("검색 오류: " + ex3.Message);
			}
			finally
			{
				if (!ct.IsCancellationRequested)
				{
					IsLoading = false;
				}
			}
		}
	}

	public async Task ExecuteSelectedAsync()
	{
		FileActionData fileAction = default(FileActionData);
		int num;
		if (IsActionMode)
		{
			object obj = SelectedItem?.Data;
			fileAction = obj as FileActionData;
			num = (((object)fileAction != null) ? 1 : 0);
		}
		else
		{
			num = 0;
		}
		if (num != 0)
		{
			ExecuteFileAction(fileAction);
			ExitActionMode();
			this.CloseRequested?.Invoke(this, EventArgs.Empty);
		}
		else if (!(SelectedItem == null))
		{
			this.CloseRequested?.Invoke(this, EventArgs.Empty);
			try
			{
				await _resolver.ExecuteAsync(SelectedItem, InputText, CancellationToken.None);
			}
			catch (Exception ex)
			{
				this.NotificationRequested?.Invoke(this, "실행 실패: " + ex.Message);
				LogService.Error("Execute 오류: " + ex.Message);
			}
		}
	}

	public bool ShowDelayTimerItems()
	{
		if (!(SelectedItem?.Data is string text))
		{
			return false;
		}
		if (text.StartsWith("delay:"))
		{
			return false;
		}
		if (!_resolver.RegisteredHandlers.TryGetValue(ActivePrefix ?? "", out IActionHandler value) || !(value is ScreenCaptureHandler screenCaptureHandler))
		{
			return false;
		}
		List<LauncherItem> list = screenCaptureHandler.GetDelayItems(text).ToList();
		Results.Clear();
		foreach (LauncherItem item in list)
		{
			Results.Add(item);
		}
		SelectedItem = Results.FirstOrDefault();
		return true;
	}

	public void SelectNext()
	{
		if (Results.Count != 0)
		{
			int num = ((SelectedItem != null) ? Results.IndexOf(SelectedItem) : (-1));
			SelectedItem = Results[(num + 1) % Results.Count];
		}
	}

	public void SelectPrev()
	{
		if (Results.Count != 0)
		{
			int num = ((SelectedItem != null) ? Results.IndexOf(SelectedItem) : 0);
			SelectedItem = Results[(num - 1 + Results.Count) % Results.Count];
		}
	}

	public string GetLargeTypeText()
	{
		if (SelectedItem == null)
		{
			return "";
		}
		if (SelectedItem.Data is string text && !string.IsNullOrWhiteSpace(text))
		{
			return text;
		}
		if (SelectedItem.Data is ClipboardEntry { IsText: not false } clipboardEntry)
		{
			return clipboardEntry.Text;
		}
		return SelectedItem.Title;
	}

	public void EnterActionMode(LauncherItem item)
	{
		if (_settings.Settings.Launcher.EnableActionMode && item.Data is IndexEntry indexEntry)
		{
			_actionSourceItem = item;
			_savedQuery = _inputText;
			IsActionMode = true;
			string text = Environment.ExpandEnvironmentVariables(indexEntry.Path);
			bool flag = Directory.Exists(text);
			string fileName = Path.GetFileName(text);
			Results.Clear();
			Results.Add(MakeAction("경로 복사", text, FileAction.CopyPath, "\ue77f", "#8764B8"));
			Results.Add(MakeAction("전체 경로 복사", text, FileAction.CopyFullPath, "\ue77f", "#C55A11"));
			Results.Add(MakeAction("파일 탐색기에서 열기", "Explorer에서 위치 선택됨으로 표시", FileAction.OpenExplorer, "\ue8b7", "#107C10"));
			if (!flag)
			{
				Results.Add(MakeAction("관리자 권한으로 실행", "UAC 권한 상승 후 실행", FileAction.RunAsAdmin, "\ue72e", "#C50F1F"));
			}
			Results.Add(MakeAction("터미널에서 열기", flag ? text : (Path.GetDirectoryName(text) ?? text), FileAction.OpenTerminal, "\ue756", "#323130"));
			if (!flag)
			{
				Results.Add(MakeAction("파일 속성 보기", "Windows 속성 대화 상자 열기", FileAction.ShowProperties, "\ue946", "#6B2C91"));
			}
			Results.Add(MakeAction("이름 바꾸기", fileName, FileAction.Rename, "\ue8ac", "#D97706"));
			Results.Add(MakeAction("휴지통으로 삭제", "복구 가능한 삭제  ·  확인 후 실행", FileAction.DeleteToRecycleBin, "\ue74d", "#C50F1F"));
			SelectedItem = Results.FirstOrDefault();
		}
		static LauncherItem MakeAction(string title, string subtitle, FileAction action, string symbol, string colorHex)
		{
			FileActionData data = new FileActionData(subtitle, action);
			return new LauncherItem(title, subtitle, null, data, null, symbol);
		}
	}

	public void ExitActionMode()
	{
		IsActionMode = false;
		_actionSourceItem = null;
		string savedQuery = _savedQuery;
		_savedQuery = "";
		SearchAsync(savedQuery);
	}

	private static void ExecuteFileAction(FileActionData data)
	{
		string path = data.Path;
		switch (data.Action)
		{
		case FileAction.CopyPath:
			((DispatcherObject)Application.Current).Dispatcher.Invoke((Action)delegate
			{
				Clipboard.SetText(path);
			});
			break;
		case FileAction.OpenExplorer:
			if (File.Exists(path))
			{
				Process.Start("explorer.exe", "/select,\"" + path + "\"");
			}
			else
			{
				Process.Start("explorer.exe", "\"" + path + "\"");
			}
			break;
		case FileAction.RunAsAdmin:
			try
			{
				Process.Start(new ProcessStartInfo(path)
				{
					UseShellExecute = true,
					Verb = "runas"
				});
				break;
			}
			catch (Exception ex2)
			{
				LogService.Warn("관리자 실행 취소: " + ex2.Message);
				break;
			}
		case FileAction.CopyFullPath:
			((DispatcherObject)Application.Current).Dispatcher.Invoke((Action)delegate
			{
				Clipboard.SetText(path);
			});
			break;
		case FileAction.ShowProperties:
			try
			{
				ProcessStartInfo startInfo = new ProcessStartInfo("explorer.exe")
				{
					Arguments = "/select,\"" + path + "\"",
					UseShellExecute = true
				};
				Process.Start(startInfo);
				ProcessStartInfo processStartInfo = new ProcessStartInfo
				{
					FileName = "rundll32.exe",
					Arguments = "shell32.dll,ShellExec_RunDLL \"properties\" \"" + path + "\"",
					UseShellExecute = false
				};
				try
				{
					Process.Start(new ProcessStartInfo(path)
					{
						UseShellExecute = true,
						Verb = "properties"
					});
					break;
				}
				catch
				{
					break;
				}
			}
			catch (Exception ex3)
			{
				LogService.Warn("속성 열기 실패: " + ex3.Message);
				break;
			}
		case FileAction.Rename:
			break;
		case FileAction.DeleteToRecycleBin:
			break;
		case FileAction.OpenTerminal:
		{
			string text = (File.Exists(path) ? (Path.GetDirectoryName(path) ?? path) : path);
			try
			{
				Process.Start("wt.exe", "-d \"" + text + "\"");
				break;
			}
			catch
			{
				try
				{
					Process.Start("cmd.exe", "/k cd /d \"" + text + "\"");
					break;
				}
				catch (Exception ex)
				{
					LogService.Warn("터미널 열기 실패: " + ex.Message);
					break;
				}
			}
		}
		}
	}

	public bool CopySelectedPath()
	{
		if (SelectedItem?.Data is IndexEntry indexEntry)
		{
			string path = Path.GetFileName(Environment.ExpandEnvironmentVariables(indexEntry.Path));
			((DispatcherObject)Application.Current).Dispatcher.Invoke((Action)delegate
			{
				Clipboard.SetText(path);
			});
			return true;
		}
		return false;
	}

	public bool CopySelectedFullPath()
	{
		if (SelectedItem?.Data is IndexEntry indexEntry)
		{
			string path = Environment.ExpandEnvironmentVariables(indexEntry.Path);
			((DispatcherObject)Application.Current).Dispatcher.Invoke((Action)delegate
			{
				Clipboard.SetText(path);
			});
			return true;
		}
		return false;
	}

	public bool OpenSelectedInExplorer()
	{
		if (SelectedItem?.Data is IndexEntry indexEntry)
		{
			string text = Environment.ExpandEnvironmentVariables(indexEntry.Path);
			if (File.Exists(text))
			{
				Process.Start("explorer.exe", "/select,\"" + text + "\"");
			}
			else if (Directory.Exists(text))
			{
				Process.Start("explorer.exe", "\"" + text + "\"");
			}
			return true;
		}
		return false;
	}

	public bool RunSelectedAsAdmin()
	{
		if (SelectedItem?.Data is IndexEntry indexEntry)
		{
			string fileName = Environment.ExpandEnvironmentVariables(indexEntry.Path);
			try
			{
				Process.Start(new ProcessStartInfo(fileName)
				{
					UseShellExecute = true,
					Verb = "runas"
				});
				return true;
			}
			catch (Exception ex)
			{
				LogService.Warn("관리자 실행 취소: " + ex.Message);
			}
		}
		return false;
	}

	public bool ShowSelectedProperties()
	{
		if (SelectedItem?.Data is IndexEntry indexEntry)
		{
			string text = Environment.ExpandEnvironmentVariables(indexEntry.Path);
			try
			{
				Process.Start(new ProcessStartInfo(text)
				{
					UseShellExecute = true,
					Verb = "properties"
				});
				return true;
			}
			catch
			{
				Process.Start("explorer.exe", "/select,\"" + text + "\"");
				return true;
			}
		}
		return false;
	}

	public bool RemoveSelectedFromRecent()
	{
		if (SelectedItem == null || Results.Count == 0)
		{
			return false;
		}
		int val = Results.IndexOf(SelectedItem);
		Results.Remove(SelectedItem);
		if (Results.Count > 0)
		{
			SelectedItem = Results[Math.Min(val, Results.Count - 1)];
		}
		else
		{
			SelectedItem = null;
		}
		return true;
	}

	public void ClearInput()
	{
		InputText = "";
	}

	public void SelectFirst()
	{
		if (Results.Count > 0)
		{
			SelectedItem = Results[0];
		}
	}

	public void SelectLast()
	{
		if (Results.Count > 0)
		{
			BulkObservableCollection<LauncherItem> results = Results;
			SelectedItem = results[results.Count - 1];
		}
	}

	public bool? ToggleFavorite()
	{
		if (!(SelectedItem?.Data is IndexEntry indexEntry))
		{
			return null;
		}
		string path = Environment.ExpandEnvironmentVariables(indexEntry.Path);
		string text = Path.GetFileNameWithoutExtension(path);
		if (string.IsNullOrWhiteSpace(text))
		{
			text = Path.GetFileName(path);
		}
		string path2 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "favorites.json");
		try
		{
			JsonSerializerOptions options = new JsonSerializerOptions
			{
				WriteIndented = true,
				PropertyNameCaseInsensitive = true
			};
			List<FavJson> list = new List<FavJson>();
			if (File.Exists(path2))
			{
				list = JsonSerializer.Deserialize<List<FavJson>>(File.ReadAllText(path2), options) ?? new List<FavJson>();
			}
			FavJson favJson = list.FirstOrDefault((FavJson f) => f.Path.Equals(path, StringComparison.OrdinalIgnoreCase));
			if (favJson != null)
			{
				list.Remove(favJson);
				Directory.CreateDirectory(Path.GetDirectoryName(path2));
				File.WriteAllText(path2, JsonSerializer.Serialize(list, options));
				return false;
			}
			list.Insert(0, new FavJson
			{
				Name = text,
				Path = path
			});
			Directory.CreateDirectory(Path.GetDirectoryName(path2));
			File.WriteAllText(path2, JsonSerializer.Serialize(list, options));
			return true;
		}
		catch (Exception ex)
		{
			LogService.Warn("즐겨찾기 토글 실패: " + ex.Message);
			return null;
		}
	}

	public bool OpenSelectedInTerminal()
	{
		string text2;
		if (SelectedItem?.Data is IndexEntry indexEntry)
		{
			string text = Environment.ExpandEnvironmentVariables(indexEntry.Path);
			text2 = (Directory.Exists(text) ? text : (Path.GetDirectoryName(text) ?? text));
		}
		else
		{
			text2 = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
		}
		try
		{
			Process.Start("wt.exe", "-d \"" + text2 + "\"");
			return true;
		}
		catch
		{
			try
			{
				Process.Start("cmd.exe", "/k cd /d \"" + text2 + "\"");
				return true;
			}
			catch (Exception ex)
			{
				LogService.Warn("터미널 열기 실패: " + ex.Message);
				return false;
			}
		}
	}

	public void NavigateToDownloads()
	{
		string text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads");
		InputText = "cd " + text;
	}

	public void ToggleMergeItem(LauncherItem? item)
	{
		if (item?.Data is ClipboardEntry { IsText: not false } clipboardEntry)
		{
			if (!_mergeQueue.Remove(clipboardEntry))
			{
				_mergeQueue.Add(clipboardEntry);
			}
			OnPropertyChanged("MergeCount");
			OnPropertyChanged("ShowMergeHint");
			OnPropertyChanged("MergeHintText");
		}
	}

	public void ExecuteMerge()
	{
		if (_mergeQueue.Count == 0)
		{
			return;
		}
		List<string> list = (from r in Results
			where r.Data is ClipboardEntry item && _mergeQueue.Contains(item)
			select ((ClipboardEntry)r.Data).Text).ToList();
		if (list.Count == 0)
		{
			list = _mergeQueue.Select((ClipboardEntry e) => e.Text).ToList();
		}
		string merged = string.Join("\n", list);
		try
		{
			((DispatcherObject)Application.Current).Dispatcher.Invoke((Action)delegate
			{
				Clipboard.SetText(merged);
			});
		}
		catch (Exception ex)
		{
			LogService.Warn("병합 클립보드 실패: " + ex.Message);
		}
		ClearMerge();
		this.CloseRequested?.Invoke(this, EventArgs.Empty);
		LogService.Info($"클립보드 병합: {list.Count}개 항목");
	}

	public void ClearMerge()
	{
		_mergeQueue.Clear();
		OnPropertyChanged("MergeCount");
		OnPropertyChanged("ShowMergeHint");
		OnPropertyChanged("MergeHintText");
	}

	protected void OnPropertyChanged([CallerMemberName] string? name = null)
	{
		this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
	}
}
