using System.Diagnostics;
using System.IO;
using AxCopilot.Models;

namespace AxCopilot.Services;

/// <summary>
/// 파일/앱 인덱싱 서비스. Fuzzy 검색의 데이터 소스를 관리합니다.
/// FileSystemWatcher로 변경 시 자동 재빌드(3초 디바운스)합니다.
/// </summary>
public partial class IndexService : IDisposable
{
    private readonly SettingsService _settings;
    private List<IndexEntry> _index = new();
    private readonly List<FileSystemWatcher> _watchers = new();
    private System.Threading.Timer? _debounceTimer;
    private readonly object _timerLock = new();
    private const int DebounceMs = 3000;
    private readonly SemaphoreSlim _rebuildLock = new(1, 1);

    public IReadOnlyList<IndexEntry> Entries => _index;

    public event EventHandler? IndexRebuilt;
    public TimeSpan LastIndexDuration { get; private set; }
    public int LastIndexCount { get; private set; }

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

    /// <summary>
    /// 앱 시작 시 전체 인덱스 빌드 후 FileSystemWatcher 시작.
    /// </summary>
    public async Task BuildAsync(CancellationToken ct = default)
    {
        await _rebuildLock.WaitAsync(ct);
        var sw = Stopwatch.StartNew();
        try
        {
            var entries = new List<IndexEntry>();
            var paths = _settings.Settings.IndexPaths
                .Select(p => Environment.ExpandEnvironmentVariables(p));
            var allowedExts = new HashSet<string>(
                _settings.Settings.IndexExtensions
                    .Select(e => e.ToLowerInvariant().StartsWith(".") ? e.ToLowerInvariant() : "." + e.ToLowerInvariant()),
                StringComparer.OrdinalIgnoreCase);

            var indexSpeed = _settings.Settings.IndexSpeed ?? "normal";

            foreach (var dir in paths)
            {
                if (!Directory.Exists(dir)) continue;
                await ScanDirectoryAsync(dir, entries, allowedExts, indexSpeed, ct);
            }

            // Alias도 인덱스에 포함 (type에 따라 IndexEntryType 구분)
            foreach (var alias in _settings.Settings.Aliases)
            {
                entries.Add(new IndexEntry
                {
                    Name        = alias.Key,
                    DisplayName = alias.Description ?? alias.Key,
                    Path        = alias.Target,
                    AliasType   = alias.Type,
                    Type        = alias.Type switch
                    {
                        "app"    => IndexEntryType.App,
                        "folder" => IndexEntryType.Folder,
                        _        => IndexEntryType.Alias   // url, batch, api, clipboard
                    },
                    Score = 100  // Alias는 최우선
                });
            }

            // Built-in 앱 별칭 (한글+영문 이름으로 즉시 실행)
            RegisterBuiltInApps(entries);

            // 검색 가속 캐시 일괄 계산 (ToLower·자모·초성을 빌드 시 1회만 수행)
            ComputeAllSearchCaches(entries);

            _index = entries;
            sw.Stop();
            LastIndexDuration = sw.Elapsed;
            LastIndexCount = entries.Count;
            LogService.Info($"인덱싱 완료: {entries.Count}개 항목 ({sw.Elapsed.TotalSeconds:F1}초)");
            IndexRebuilt?.Invoke(this, EventArgs.Empty);
        }
        finally
        {
            _rebuildLock.Release();
        }
    }

    /// <summary>
    /// 인덱스 경로에 대한 FileSystemWatcher를 시작합니다.
    /// BuildAsync() 완료 후 호출하세요.
    /// </summary>
    public void StartWatchers()
    {
        StopWatchers();

        foreach (var rawPath in _settings.Settings.IndexPaths)
        {
            var dir = Environment.ExpandEnvironmentVariables(rawPath);
            if (!Directory.Exists(dir)) continue;

            try
            {
                var w = new FileSystemWatcher(dir)
                {
                    Filter                = "*.*",
                    IncludeSubdirectories = true,
                    NotifyFilter          = NotifyFilters.FileName | NotifyFilters.DirectoryName,
                    EnableRaisingEvents   = true
                };
                w.Created += OnWatcherEvent;
                w.Deleted += OnWatcherEvent;
                w.Renamed += OnWatcherRenamed;
                _watchers.Add(w);
            }
            catch (Exception ex)
            {
                LogService.Warn($"FileSystemWatcher 생성 실패: {dir} - {ex.Message}");
            }
        }

        LogService.Info($"파일 감시 시작: {_watchers.Count}개 경로");
    }

