using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AxCopilot.Core;
using AxCopilot.Models;

namespace AxCopilot.Services;

public class IndexService : IDisposable
{
	private readonly SettingsService _settings;

	private List<IndexEntry> _index = new List<IndexEntry>();

	private readonly List<FileSystemWatcher> _watchers = new List<FileSystemWatcher>();

	private Timer? _debounceTimer;

	private readonly object _timerLock = new object();

	private const int DebounceMs = 3000;

	private readonly SemaphoreSlim _rebuildLock = new SemaphoreSlim(1, 1);

	public IReadOnlyList<IndexEntry> Entries => _index;

	public TimeSpan LastIndexDuration { get; private set; }

	public int LastIndexCount { get; private set; }

	public event EventHandler? IndexRebuilt;

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

	public async Task BuildAsync(CancellationToken ct = default(CancellationToken))
	{
		await _rebuildLock.WaitAsync(ct);
		Stopwatch sw = Stopwatch.StartNew();
		try
		{
			List<IndexEntry> entries = new List<IndexEntry>();
			IEnumerable<string> paths = _settings.Settings.IndexPaths.Select((string p) => Environment.ExpandEnvironmentVariables(p));
			HashSet<string> allowedExts = new HashSet<string>(_settings.Settings.IndexExtensions.Select((string e) => e.ToLowerInvariant().StartsWith(".") ? e.ToLowerInvariant() : ("." + e.ToLowerInvariant())), StringComparer.OrdinalIgnoreCase);
			string indexSpeed = _settings.Settings.IndexSpeed ?? "normal";
			foreach (string dir in paths)
			{
				if (Directory.Exists(dir))
				{
					await ScanDirectoryAsync(dir, entries, allowedExts, indexSpeed, ct);
				}
			}
			foreach (AliasEntry alias in _settings.Settings.Aliases)
			{
				List<IndexEntry> list = entries;
				IndexEntry indexEntry = new IndexEntry
				{
					Name = alias.Key,
					DisplayName = (alias.Description ?? alias.Key),
					Path = alias.Target,
					AliasType = alias.Type
				};
				IndexEntry indexEntry2 = indexEntry;
				string type = alias.Type;
				if (1 == 0)
				{
				}
				string text = type;
				IndexEntryType type2 = ((!(text == "app")) ? ((!(text == "folder")) ? IndexEntryType.Alias : IndexEntryType.Folder) : IndexEntryType.App);
				if (1 == 0)
				{
				}
				indexEntry2.Type = type2;
				indexEntry.Score = 100;
				list.Add(indexEntry);
			}
			RegisterBuiltInApps(entries);
			ComputeAllSearchCaches(entries);
			_index = entries;
			sw.Stop();
			LastIndexDuration = sw.Elapsed;
			LastIndexCount = entries.Count;
			LogService.Info($"인덱싱 완료: {entries.Count}개 항목 ({sw.Elapsed.TotalSeconds:F1}초)");
			this.IndexRebuilt?.Invoke(this, EventArgs.Empty);
		}
		finally
		{
			_rebuildLock.Release();
		}
	}

	public void StartWatchers()
	{
		StopWatchers();
		foreach (string indexPath in _settings.Settings.IndexPaths)
		{
			string text = Environment.ExpandEnvironmentVariables(indexPath);
			if (Directory.Exists(text))
			{
				try
				{
					FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(text)
					{
						Filter = "*.*",
						IncludeSubdirectories = true,
						NotifyFilter = (NotifyFilters.FileName | NotifyFilters.DirectoryName),
						EnableRaisingEvents = true
					};
					fileSystemWatcher.Created += OnWatcherEvent;
					fileSystemWatcher.Deleted += OnWatcherEvent;
					fileSystemWatcher.Renamed += OnWatcherRenamed;
					_watchers.Add(fileSystemWatcher);
				}
				catch (Exception ex)
				{
					LogService.Warn("FileSystemWatcher 생성 실패: " + text + " - " + ex.Message);
				}
			}
		}
		LogService.Info($"파일 감시 시작: {_watchers.Count}개 경로");
	}

	private void StopWatchers()
	{
		foreach (FileSystemWatcher watcher in _watchers)
		{
			watcher.EnableRaisingEvents = false;
			watcher.Dispose();
		}
		_watchers.Clear();
	}

