using System.Collections.Concurrent;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;

namespace AxCopilot.Services;

/// <summary>
/// Shell32 SHGetFileInfo를 사용하여 파일/폴더의 Windows 아이콘을 추출·캐시합니다.
/// 캐시 위치: %LOCALAPPDATA%\AxCopilot\IconCache\{확장자}.png
/// GetIconPath()는 캐시 미스 시 null을 반환하고 백그라운드에서 추출을 시작합니다.
/// WarmUp()을 앱 시작 시 호출하면 자주 쓰는 확장자를 미리 준비합니다.
/// </summary>
internal static class IconCacheService
{
    private static readonly string _cacheDir = Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
        "AxCopilot", "IconCache");

    /// <summary>key(확장자 또는 "folder") → PNG 캐시 파일 경로 (null = 추출 실패)</summary>
    private static readonly ConcurrentDictionary<string, string?> _cache =
        new(StringComparer.OrdinalIgnoreCase);

    private static volatile bool _warmupDone;

    // ─── Win32 ──────────────────────────────────────────────────────────────

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    private struct SHFILEINFO
    {
        public IntPtr hIcon;
        public int    iIcon;
        public uint   dwAttributes;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szDisplayName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]  public string szTypeName;
    }

    [DllImport("shell32.dll", CharSet = CharSet.Auto)]
    private static extern IntPtr SHGetFileInfo(
        string pszPath, uint dwFileAttributes,
        ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool DestroyIcon(IntPtr hIcon);

    private const uint SHGFI_ICON              = 0x000000100;
    private const uint SHGFI_LARGEICON         = 0x000000000;
    private const uint SHGFI_USEFILEATTRIBUTES = 0x000000010;
    private const uint FILE_ATTRIBUTE_NORMAL    = 0x00000080;
    private const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010;

    // ─── 공개 API ───────────────────────────────────────────────────────────

    /// <summary>
    /// 파일 확장자 기반 아이콘 PNG 경로를 반환합니다.
    /// 캐시가 없으면 백그라운드에서 추출을 시작하고 null을 반환합니다.
    /// </summary>
    public static string? GetIconPath(string filePath, bool isDirectory = false)
    {
        var key = isDirectory ? "folder" : GetExtKey(filePath);
        if (_cache.TryGetValue(key, out var cached)) return cached;

        _ = ExtractAsync(filePath, key, isDirectory);
        return null;
    }

    /// <summary>
    /// 앱 시작 시 자주 쓰는 파일 형식의 아이콘을 미리 추출합니다.
    /// 중복 실행은 자동으로 건너뜁니다.
    /// </summary>
    public static void WarmUp()
    {
        if (_warmupDone) return;
        _warmupDone = true;

        // 캐시 디렉터리에 이미 저장된 PNG 로드 (재시작 시 빠른 복원)
        _ = Task.Run(() =>
        {
            try
            {
                Directory.CreateDirectory(_cacheDir);
                foreach (var file in Directory.GetFiles(_cacheDir, "*.png"))
                {
                    var key = Path.GetFileNameWithoutExtension(file);
                    _cache.TryAdd(key, file);
                }
            }
            catch { }
        });

        // 자주 쓰는 확장자 사전 추출 (UI 스레드 부하를 분산하기 위해 순차 실행)
        _ = Task.Run(async () =>
        {
            await Task.Delay(2000); // 앱 초기화 완료 후 추출 시작
            var common = new (string Path, string Key, bool IsDir)[]
            {
                ("dummy.exe",  "exe",    false),
                ("dummy.lnk",  "lnk",    false),
                ("dummy.pdf",  "pdf",    false),
                ("dummy.docx", "docx",   false),
                ("dummy.xlsx", "xlsx",   false),
                ("dummy.pptx", "pptx",   false),
                ("dummy.txt",  "txt",    false),
                ("dummy.png",  "png",    false),
                ("dummy.zip",  "zip",    false),
                ("dummy.mp4",  "mp4",    false),
                ("C:\\",       "folder", true),
            };
            foreach (var (path, key, isDir) in common)
            {
                if (_cache.ContainsKey(key)) continue;
                await ExtractAsync(path, key, isDir);
                await Task.Delay(80); // UI 스레드 부하 분산
            }
        });
    }

    // ─── 내부 ───────────────────────────────────────────────────────────────

    private static string GetExtKey(string filePath)
    {
        var ext = Path.GetExtension(filePath).ToLowerInvariant();
        return string.IsNullOrEmpty(ext) ? "noext" : ext.TrimStart('.');
    }

    private static async Task ExtractAsync(string filePath, string key, bool isDirectory)
    {
        if (_cache.ContainsKey(key)) return;

        try
        {
            Directory.CreateDirectory(_cacheDir);
            var cachePath = Path.Combine(_cacheDir, key + ".png");

            // 파일이 이미 캐시에 있으면 메모리 캐시에 등록만
            if (File.Exists(cachePath))
            {
                _cache.TryAdd(key, cachePath);
                return;
            }

            // SHGetFileInfo + HICON → PNG 변환은 반드시 STA(Dispatcher) 스레드에서
            var dispatcher = Application.Current?.Dispatcher;
            if (dispatcher == null) return;

            var saved = await dispatcher.InvokeAsync(() =>
                TryExtractAndSave(filePath, cachePath, isDirectory));

            _cache.TryAdd(key, saved ? cachePath : null);
        }
        catch (Exception ex)
        {
            LogService.Warn($"[IconCache] '{key}' 추출 실패: {ex.Message}");
            _cache.TryAdd(key, null);
        }
    }

    /// <summary>Dispatcher(STA) 스레드에서 HICON을 PNG로 변환하여 저장합니다.</summary>
    private static bool TryExtractAndSave(string filePath, string cachePath, bool isDirectory)
    {
        var info  = new SHFILEINFO();
        uint flags = SHGFI_ICON | SHGFI_LARGEICON | SHGFI_USEFILEATTRIBUTES;
        uint attrs = isDirectory ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL;

        SHGetFileInfo(filePath, attrs, ref info, (uint)Marshal.SizeOf(info), flags);
        if (info.hIcon == IntPtr.Zero) return false;

        try
        {
            var bitmap = Imaging.CreateBitmapSourceFromHIcon(
                info.hIcon, Int32Rect.Empty,
                BitmapSizeOptions.FromEmptyOptions());
            bitmap.Freeze();

            var encoder = new PngBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(bitmap));
            using var stream = File.OpenWrite(cachePath);
            encoder.Save(stream);
            return true;
        }
        catch
        {
            return false;
        }
        finally
        {
            DestroyIcon(info.hIcon);
        }
    }
}