    private void StopWatchers()
    {
        foreach (var w in _watchers)
        {
            w.EnableRaisingEvents = false;
            w.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} — {DebounceMs}ms 후 재빌드 예약");
        lock (_timerLock)
        {
            _debounceTimer?.Dispose();
            _debounceTimer = new System.Threading.Timer(__ =>
            {
                _ = BuildAsync();
            }, null, DebounceMs, System.Threading.Timeout.Infinite);
        }
    }

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

    /// <summary>
    /// 자주 사용하는 Windows 기본 앱을 한글+영문 이름으로 등록합니다.
    /// 실제로 존재하는 경우에만 인덱스에 추가됩니다.
    /// </summary>
    private static void RegisterBuiltInApps(List<IndexEntry> entries)
    {
        // (displayName, keywords[], exePath)
        var builtIns = new (string Display, string[] Names, string? Exe)[]
        {
            // 메모장
            ("메모장 (Notepad)", new[] { "메모장", "notepad", "note", "txt" },
                @"C:\Windows\notepad.exe"),
            // 계산기
            ("계산기 (Calculator)", new[] { "계산기", "calc", "calculator" },
                "calculator:"),  // UWP protocol
            // 캡처 도구 (Snipping Tool)
            ("캡처 도구 (Snipping Tool)", new[] { "캡처", "캡처도구", "snippingtool", "snip", "스크린샷" },
                @"C:\Windows\System32\SnippingTool.exe"),
            // 그림판
            ("그림판 (Paint)", new[] { "그림판", "mspaint", "paint" },
                @"C:\Windows\System32\mspaint.exe"),
            // 탐색기
            ("파일 탐색기 (Explorer)", new[] { "탐색기", "explorer", "파일탐색기" },
                @"C:\Windows\explorer.exe"),
            // 명령 프롬프트
            ("명령 프롬프트 (CMD)", new[] { "cmd", "명령프롬프트", "커맨드", "터미널" },
                @"C:\Windows\System32\cmd.exe"),
            // PowerShell
            ("PowerShell", new[] { "powershell", "파워쉘" },
                @"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"),
            // 제어판
            ("제어판 (Control Panel)", new[] { "제어판", "control", "controlpanel" },
                @"C:\Windows\System32\control.exe"),
            // 작업 관리자
            ("작업 관리자 (Task Manager)", new[] { "작업관리자", "taskmgr", "taskmanager" },
                @"C:\Windows\System32\Taskmgr.exe"),
            // 원격 데스크톱
            ("원격 데스크톱 (Remote Desktop)", new[] { "원격", "mstsc", "rdp", "원격데스크톱" },
                @"C:\Windows\System32\mstsc.exe"),
            // 레지스트리 편집기
            ("레지스트리 편집기", new[] { "regedit", "레지스트리" },
                @"C:\Windows\regedit.exe"),
            // 장치 관리자
            ("장치 관리자", new[] { "장치관리자", "devmgmt", "devicemanager" },
                @"C:\Windows\System32\devmgmt.msc"),
            // Microsoft Edge
            ("Microsoft Edge", new[] { "edge", "엣지", "브라우저" },
                @"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"),
            // Excel — 확장자 + MS prefix + 한글
            ("Microsoft Excel", new[] { "엑셀", "excel", "msexcel", "xlsx", "xls", "csv" },
                FindOfficeApp("EXCEL.EXE")),
            // PowerPoint
            ("Microsoft PowerPoint", new[] { "파워포인트", "powerpoint", "mspowerpoint", "pptx", "ppt" },
                FindOfficeApp("POWERPNT.EXE")),
            // Word
            ("Microsoft Word", new[] { "워드", "word", "msword", "docx", "doc" },
                FindOfficeApp("WINWORD.EXE")),
            // Outlook
            ("Microsoft Outlook", new[] { "아웃룩", "outlook", "메일" },
                FindOfficeApp("OUTLOOK.EXE")),
            // Teams
            ("Microsoft Teams", new[] { "팀즈", "teams" },
                FindTeams()),
            // OneNote
            ("Microsoft OneNote", new[] { "원노트", "onenote" },
                FindOfficeApp("ONENOTE.EXE")),
            // Access
            ("Microsoft Access", new[] { "액세스", "access", "msaccess" },
                FindOfficeApp("MSACCESS.EXE")),
            // VS Code
            ("Visual Studio Code", new[] { "vscode", "비주얼스튜디오코드", "코드에디터", "code" },
                FindInPath("code.cmd") ?? FindInLocalAppData(@"Programs\Microsoft VS Code\Code.exe")),
            // Visual Studio
            ("Visual Studio", new[] { "비주얼스튜디오", "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
            ("Windows Terminal", new[] { "윈도우터미널", "wt", "windowsterminal", "터미널" },
                FindInLocalAppData(@"Microsoft\WindowsApps\wt.exe")),
            // OneDrive
            ("OneDrive", new[] { "원드라이브", "onedrive" },
                FindInLocalAppData(@"Microsoft\OneDrive\OneDrive.exe")),
            // Google Chrome
            ("Google Chrome", new[] { "크롬", "구글크롬", "chrome", "google" },
                FindInProgramFiles(@"Google\Chrome\Application\chrome.exe")
                ?? FindInLocalAppData(@"Google\Chrome\Application\chrome.exe")),
            // Firefox
            ("Mozilla Firefox", new[] { "파이어폭스", "불여우", "firefox" },
                FindInProgramFiles(@"Mozilla Firefox\firefox.exe")),
            // Naver Whale
            ("Naver Whale", new[] { "웨일", "네이버웨일", "whale" },
                FindInLocalAppData(@"Naver\Naver Whale\Application\whale.exe")),
            // KakaoTalk
            ("KakaoTalk", new[] { "카카오톡", "카톡", "kakaotalk", "kakao" },
                FindInLocalAppData(@"Kakao\KakaoTalk\KakaoTalk.exe")),
            // KakaoWork
            ("KakaoWork", new[] { "카카오워크", "카워크", "kakaowork" },
                FindInLocalAppData(@"Kakao\KakaoWork\KakaoWork.exe")),
            // Zoom
            ("Zoom", new[] { "줌", "zoom" },
                FindInRoaming(@"Zoom\bin\Zoom.exe")
                ?? FindInLocalAppData(@"Zoom\bin\Zoom.exe")),
            // Slack
            ("Slack", new[] { "슬랙", "slack" },
                FindInLocalAppData(@"slack\slack.exe")),
            // Figma
            ("Figma", new[] { "피그마", "figma" },
                FindInLocalAppData(@"Figma\Figma.exe")),
            // Notepad++
            ("Notepad++", new[] { "노트패드++", "npp", "notepad++" },
                FindInProgramFiles(@"Notepad++\notepad++.exe")),
            // 7-Zip
            ("7-Zip", new[] { "7zip", "7집", "세븐집", "7z" },
                FindInProgramFiles(@"7-Zip\7zFM.exe")),
            // Bandizip
            ("Bandizip", new[] { "반디집", "bandizip" },
                FindInProgramFiles(@"Bandizip\Bandizip.exe")
                ?? FindInLocalAppData(@"Bandizip\Bandizip.exe")),
            // ALZip
            ("ALZip", new[] { "알집", "alzip", "이스트소프트" },
                FindInProgramFiles(@"ESTsoft\ALZip\ALZip.exe")),
            // PotPlayer
            ("PotPlayer", new[] { "팟플레이어", "팟플", "potplayer" },
                FindInProgramFiles(@"DAUM\PotPlayer\PotPlayerMini64.exe")
                ?? FindInProgramFiles(@"DAUM\PotPlayer\PotPlayerMini.exe")),
            // GOM Player
            ("GOM Player", new[] { "곰플레이어", "곰플", "gomplayer", "gom" },
                FindInProgramFiles(@"GRETECH\GomPlayer\GOM.EXE")),
            // Adobe Photoshop
            ("Adobe Photoshop", new[] { "포토샵", "포샵", "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
            ("Adobe Acrobat", new[] { "아크로뱃", "acrobat", "아도비pdf" },
                FindInProgramFiles(@"Adobe\Acrobat DC\Acrobat\Acrobat.exe")),
            // 한컴 한글
            ("한컴 한글", new[] { "한글", "한컴한글", "hwp", "hangul", "아래아한글" },
                FindInProgramFiles(@"HNC\Hwp\Hwp.exe")
                ?? FindInProgramFiles(@"HNC\Office NEO\HOffice NEO\Bin\Hwp.exe")
                ?? FindInProgramFiles(@"Hnc\Hwp80\Hwp.exe")),
            // 한컴 한셀
            ("한컴 한셀", new[] { "한셀", "hcell", "한컴스프레드" },
                FindInProgramFiles(@"HNC\Hwp\Hcell.exe")
                ?? FindInProgramFiles(@"HNC\Office NEO\HOffice NEO\Bin\Hcell.exe")),
            // 한컴 한쇼
            ("한컴 한쇼", new[] { "한쇼", "hshow", "한컴프레젠테이션" },
                FindInProgramFiles(@"HNC\Hwp\Show.exe")
                ?? FindInProgramFiles(@"HNC\Office NEO\HOffice NEO\Bin\Show.exe")),
        };

        // 기존 항목의 이름 → 인덱스 매핑 + Path 기반 중복 방지
        var nameToIdx = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
        var pathToIdx = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
        for (int i = 0; i < entries.Count; i++)
        {
            nameToIdx.TryAdd(entries[i].Name, i);
            pathToIdx.TryAdd(entries[i].Path, i);
        }

        foreach (var (display, names, exe) in builtIns)
        {
            if (string.IsNullOrEmpty(exe)) continue;
            // UWP protocol은 파일 존재 체크 불필요
            if (!exe.Contains(":") && !File.Exists(exe) && !exe.EndsWith(".msc")) continue;

            // 같은 경로가 이미 인덱스에 있으면 DisplayName과 Score만 업데이트
            if (pathToIdx.TryGetValue(exe, out var existingIdx))
            {
                entries[existingIdx].DisplayName = display;
                if (entries[existingIdx].Score < 95) entries[existingIdx].Score = 95;
            }

            // ── 키워드별 IndexEntry 등록 ─────────────────────────────────────────
            // - 한글 이름: 항상 IndexEntry로 추가 (앱이 이미 스캔됐어도)
            //   → "엑" 입력 시 "엑셀" IndexEntry의 StartsWith("엑")으로 매칭
            // - 영문/약어: 앱이 미스캔 상태일 때 첫 키워드만 대표 항목으로 등록
            // - 경로 중복은 CommandResolver 레이어에서 seenPaths로 최고점만 표시
            bool mainAdded = pathToIdx.ContainsKey(exe);
            foreach (var name in names)
            {
                if (nameToIdx.ContainsKey(name))
                {
                    // 이미 같은 이름 존재 → Score만 부스트
                    if (nameToIdx.TryGetValue(name, out var idx) && entries[idx].Score < 95)
                        entries[idx].Score = 95;
                    continue;
                }

                // 한글 음절이 포함된 이름은 항상 IndexEntry로 추가
                bool isKoreanName = name.Any(c => c >= '\uAC00' && c <= '\uD7A3');

                if (!mainAdded || isKoreanName)
                {
                    entries.Add(new IndexEntry
                    {
                        Name        = name,
                        DisplayName = display,
                        Path        = exe,
                        Type        = IndexEntryType.App,
                        Score       = 95
                    });
                    var newIdx = entries.Count - 1;
                    nameToIdx[name] = newIdx;
                    if (!mainAdded)
                    {
                        pathToIdx[exe] = newIdx;
                        mainAdded = true;
                    }
                }
                else
                {
                    // 영문/약어는 pathToIdx 참조만 (FuzzyEngine은 영문 앱명 직접 매칭)
                    nameToIdx[name] = pathToIdx[exe];
                }
            }
        }
    }

}

public class IndexEntry
{
    public string Name        { get; set; } = "";
    public string DisplayName { get; set; } = "";
    public string Path        { get; set; } = "";
    public IndexEntryType Type { get; set; }
    public int Score          { get; set; } = 0;
    /// <summary>Alias 항목의 원본 type 문자열 (url | folder | app | batch | api | clipboard)</summary>
    public string? AliasType  { get; set; }

    // ─ 검색 가속 캐시 (BuildAsync 시 1회 계산, 검색마다 재계산 방지) ──────
    /// <summary>ToLowerInvariant() 결과 캐시</summary>
    public string NameLower   { get; set; } = "";
    /// <summary>자모 분리 문자열 캐시 (DecomposeToJamo 결과)</summary>
    public string NameJamo    { get; set; } = "";
    /// <summary>초성만 연결한 문자열 캐시 (예: "엑셀" → "ㅇㅅ")</summary>
    public string NameChosung { get; set; } = "";
}

public enum IndexEntryType { App, File, Alias, Folder }