	private void OnWatcherEvent(object sender, FileSystemEventArgs e)
	{
		ScheduleRebuild(e.FullPath);
	}

	private void OnWatcherRenamed(object sender, RenamedEventArgs e)
	{
		ScheduleRebuild(e.FullPath);
	}

	private void ScheduleRebuild(string triggerPath)
	{
		LogService.Info($"파일 변경 감지: {triggerPath} — {3000}ms 후 재빌드 예약");
		lock (_timerLock)
		{
			_debounceTimer?.Dispose();
			_debounceTimer = new Timer(delegate
			{
				BuildAsync();
			}, null, 3000, -1);
		}
	}

	public void Dispose()
	{
		_debounceTimer?.Dispose();
		StopWatchers();
	}

	private static void RegisterBuiltInApps(List<IndexEntry> entries)
	{
		(string, string[], string)[] array = new(string, string[], string)[43]
		{
			("메모장 (Notepad)", new string[4] { "메모장", "notepad", "note", "txt" }, "C:\\Windows\\notepad.exe"),
			("계산기 (Calculator)", new string[3] { "계산기", "calc", "calculator" }, "calculator:"),
			("캡처 도구 (Snipping Tool)", new string[5] { "캡처", "캡처도구", "snippingtool", "snip", "스크린샷" }, "C:\\Windows\\System32\\SnippingTool.exe"),
			("그림판 (Paint)", new string[3] { "그림판", "mspaint", "paint" }, "C:\\Windows\\System32\\mspaint.exe"),
			("파일 탐색기 (Explorer)", new string[3] { "탐색기", "explorer", "파일탐색기" }, "C:\\Windows\\explorer.exe"),
			("명령 프롬프트 (CMD)", new string[4] { "cmd", "명령프롬프트", "커맨드", "터미널" }, "C:\\Windows\\System32\\cmd.exe"),
			("PowerShell", new string[2] { "powershell", "파워쉘" }, "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"),
			("제어판 (Control Panel)", new string[3] { "제어판", "control", "controlpanel" }, "C:\\Windows\\System32\\control.exe"),
			("작업 관리자 (Task Manager)", new string[3] { "작업관리자", "taskmgr", "taskmanager" }, "C:\\Windows\\System32\\Taskmgr.exe"),
			("원격 데스크톱 (Remote Desktop)", new string[4] { "원격", "mstsc", "rdp", "원격데스크톱" }, "C:\\Windows\\System32\\mstsc.exe"),
			("레지스트리 편집기", new string[2] { "regedit", "레지스트리" }, "C:\\Windows\\regedit.exe"),
			("장치 관리자", new string[3] { "장치관리자", "devmgmt", "devicemanager" }, "C:\\Windows\\System32\\devmgmt.msc"),
			("Microsoft Edge", new string[3] { "edge", "엣지", "브라우저" }, "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe"),
			("Microsoft Excel", new string[6] { "엑셀", "excel", "msexcel", "xlsx", "xls", "csv" }, FindOfficeApp("EXCEL.EXE")),
			("Microsoft PowerPoint", new string[5] { "파워포인트", "powerpoint", "mspowerpoint", "pptx", "ppt" }, FindOfficeApp("POWERPNT.EXE")),
			("Microsoft Word", new string[5] { "워드", "word", "msword", "docx", "doc" }, FindOfficeApp("WINWORD.EXE")),
			("Microsoft Outlook", new string[3] { "아웃룩", "outlook", "메일" }, FindOfficeApp("OUTLOOK.EXE")),
			("Microsoft Teams", new string[2] { "팀즈", "teams" }, FindTeams()),
			("Microsoft OneNote", new string[2] { "원노트", "onenote" }, FindOfficeApp("ONENOTE.EXE")),
			("Microsoft Access", new string[3] { "액세스", "access", "msaccess" }, FindOfficeApp("MSACCESS.EXE")),
			("Visual Studio Code", new string[4] { "vscode", "비주얼스튜디오코드", "코드에디터", "code" }, FindInPath("code.cmd") ?? FindInLocalAppData("Programs\\Microsoft VS Code\\Code.exe")),
			("Visual Studio", new string[5] { "비주얼스튜디오", "devenv", "vs2022", "vs2019", "visualstudio" }, FindInProgramFiles("Microsoft Visual Studio\\2022\\Community\\Common7\\IDE\\devenv.exe") ?? FindInProgramFiles("Microsoft Visual Studio\\2022\\Professional\\Common7\\IDE\\devenv.exe") ?? FindInProgramFiles("Microsoft Visual Studio\\2022\\Enterprise\\Common7\\IDE\\devenv.exe")),
			("Windows Terminal", new string[4] { "윈도우터미널", "wt", "windowsterminal", "터미널" }, FindInLocalAppData("Microsoft\\WindowsApps\\wt.exe")),
			("OneDrive", new string[2] { "원드라이브", "onedrive" }, FindInLocalAppData("Microsoft\\OneDrive\\OneDrive.exe")),
			("Google Chrome", new string[4] { "크롬", "구글크롬", "chrome", "google" }, FindInProgramFiles("Google\\Chrome\\Application\\chrome.exe") ?? FindInLocalAppData("Google\\Chrome\\Application\\chrome.exe")),
			("Mozilla Firefox", new string[3] { "파이어폭스", "불여우", "firefox" }, FindInProgramFiles("Mozilla Firefox\\firefox.exe")),
			("Naver Whale", new string[3] { "웨일", "네이버웨일", "whale" }, FindInLocalAppData("Naver\\Naver Whale\\Application\\whale.exe")),
			("KakaoTalk", new string[4] { "카카오톡", "카톡", "kakaotalk", "kakao" }, FindInLocalAppData("Kakao\\KakaoTalk\\KakaoTalk.exe")),
			("KakaoWork", new string[3] { "카카오워크", "카워크", "kakaowork" }, FindInLocalAppData("Kakao\\KakaoWork\\KakaoWork.exe")),
			("Zoom", new string[2] { "줌", "zoom" }, FindInRoaming("Zoom\\bin\\Zoom.exe") ?? FindInLocalAppData("Zoom\\bin\\Zoom.exe")),
			("Slack", new string[2] { "슬랙", "slack" }, FindInLocalAppData("slack\\slack.exe")),
			("Figma", new string[2] { "피그마", "figma" }, FindInLocalAppData("Figma\\Figma.exe")),
			("Notepad++", new string[3] { "노트패드++", "npp", "notepad++" }, FindInProgramFiles("Notepad++\\notepad++.exe")),
			("7-Zip", new string[4] { "7zip", "7집", "세븐집", "7z" }, FindInProgramFiles("7-Zip\\7zFM.exe")),
			("Bandizip", new string[2] { "반디집", "bandizip" }, FindInProgramFiles("Bandizip\\Bandizip.exe") ?? FindInLocalAppData("Bandizip\\Bandizip.exe")),
			("ALZip", new string[3] { "알집", "alzip", "이스트소프트" }, FindInProgramFiles("ESTsoft\\ALZip\\ALZip.exe")),
			("PotPlayer", new string[3] { "팟플레이어", "팟플", "potplayer" }, FindInProgramFiles("DAUM\\PotPlayer\\PotPlayerMini64.exe") ?? FindInProgramFiles("DAUM\\PotPlayer\\PotPlayerMini.exe")),
			("GOM Player", new string[4] { "곰플레이어", "곰플", "gomplayer", "gom" }, FindInProgramFiles("GRETECH\\GomPlayer\\GOM.EXE")),
			("Adobe Photoshop", new string[4] { "포토샵", "포샵", "photoshop", "ps" }, FindInProgramFiles("Adobe\\Adobe Photoshop 2025\\Photoshop.exe") ?? FindInProgramFiles("Adobe\\Adobe Photoshop 2024\\Photoshop.exe") ?? FindInProgramFiles("Adobe\\Adobe Photoshop 2023\\Photoshop.exe")),
			("Adobe Acrobat", new string[3] { "아크로뱃", "acrobat", "아도비pdf" }, FindInProgramFiles("Adobe\\Acrobat DC\\Acrobat\\Acrobat.exe")),
			("한컴 한글", new string[5] { "한글", "한컴한글", "hwp", "hangul", "아래아한글" }, FindInProgramFiles("HNC\\Hwp\\Hwp.exe") ?? FindInProgramFiles("HNC\\Office NEO\\HOffice NEO\\Bin\\Hwp.exe") ?? FindInProgramFiles("Hnc\\Hwp80\\Hwp.exe")),
			("한컴 한셀", new string[3] { "한셀", "hcell", "한컴스프레드" }, FindInProgramFiles("HNC\\Hwp\\Hcell.exe") ?? FindInProgramFiles("HNC\\Office NEO\\HOffice NEO\\Bin\\Hcell.exe")),
			("한컴 한쇼", new string[3] { "한쇼", "hshow", "한컴프레젠테이션" }, FindInProgramFiles("HNC\\Hwp\\Show.exe") ?? FindInProgramFiles("HNC\\Office NEO\\HOffice NEO\\Bin\\Show.exe"))
		};
		Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
		Dictionary<string, int> dictionary2 = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
		for (int i = 0; i < entries.Count; i++)
		{
			dictionary.TryAdd(entries[i].Name, i);
			dictionary2.TryAdd(entries[i].Path, i);
		}
		(string, string[], string)[] array2 = array;
		for (int j = 0; j < array2.Length; j++)
		{
			var (displayName, array3, text) = array2[j];
			if (string.IsNullOrEmpty(text) || (!text.Contains(":") && !File.Exists(text) && !text.EndsWith(".msc")))
			{
				continue;
			}
			if (dictionary2.TryGetValue(text, out var value))
			{
				entries[value].DisplayName = displayName;
				if (entries[value].Score < 95)
				{
					entries[value].Score = 95;
				}
			}
			bool flag = dictionary2.ContainsKey(text);
			string[] array4 = array3;
			foreach (string text2 in array4)
			{
				if (dictionary.ContainsKey(text2))
				{
					if (dictionary.TryGetValue(text2, out var value2) && entries[value2].Score < 95)
					{
						entries[value2].Score = 95;
					}
					continue;
				}
				bool flag2 = text2.Any((char c) => c >= '가' && c <= '힣');
				if (!flag || flag2)
				{
					entries.Add(new IndexEntry
					{
						Name = text2,
						DisplayName = displayName,
						Path = text,
						Type = IndexEntryType.App,
						Score = 95
					});
					int value3 = (dictionary[text2] = entries.Count - 1);
					if (!flag)
					{
						dictionary2[text] = value3;
						flag = true;
					}
				}
				else
				{
					dictionary[text2] = dictionary2[text];
				}
			}
		}
	}

