using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using AxCopilot.Services;

namespace AxCopilot.Core;

/// <summary>
/// 모든 앱에서 ';키워드 + Space/Enter' 패턴을 감지해 스니펫을 자동 확장합니다.
/// InputListener.KeyFilter에 <see cref="HandleKey"/> 을 등록하여 사용합니다.
/// </summary>
public class SnippetExpander
{
    private readonly SettingsService _settings;
    private readonly StringBuilder _buffer = new();
    private bool _tracking;

    // VK 상수
    private const ushort VK_BACK    = 0x08;
    private const int    VK_ESCAPE  = 0x1B;
    private const int    VK_SPACE   = 0x20;
    private const int    VK_RETURN  = 0x0D;
    private const int    VK_OEM_1   = 0xBA; // ; (US QWERTY)
    private const int    VK_SHIFT   = 0x10;
    private const int    VK_CONTROL = 0x11;
    private const int    VK_MENU    = 0x12;
    private const ushort VK_CTRL_US = 0x11;

    // 방향키 / 기능키 — 이 키가 오면 버퍼 초기화
    private static readonly HashSet<int> ClearKeys = new()
    {
        0x21, 0x22, 0x23, 0x24,  // PgUp, PgDn, End, Home
        0x25, 0x26, 0x27, 0x28,  // ←↑→↓
        0x2E,                     // Delete
    };

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

    /// <summary>
    /// InputListener.KeyFilter에 등록할 메서드.
    /// true → 해당 키 이벤트 소비(차단), false → 통과.
    /// 훅 스레드에서 호출되므로 신속히 처리해야 합니다.
    /// </summary>
    public bool HandleKey(int vkCode)
    {
        // 자동 확장 비활성화 시 즉시 통과
        if (!_settings.Settings.Launcher.SnippetAutoExpand) return false;

        // Ctrl/Alt 조합은 무시 (단축키와 충돌 방지)
        if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0) { _tracking = false; _buffer.Clear(); return false; }
        if ((GetAsyncKeyState(VK_MENU)    & 0x8000) != 0) { _tracking = false; _buffer.Clear(); return false; }

        // ─── 트리거 시작: ';' 입력 ──────────────────────────────────────────
        if (vkCode == VK_OEM_1 && (GetAsyncKeyState(VK_SHIFT) & 0x8000) == 0)
        {
            _tracking = true;
            _buffer.Clear();
            _buffer.Append(';');
            return false; // ';'는 소비하지 않고 앱으로 전달
        }

        if (!_tracking) return false;

        // ─── 영문자/숫자 — 버퍼에 추가 ─────────────────────────────────────
        if ((vkCode >= 0x41 && vkCode <= 0x5A) ||  // A-Z
            (vkCode >= 0x30 && vkCode <= 0x39) ||  // 0-9
            (vkCode >= 0x60 && vkCode <= 0x69) ||  // Numpad 0-9
            vkCode == 0xBD)                         // -
        {
            bool shifted = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0;
            char c = VkToChar(vkCode, shifted);
            if (c != '\0') _buffer.Append(char.ToLowerInvariant(c));
            return false;
        }

        // ─── BackSpace — 버퍼에서 한 글자 제거 ──────────────────────────────
        if (vkCode == VK_BACK)
        {
            if (_buffer.Length > 1)
                _buffer.Remove(_buffer.Length - 1, 1);
            else
            {
                _tracking = false;
                _buffer.Clear();
            }
            return false;
        }

        // ─── Space / Enter — 스니펫 확장 시도 ───────────────────────────────
        if (vkCode == VK_SPACE || vkCode == VK_RETURN)
        {
            if (_buffer.Length > 1) // ';' 이후 한 글자 이상
            {
                var keyword = _buffer.ToString(1, _buffer.Length - 1);
                _tracking = false;
                _buffer.Clear();

                var snippet = _settings.Settings.Snippets.FirstOrDefault(
                    s => s.Key.Equals(keyword, StringComparison.OrdinalIgnoreCase));

                if (snippet != null)
                {
                    var expanded   = ExpandVariables(snippet.Content);
                    var deleteCount = keyword.Length + 1; // ';' + keyword
                    // 트리거 키(Space/Enter) 소비 후 UI 스레드에서 확장 처리
                    Application.Current.Dispatcher.BeginInvoke(() =>
                        PasteExpansion(expanded, deleteCount));
                    return true; // 트리거 키 소비
                }
            }
            _tracking = false;
            _buffer.Clear();
            return false;
        }