	private static string? FindOfficeApp(string exeName)
	{
		string[] array = new string[4] { "C:\\Program Files\\Microsoft Office\\root\\Office16", "C:\\Program Files (x86)\\Microsoft Office\\root\\Office16", "C:\\Program Files\\Microsoft Office\\Office16", "C:\\Program Files (x86)\\Microsoft Office\\Office16" };
		string[] array2 = array;
		foreach (string path in array2)
		{
			string text = Path.Combine(path, exeName);
			if (File.Exists(text))
			{
				return text;
			}
		}
		return null;
	}

	private static string? FindTeams()
	{
		string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
		string text = Path.Combine(folderPath, "Microsoft\\Teams\\current\\Teams.exe");
		if (File.Exists(text))
		{
			return text;
		}
		string text2 = Path.Combine(folderPath, "Microsoft\\WindowsApps\\ms-teams.exe");
		return File.Exists(text2) ? text2 : null;
	}

	private static string? FindInPath(string fileName)
	{
		string text = Environment.GetEnvironmentVariable("PATH") ?? "";
		string[] array = text.Split(';');
		foreach (string text2 in array)
		{
			if (!string.IsNullOrWhiteSpace(text2))
			{
				string text3 = Path.Combine(text2.Trim(), fileName);
				if (File.Exists(text3))
				{
					return text3;
				}
			}
		}
		return null;
	}

	private static string? FindInLocalAppData(string relativePath)
	{
		string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
		string text = Path.Combine(folderPath, relativePath);
		return File.Exists(text) ? text : null;
	}

	private static string? FindInProgramFiles(string relativePath)
	{
		string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
		string folderPath2 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
		string text = Path.Combine(folderPath, relativePath);
		if (File.Exists(text))
		{
			return text;
		}
		string text2 = Path.Combine(folderPath2, relativePath);
		return File.Exists(text2) ? text2 : null;
	}

	private static string? FindInRoaming(string relativePath)
	{
		string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
		string text = Path.Combine(folderPath, relativePath);
		return File.Exists(text) ? text : null;
	}

	private static void ComputeAllSearchCaches(List<IndexEntry> entries)
	{
		foreach (IndexEntry entry in entries)
		{
			ComputeSearchCache(entry);
		}
	}

	private static void ComputeSearchCache(IndexEntry entry)
	{
		entry.NameLower = entry.Name.ToLowerInvariant();
		entry.NameJamo = FuzzyEngine.DecomposeToJamo(entry.NameLower);
		StringBuilder stringBuilder = new StringBuilder(entry.NameLower.Length);
		string nameLower = entry.NameLower;
		foreach (char hangul in nameLower)
		{
			char chosung = FuzzyEngine.GetChosung(hangul);
			if (chosung != 0)
			{
				stringBuilder.Append(chosung);
			}
		}
		entry.NameChosung = stringBuilder.ToString();
	}