        // ─── Escape / 방향키 / 기능키 — 추적 중단 ───────────────────────────
        if (vkCode == VK_ESCAPE || ClearKeys.Contains(vkCode) || vkCode >= 0x70)
        {
            _tracking = false;
            _buffer.Clear();
        }

        return false;
    }

    // ─── 확장 실행 (UI 스레드) ───────────────────────────────────────────────

    private static void PasteExpansion(string text, int deleteCount)
    {
        try
        {
            // 1. Backspace × deleteCount
            var inputs = new INPUT[deleteCount * 2];
            for (int i = 0; i < deleteCount; i++)
            {
                inputs[i * 2] = MakeKeyInput(VK_BACK, false);
                inputs[i * 2 + 1] = MakeKeyInput(VK_BACK, true);
            }
            SendInput((uint)inputs.Length, inputs, Marshal.SizeOf<INPUT>());

            // 2. 클립보드 → Ctrl+V
            Clipboard.SetText(text);

            var paste = new[]
            {
                MakeKeyInput(VK_CTRL_US, false),
                MakeKeyInput(0x56, false),  // V
                MakeKeyInput(0x56, true),
                MakeKeyInput(VK_CTRL_US, true),
            };
            SendInput((uint)paste.Length, paste, Marshal.SizeOf<INPUT>());

            LogService.Info($"스니펫 확장 완료: {deleteCount}자 삭제 후 붙여넣기");
        }
        catch (Exception ex)
        {
            LogService.Warn($"스니펫 확장 실패: {ex.Message}");
        }
    }

    private static INPUT MakeKeyInput(ushort vk, bool keyUp)
    {
        var input = new INPUT { type = 1 };
        input.u.ki.wVk     = vk;
        input.u.ki.dwFlags = keyUp ? 0x0002u : 0u; // KEYEVENTF_KEYUP
        return input;
    }

    // ─── 변수 치환 ────────────────────────────────────────────────────────────

    private static string ExpandVariables(string content)
    {
        var now = DateTime.Now;
        return content
            .Replace("{date}",     now.ToString("yyyy-MM-dd"))
            .Replace("{time}",     now.ToString("HH:mm:ss"))
            .Replace("{datetime}", now.ToString("yyyy-MM-dd HH:mm:ss"))
            .Replace("{year}",     now.Year.ToString())
            .Replace("{month}",    now.Month.ToString("D2"))
            .Replace("{day}",      now.Day.ToString("D2"));
    }

    // ─── VK → Char 매핑 (US QWERTY 기준) ────────────────────────────────────

    private static char VkToChar(int vk, bool shifted)
    {
        if (vk >= 0x41 && vk <= 0x5A)
            return shifted ? (char)vk : char.ToLowerInvariant((char)vk);
        if (vk >= 0x30 && vk <= 0x39)
            return shifted ? ")!@#$%^&*("[vk - 0x30] : (char)vk;
        if (vk >= 0x60 && vk <= 0x69)
            return (char)('0' + (vk - 0x60));
        if (vk == 0xBD)
            return shifted ? '_' : '-';
        return '\0';
    }

    // ─── P/Invoke ────────────────────────────────────────────────────────────

    [DllImport("user32.dll")]
    private static extern short GetAsyncKeyState(int vKey);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);

    [StructLayout(LayoutKind.Sequential)]
    private struct INPUT
    {
        public int type;
        public InputUnion u;
    }

    [StructLayout(LayoutKind.Explicit)]
    private struct InputUnion
    {
        [FieldOffset(0)] public MOUSEINPUT  mi;
        [FieldOffset(0)] public KEYBDINPUT  ki;
        [FieldOffset(0)] public HARDWAREINPUT hi;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct KEYBDINPUT
    {
        public ushort wVk;
        public ushort wScan;
        public uint   dwFlags;
        public uint   time;
        public IntPtr dwExtraInfo;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct MOUSEINPUT
    {
        public int    dx;
        public int    dy;
        public uint   mouseData;
        public uint   dwFlags;
        public uint   time;
        public IntPtr dwExtraInfo;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct HARDWAREINPUT
    {
        public uint   uMsg;
        public ushort wParamL;
        public ushort wParamH;
    }
}