	private static (int batchSize, int delayMs) GetThrottle(string speed)
	{
		if (1 == 0)
		{
		}
		(int, int) result = ((speed == "fast") ? (500, 0) : ((!(speed == "slow")) ? (150, 5) : (50, 15)));
		if (1 == 0)
		{
		}
		return result;
	}

	private static async Task ScanDirectoryAsync(string dir, List<IndexEntry> entries, HashSet<string> allowedExts, string indexSpeed, CancellationToken ct)
	{
		var (batchSize, delayMs) = GetThrottle(indexSpeed);
		await Task.Run(async delegate
		{
			try
			{
				int count = 0;
				foreach (string file in Directory.EnumerateFiles(dir, "*.*", SearchOption.AllDirectories))
				{
					ct.ThrowIfCancellationRequested();
					string ext = Path.GetExtension(file).ToLowerInvariant();
					if (allowedExts.Count <= 0 || allowedExts.Contains(ext))
					{
						string name = Path.GetFileNameWithoutExtension(file);
						if (!string.IsNullOrEmpty(name))
						{
							if (1 == 0)
							{
							}
							IndexEntryType indexEntryType;
							switch (ext)
							{
							case ".exe":
								indexEntryType = IndexEntryType.App;
								break;
							case ".lnk":
							case ".url":
								indexEntryType = IndexEntryType.File;
								break;
							default:
								indexEntryType = IndexEntryType.File;
								break;
							}
							if (1 == 0)
							{
							}
							IndexEntryType type = indexEntryType;
							List<IndexEntry> list = entries;
							IndexEntry indexEntry = new IndexEntry
							{
								Name = name
							};
							IndexEntry indexEntry2 = indexEntry;
							bool flag;
							switch (ext)
							{
							case ".exe":
							case ".lnk":
							case ".url":
								flag = true;
								break;
							default:
								flag = false;
								break;
							}
							indexEntry2.DisplayName = (flag ? name : (name + ext));
							indexEntry.Path = file;
							indexEntry.Type = type;
							list.Add(indexEntry);
							int num2;
							if (delayMs > 0)
							{
								int num = count + 1;
								count = num;
								num2 = ((num % batchSize == 0) ? 1 : 0);
							}
							else
							{
								num2 = 0;
							}
							if (num2 != 0)
							{
								await Task.Delay(delayMs, ct);
							}
						}
					}
				}
				foreach (string subDir in Directory.EnumerateDirectories(dir, "*", SearchOption.TopDirectoryOnly))
				{
					ct.ThrowIfCancellationRequested();
					string name2 = Path.GetFileName(subDir);
					if (!name2.StartsWith("."))
					{
						entries.Add(new IndexEntry
						{
							Name = name2,
							DisplayName = name2,
							Path = subDir,
							Type = IndexEntryType.Folder
						});
					}
				}
			}
			catch (UnauthorizedAccessException ex)
			{
				UnauthorizedAccessException ex2 = ex;
				LogService.Warn("폴더 접근 불가 (건너뜀): " + dir + " - " + ex2.Message);
			}
		}, ct);
	}
}
