#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>
#include <objbase.h>
#include <windowsx.h>
#include <commctrl.h>
#include <shellapi.h>
#include <dwmapi.h>
#include <gdiplus.h>
#include <uxtheme.h>
#pragma comment(lib, "comctl32.lib")
#pragma comment(lib, "dwmapi.lib")
#pragma comment(lib, "gdiplus.lib")
#pragma comment(lib, "uxtheme.lib")
using namespace Gdiplus;

#include <string>
#include <vector>
#include <cstring>
#include <cmath>
#include <thread>
#include <mutex>
#include <atomic>
#include <SDL.h>
#include "transcriber.h"
#include "text_util.h"
#include "whisper.h"
#include "settings.h"
#include "logging.h"
#include "timing.h"

#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif
#ifndef DWMWA_BORDER_COLOR
#define DWMWA_BORDER_COLOR 34
#endif
#ifndef DWMWA_CAPTION_COLOR
#define DWMWA_CAPTION_COLOR 35
#endif
#ifndef DWMWA_TEXT_COLOR
#define DWMWA_TEXT_COLOR 36
#endif
#ifndef DWMWA_WINDOW_CORNER_PREFERENCE
#define DWMWA_WINDOW_CORNER_PREFERENCE 33
#endif
#ifndef DWMWCP_ROUND
#define DWMWCP_ROUND 2
#endif
#ifndef DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4)
#endif

#define WM_TRAYICON           (WM_USER + 1)
#define WM_APP_RESULT         (WM_USER + 2)
#define WM_APP_SHOW           (WM_USER + 3)
#define WM_APP_SELECT         (WM_USER + 5)
#define WM_APP_PROGRESS       (WM_USER + 6)
#define ID_TRAY_APP_ICON      1001
#define ID_TRAY_EXIT          1002
#define ID_TRAY_SHOW          1003
#define ID_TRAY_AUTOPASTE     1016
#define ID_TRAY_TOPMOST       1017
#define ID_TRAY_AUTOHIDE      1018
#define ID_BTN_RECORD         1004
#define ID_BTN_CLEAR          1005
#define ID_EDIT_TEXT          1006
#define ID_COMBO_AUDIO        1007
#define ID_STATIC_STATUS      1009
#define ID_BTN_COPY           1011
#define ID_BTN_PASTE          1012
#define ID_BTN_PIN            1013
#define ID_STATIC_PLACEHOLDER 1014
#define ID_COMBO_MODEL        1015
#define ID_SEL_AUDIO          1007
#define ID_SEL_MODEL          1015
#define ID_TIMER_UPDATE       2
#define HK_TOGGLE             1
#define HK_HIDE               2
#define IDI_ICON1             101

#define CR_BG      RGB(0x0E,0x10,0x14)
#define CR_SURFACE RGB(0x16,0x19,0x20)
#define CR_TEXT    RGB(0xEC,0xEE,0xF2)

static const Color T_BG       (255, 0x0E, 0x10, 0x14);
static const Color T_CARD     (255, 0x16, 0x19, 0x20);
static const Color T_CARD_HI  (255, 0x1E, 0x22, 0x2B);
static const Color T_CARD_LO  (255, 0x12, 0x15, 0x1B);
static const Color T_TEXT     (255, 0xEC, 0xEE, 0xF2);
static const Color T_DIM      (255, 0x8A, 0x90, 0x9C);
static const Color T_FAINT    (255, 0x5A, 0x60, 0x6C);
static const Color T_ACCENT   (255, 0x6E, 0x8B, 0xFF);
static const Color T_ACCENT_HI(255, 0x83, 0x9C, 0xFF);
static const Color T_DANGER   (255, 0xFF, 0x5C, 0x5C);
static const Color T_GOOD     (255, 0x46, 0xD3, 0x9A);
static const Color T_TOPLIGHT (26,  0xFF, 0xFF, 0xFF);

static const Color C_BG       = T_BG;
static const Color C_SURFACE  = T_CARD;
static const Color C_SURFACEHI= T_CARD_HI;
static const Color C_BORDER   = Color(255,0x26,0x2B,0x36);
static const Color C_TEXT     = T_TEXT;
static const Color C_TEXTDIM  = T_DIM;
static const Color C_ACCENT   = T_ACCENT;
static const Color C_ACCENTHI = T_ACCENT_HI;
static const Color C_DANGER   = T_DANGER;
static const Color C_GOOD     = T_GOOD;

HINSTANCE  hInst;
HWND       hMainWnd;
NOTIFYICONDATA nid;
Transcriber g_tx;
WhisperConfig g_config;
HWND  g_prevForeground = nullptr;
bool  g_autoPaste = true;
bool  g_pinned   = true;
bool  g_autoHide = false;
HANDLE g_hMutex  = nullptr;
ULONG_PTR g_gdipToken = 0;
std::atomic<bool> g_modelLoaded{false};
std::atomic<bool> g_modelOk{false};
std::atomic<int>  g_recordingSecs{0};
std::atomic<int>  g_progress{0};
DWORD  g_busyStart = 0;
float  g_energy = 0.0f;
bool   g_cancelRequested = false;
TimingModel       g_timing;
ProgressEstimator g_est;
double            g_lastAudioLen = 0.0;
DWORD             g_lastTick = 0;
float             g_progressFrac = 0.0f;
float             g_progressRemain = 0.0f;
float             g_dpiScale = 1.0f;
std::wstring      g_statusOverride;
DWORD             g_statusOverrideUntil = 0;
AppSettings g_set;
static const float kMaxRecordSeconds = 600.0f;

void PersistNow() {
    g_set.pinned    = g_pinned;
    g_set.autoPaste = g_autoPaste;
    g_set.autoHide  = g_autoHide;
    g_set.captureId = g_config.capture_id;
    {
        std::string path = g_config.model_path;
        g_set.modelFile = std::wstring(path.begin(), path.end());
    }
    RECT r; if (GetWindowRect(hMainWnd, &r)) {
        g_set.winX = r.left; g_set.winY = r.top;
        g_set.winW = r.right - r.left; g_set.winH = r.bottom - r.top;
    }
    SaveSettings(g_set);
}
RECT   g_vuRect = {0,0,0,0};
RECT   g_panelRect = {0,0,0,0};

HFONT  g_fUI = nullptr, g_fUISemi = nullptr, g_fSmall = nullptr, g_fText = nullptr;
HBRUSH g_brBg = nullptr, g_brSurface = nullptr;
Gdiplus::Font* g_gpUI     = nullptr;
Gdiplus::Font* g_gpUISemi = nullptr;
Gdiplus::Font* g_gpSmall  = nullptr;
Gdiplus::Font* g_gpText   = nullptr;

static const wchar_t* kModelNames[] = { L"tiny.en", L"tiny.en-q8_0", L"base.en-q5_1", L"base.en" };
static const char*    kModelFiles[] = {
    "models\\ggml-tiny.en.bin", "models\\ggml-tiny.en-q8_0.bin",
    "models\\ggml-base.en-q5_1.bin", "models\\ggml-base.en.bin",
};
std::vector<std::string> g_modelComboPaths;
std::vector<std::wstring> g_audioItems;  int g_audioSel = 0;
std::vector<std::wstring> g_modelItems;  int g_modelSel = 0;
bool g_initializing = true;

struct PopupState { std::vector<std::wstring> items; int sel; int hot; HWND owner; int ctrlId; };
static PopupState g_pop;

enum class WK { RecordHero, Pin, Copy, Paste, Clear, SelAudio, SelModel, Transcript };
struct Widget {
    WK     kind;
    RectF  r;
    bool   hover = false;
    bool   pressed = false;
    float  anim = 0.0f;
};
static Widget g_w[ (int)WK::Transcript + 1 ];
static int    g_hot = -1;
static int    g_active = -1;

static DWORD  g_lastFrame = 0;
static bool   g_animating = false;

static bool AnyAnimating() {
    if (g_animating) return true;
    if (g_tx.is_recording() || g_tx.is_busy()) return true;
    for (auto& w : g_w) {
        float target = g_active == (&w - g_w) ? 1.0f : (w.hover ? 0.6f : 0.0f);
        if (std::fabs(w.anim - target) > 0.002f) return true;
    }
    return false;
}

static void StepAnimations(float dt) {
    for (auto& w : g_w) {
        float target = (g_active == (&w - g_w)) ? 1.0f : (w.hover ? 0.6f : 0.0f);
        w.anim += (target - w.anim) * std::min(1.0f, dt * 12.0f);
    }
}

static void EnsureAnimating(HWND h) {
    if (!g_animating) {
        g_animating = true;
        g_lastFrame = GetTickCount();
        SetTimer(h, 3, 16, nullptr); // 16ms animation timer
    }
}

static void StopAnimating(HWND h) {
    KillTimer(h, 3);
    g_animating = false;
}

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK PopupProc(HWND, UINT, WPARAM, LPARAM);
void ShowContextMenu(HWND, POINT);
void RefreshAudioDevices(HWND);
void RefreshModelList(HWND);
void SetStatus(HWND, const wchar_t*);
void UpdateStatus(HWND);
void UpdatePlaceholder(HWND);
void LayoutControls(HWND, int, int);
void LayoutWidgets(int W, int H);
void PaintSurface(HWND hwnd);
int  HitTest(POINT p);
void OnClick(HWND hwnd, WK kind);
void DrawHero(Graphics& g, const Widget& w);
void DrawGhost(Graphics& g, const Widget& w, const wchar_t* label, bool active);
void DrawPinSurface(Graphics& g, const Widget& w);
void DrawSelectSurface(Graphics& g, const Widget& w, const std::wstring& text);
void DrawStatusStrip(Graphics& g, const RectF& strip);
void DrawCard(Graphics& g, const Rect& cardRect);
void DrawVU(Graphics&, const RECT&, float);
void DrawProgress(Graphics&, const RECT&, float);
void ShowSelectPopup(HWND owner, int ctrlId, const std::vector<std::wstring>& items, int sel);
std::string exe_dir();
std::wstring to_w(const std::string&);
bool SetClipboardTextUtf8(HWND, const std::string&);
void send_ctrl_v();
void PasteIntoWindow(HWND);
bool DetectGPUAvailability();
std::string SelectOptimalModel(bool);

static HFONT MakeFont(int px, int weight, float scale = 1.0f) {
    return CreateFontW(-(int)(px * scale + 0.5f), 0, 0, 0, weight, FALSE, FALSE, FALSE, DEFAULT_CHARSET,
        OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY,
        DEFAULT_PITCH | FF_DONTCARE, L"Segoe UI Variable Display");
}
static void RebuildGdipFonts();

static void RecreateFonts(float scale) {
    if (g_fUI) DeleteObject(g_fUI);
    if (g_fUISemi) DeleteObject(g_fUISemi);
    if (g_fSmall) DeleteObject(g_fSmall);
    if (g_fText) DeleteObject(g_fText);
    g_fUI     = MakeFont(15, FW_NORMAL, scale);
    g_fUISemi = MakeFont(15, FW_SEMIBOLD, scale);
    g_fSmall  = MakeFont(12, FW_NORMAL, scale);
    g_fText   = MakeFont(16, FW_NORMAL, scale);
    if (!g_fUI) g_fUI = MakeFont(15, FW_NORMAL, scale);
    RebuildGdipFonts();
}

static Gdiplus::Font* GdipFontFromHFont(HFONT hf) {
    HDC sdc = GetDC(nullptr);
    Gdiplus::Font* f = new Gdiplus::Font(sdc, hf);
    ReleaseDC(nullptr, sdc);
    if (f->GetLastStatus() != Ok) { delete f; return nullptr; }
    return f;
}

static void RebuildGdipFonts() {
    delete g_gpUI; delete g_gpUISemi; delete g_gpSmall; delete g_gpText;
    g_gpUI     = GdipFontFromHFont(g_fUI);
    g_gpUISemi = GdipFontFromHFont(g_fUISemi);
    g_gpSmall  = GdipFontFromHFont(g_fSmall);
    g_gpText   = GdipFontFromHFont(g_fText);
    if (!g_gpUI)     g_gpUI     = new Gdiplus::Font(L"Segoe UI", 15.0f * g_dpiScale, FontStyleRegular, UnitPixel);
    if (!g_gpUISemi) g_gpUISemi = new Gdiplus::Font(L"Segoe UI", 15.0f * g_dpiScale, FontStyleBold,    UnitPixel);
    if (!g_gpSmall)  g_gpSmall  = new Gdiplus::Font(L"Segoe UI", 12.0f * g_dpiScale, FontStyleRegular, UnitPixel);
    if (!g_gpText)   g_gpText   = new Gdiplus::Font(L"Segoe UI", 16.0f * g_dpiScale, FontStyleRegular, UnitPixel);
}

static void RoundPath(GraphicsPath& p, const Rect& r, int rad) {
    int d = rad * 2;
    p.AddArc(r.X, r.Y, d, d, 180, 90);
    p.AddArc(r.GetRight() - d, r.Y, d, d, 270, 90);
    p.AddArc(r.GetRight() - d, r.GetBottom() - d, d, d, 0, 90);
    p.AddArc(r.X, r.GetBottom() - d, d, d, 90, 90);
    p.CloseFigure();
}
static void FillRound(Graphics& g, const Color& c, const Rect& r, int rad) {
    GraphicsPath p; RoundPath(p, r, rad); SolidBrush b(c); g.FillPath(&b, &p);
}
static void StrokeRound(Graphics& g, const Color& c, const Rect& r, int rad, REAL w = 1.0f) {
    GraphicsPath p; RoundPath(p, r, rad); Pen pen(c, w); g.DrawPath(&pen, &p);
}
static void DrawTextC(Graphics& g, const wchar_t* s, const Font& f, const Color& c,
                       const RectF& box, StringAlignment h, StringAlignment v) {
    StringFormat sf; sf.SetAlignment(h); sf.SetLineAlignment(v);
    sf.SetTrimming(StringTrimmingEllipsisCharacter);
    SolidBrush b(c); g.DrawString(s, -1, &f, box, &sf, &b);
}

int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) {
    hInst = hInstance;

    g_hMutex = CreateMutexW(nullptr, TRUE, L"WhisperDictation_SingleInstance");
    if (GetLastError() == ERROR_ALREADY_EXISTS) {
        HWND existing = FindWindowW(L"WhisperDictationClass", nullptr);
        if (existing) PostMessage(existing, WM_APP_SHOW, 0, 0);
        CloseHandle(g_hMutex);
        return 0;
    }

    GdiplusStartupInput gdipIn;
    if (GdiplusStartup(&g_gdipToken, &gdipIn, nullptr) != Ok) return 1;

    SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);

    INITCOMMONCONTROLSEX icex;
    icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
    icex.dwICC  = ICC_WIN95_CLASSES | ICC_STANDARD_CLASSES;
    InitCommonControlsEx(&icex);

    g_fUI     = MakeFont(15, FW_NORMAL, g_dpiScale);
    g_fUISemi = MakeFont(15, FW_SEMIBOLD, g_dpiScale);
    g_fSmall  = MakeFont(12, FW_NORMAL, g_dpiScale);
    g_fText   = MakeFont(16, FW_NORMAL, g_dpiScale);
    if (!g_fUI) g_fUI = MakeFont(15, FW_NORMAL, g_dpiScale);
    g_brBg      = CreateSolidBrush(CR_BG);
    g_brSurface = CreateSolidBrush(CR_SURFACE);

    WNDCLASSEX wc = {0};
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WndProc;
    wc.hInstance     = hInstance;
    wc.hCursor       = LoadCursor(nullptr, IDC_ARROW);
    wc.hbrBackground = g_brBg;
    wc.lpszClassName = L"WhisperDictationClass";
    wc.hIcon         = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
    RegisterClassEx(&wc);

    LoadSettings(g_set);
    g_pinned   = g_set.pinned;
    g_autoPaste = g_set.autoPaste;
    g_autoHide  = g_set.autoHide;

    hMainWnd = CreateWindowExW(
        g_pinned ? WS_EX_TOPMOST : 0,
        L"WhisperDictationClass", L"Dictation",
        WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
        g_set.winX, g_set.winY, g_set.winW, g_set.winH,
        nullptr, nullptr, hInstance, nullptr);

    if (!hMainWnd) return FALSE;

    g_dpiScale = GetDpiForWindow(hMainWnd) / 96.0f;
    if (g_dpiScale <= 0.0f) g_dpiScale = 1.0f;
    RecreateFonts(g_dpiScale);

    BOOL dark = TRUE;
    DwmSetWindowAttribute(hMainWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &dark, sizeof(dark));
    COLORREF cap = CR_BG, bord = RGB(0x5A,0x60,0x6C), txt = CR_TEXT;
    DwmSetWindowAttribute(hMainWnd, DWMWA_CAPTION_COLOR, &cap,  sizeof(cap));
    DwmSetWindowAttribute(hMainWnd, DWMWA_BORDER_COLOR,  &bord, sizeof(bord));
    DwmSetWindowAttribute(hMainWnd, DWMWA_TEXT_COLOR,    &txt,  sizeof(txt));
    int corner = DWMWCP_ROUND;
    DwmSetWindowAttribute(hMainWnd, DWMWA_WINDOW_CORNER_PREFERENCE, &corner, sizeof(corner));

    {
        HWND hBtnRecord = CreateWindow(L"BUTTON", L"",
            WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_OWNERDRAW,
            0, 0, 0, 0, hMainWnd, (HMENU)ID_BTN_RECORD, hInst, nullptr);
        SetWindowTheme(hBtnRecord, L"", L"");

        HWND hPin = CreateWindow(L"BUTTON", L"",
            WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_OWNERDRAW,
            0, 0, 0, 0, hMainWnd, (HMENU)ID_BTN_PIN, hInst, nullptr);
        SetWindowTheme(hPin, L"", L"");

        CreateWindow(L"STATIC", L"Ready",
            WS_CHILD | WS_VISIBLE | SS_LEFT,
            0, 0, 0, 0, hMainWnd, (HMENU)ID_STATIC_STATUS, hInst, nullptr);

        HWND hEdit = CreateWindowExW(0, L"EDIT", L"",
            WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY,
            0, 0, 0, 0, hMainWnd, (HMENU)ID_EDIT_TEXT, hInst, nullptr);
        SetWindowTheme(hEdit, L"DarkMode_Explorer", nullptr);
        SendMessage(hEdit, WM_SETFONT, (WPARAM)g_fText, TRUE);
        int editMargin = (int)(10 * g_dpiScale);
        SendMessage(hEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(editMargin, editMargin));

        CreateWindowW(L"STATIC", L"Your transcription will appear here\u2026",
            WS_CHILD | WS_VISIBLE | SS_LEFT,
            0, 0, 0, 0, hMainWnd, (HMENU)ID_STATIC_PLACEHOLDER, hInst, nullptr);

        HWND hSelAudio = CreateWindow(L"BUTTON", L"",
            WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_OWNERDRAW,
            0, 0, 0, 0, hMainWnd, (HMENU)ID_SEL_AUDIO, hInst, nullptr);
        SetWindowTheme(hSelAudio, L"", L"");

        HWND hSelModel = CreateWindow(L"BUTTON", L"",
            WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_OWNERDRAW,
            0, 0, 0, 0, hMainWnd, (HMENU)ID_SEL_MODEL, hInst, nullptr);
        SetWindowTheme(hSelModel, L"", L"");

        HWND hCopy = CreateWindow(L"BUTTON", L"",
            WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_OWNERDRAW,
            0, 0, 0, 0, hMainWnd, (HMENU)ID_BTN_COPY, hInst, nullptr);
        SetWindowTheme(hCopy, L"", L"");

        HWND hPaste = CreateWindow(L"BUTTON", L"",
            WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_OWNERDRAW,
            0, 0, 0, 0, hMainWnd, (HMENU)ID_BTN_PASTE, hInst, nullptr);
        SetWindowTheme(hPaste, L"", L"");

        HWND hClear = CreateWindow(L"BUTTON", L"",
            WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_OWNERDRAW,
            0, 0, 0, 0, hMainWnd, (HMENU)ID_BTN_CLEAR, hInst, nullptr);
        SetWindowTheme(hClear, L"", L"");
    }

    ShowWindow(GetDlgItem(hMainWnd, ID_BTN_RECORD), SW_HIDE);
    ShowWindow(GetDlgItem(hMainWnd, ID_BTN_PIN), SW_HIDE);
    ShowWindow(GetDlgItem(hMainWnd, ID_STATIC_STATUS), SW_HIDE);
    ShowWindow(GetDlgItem(hMainWnd, ID_STATIC_PLACEHOLDER), SW_HIDE);
    ShowWindow(GetDlgItem(hMainWnd, ID_SEL_AUDIO), SW_HIDE);
    ShowWindow(GetDlgItem(hMainWnd, ID_SEL_MODEL), SW_HIDE);
    ShowWindow(GetDlgItem(hMainWnd, ID_BTN_COPY), SW_HIDE);
    ShowWindow(GetDlgItem(hMainWnd, ID_BTN_PASTE), SW_HIDE);
    ShowWindow(GetDlgItem(hMainWnd, ID_BTN_CLEAR), SW_HIDE);

    SendMessageW(hMainWnd, WM_CHANGEUISTATE, MAKEWPARAM(UIS_SET, UISF_HIDEFOCUS), 0);

    SDL_Init(SDL_INIT_AUDIO);
    RefreshAudioDevices(hMainWnd);
    RefreshModelList(hMainWnd);

    bool has_gpu = DetectGPUAvailability();
    std::string model = SelectOptimalModel(has_gpu);

    if (!g_set.modelFile.empty()) {
        std::string mf;
        {
            int len = WideCharToMultiByte(CP_UTF8, 0, g_set.modelFile.c_str(), -1, nullptr, 0, nullptr, nullptr);
            if (len > 0) { mf.resize(len - 1); WideCharToMultiByte(CP_UTF8, 0, g_set.modelFile.c_str(), -1, &mf[0], len, nullptr, nullptr); }
        }
        if (GetFileAttributesA(mf.c_str()) != INVALID_FILE_ATTRIBUTES) {
            g_config.model_path = mf;
            for (int i = 0; i < (int)g_modelComboPaths.size(); ++i) {
                std::string p = exe_dir() + "\\" + g_modelComboPaths[i];
                if (p == mf) { g_modelSel = i; break; }
            }
        }
    }
    if (g_config.model_path.empty()) {
        if (!g_modelComboPaths.empty())
            g_config.model_path = exe_dir() + "\\" + g_modelComboPaths[0];
        else
            g_config.model_path = exe_dir() + "\\" + model;
    }
    g_config.n_threads = 0;
    g_config.capture_id = g_set.captureId;

    SeedDefaults(g_timing, g_config.model_path);
    LoadTiming(g_timing, g_config.model_path);

    {
        char log[256];
        snprintf(log, sizeof(log), "Startup  model=%s  threads=%d  capture_id=%d",
                 g_config.model_path.c_str(), g_config.n_threads, g_config.capture_id);
        LogLine(log);
    }

    nid.cbSize = sizeof(NOTIFYICONDATA);
    nid.hWnd   = hMainWnd;
    nid.uID    = ID_TRAY_APP_ICON;
    nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
    nid.uCallbackMessage = WM_TRAYICON;
    nid.hIcon  = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
    wcscpy_s(nid.szTip, L"Dictation");
    Shell_NotifyIcon(NIM_ADD, &nid);

    bool hkOk1 = RegisterHotKey(hMainWnd, HK_TOGGLE, g_set.hkMods, g_set.hkVk) != FALSE;
    bool hkOk2 = RegisterHotKey(hMainWnd, HK_HIDE,   MOD_CONTROL | MOD_SHIFT, 'H') != FALSE;
    if (!hkOk1) SetStatus(hMainWnd, L"Hotkey in use \u2014 edit win-dictation.ini");

    g_tx.set_result_callback([](const std::string& t) {
        PostMessage(hMainWnd, WM_APP_RESULT, (WPARAM)new std::string(t), 0);
    });
    g_tx.set_progress_callback([](int p) {
        PostMessage(hMainWnd, WM_APP_PROGRESS, (WPARAM)p, 0);
    });

    std::thread([] {
        bool ok = g_tx.preload(g_config);
        g_modelOk = ok;
        g_modelLoaded = true;
        LogLine(ok ? "Model loaded successfully" : "Model load FAILED");
    }).detach();

    SetTimer(hMainWnd, ID_TIMER_UPDATE, 50, nullptr);
    SetStatus(hMainWnd, L"Loading model\u2026");

    ShowWindow(hMainWnd, nCmdShow);
    {
        RECT rc; GetClientRect(hMainWnd, &rc);
        LayoutControls(hMainWnd, rc.right, rc.bottom);
        LayoutWidgets(rc.right, rc.bottom);
    }
    InvalidateRect(hMainWnd, nullptr, FALSE);
    g_initializing = false;

    MSG msg;
    while (GetMessage(&msg, nullptr, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    KillTimer(hMainWnd, ID_TIMER_UPDATE);
    UnregisterHotKey(hMainWnd, HK_TOGGLE);
    UnregisterHotKey(hMainWnd, HK_HIDE);
    g_tx.cancel();
    Shell_NotifyIcon(NIM_DELETE, &nid);
    SDL_Quit();
    ReleaseMutex(g_hMutex);
    CloseHandle(g_hMutex);
    delete g_gpUI; delete g_gpUISemi; delete g_gpSmall; delete g_gpText;
    g_gpUI = g_gpUISemi = g_gpSmall = g_gpText = nullptr;
    GdiplusShutdown(g_gdipToken);

    DeleteObject(g_fUI); DeleteObject(g_fUISemi);
    DeleteObject(g_fSmall); DeleteObject(g_fText);
    DeleteObject(g_brBg); DeleteObject(g_brSurface);

    return (int)msg.wParam;
}

void DrawVU(Graphics& g, const RECT& r, float level) {
    const int N = 14, gap = 3;
    int w = r.right - r.left;
    if (w <= 0) return;
    int segW = (w - gap * (N - 1)) / N;
    if (segW < 1) return;
    int h = r.bottom - r.top;
    int lit = (int)(level * N + 0.5f);
    for (int i = 0; i < N; ++i) {
        int x = r.left + i * (segW + gap);
        Rect seg(x, r.top, segW, h);
        FillRound(g, (i < lit) ? C_GOOD : C_SURFACEHI, seg, 2);
    }
}

void DrawProgress(Graphics& g, const RECT& r, float frac) {
    Rect track(r.left, r.top, r.right - r.left, r.bottom - r.top);
    FillRound(g, C_SURFACEHI, track, 4);
    frac = frac < 0.0f ? 0.0f : (frac > 1.0f ? 1.0f : frac);
    int w = (int)((r.right - r.left) * frac);
    if (w > 0) { Rect fill(r.left, r.top, w, r.bottom - r.top); FillRound(g, C_ACCENT, fill, 4); }
}

LRESULT CALLBACK PopupProc(HWND h, UINT m, WPARAM w, LPARAM l) {
    switch (m) {
    case WM_MOUSEMOVE: {
        int row = GET_Y_LPARAM(l) / 30;
        if (row != g_pop.hot) { g_pop.hot = row; InvalidateRect(h, nullptr, FALSE); }
        TRACKMOUSEEVENT t{ sizeof(t), TME_LEAVE, h, 0 }; TrackMouseEvent(&t);
        return 0;
    }
    case WM_MOUSELEAVE: g_pop.hot = -1; InvalidateRect(h, nullptr, FALSE); return 0;
    case WM_LBUTTONUP: {
        int row = GET_Y_LPARAM(l) / 30;
        if (row >= 0 && row < (int)g_pop.items.size())
            PostMessageW(g_pop.owner, WM_APP_SELECT, (WPARAM)g_pop.ctrlId, (LPARAM)row);
        DestroyWindow(h); return 0;
    }
    case WM_KILLFOCUS: DestroyWindow(h); return 0;
    case WM_ERASEBKGND: return 1;
    case WM_PAINT: {
        PAINTSTRUCT ps; HDC hdc = BeginPaint(h, &ps);
        RECT rc; GetClientRect(h, &rc);
        HDC mem = CreateCompatibleDC(hdc);
        HBITMAP bmp = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
        HBITMAP old = (HBITMAP)SelectObject(mem, bmp);
        {
            Graphics g(mem); g.SetSmoothingMode(SmoothingModeAntiAlias);
            Rect all(0, 0, rc.right, rc.bottom);
            FillRound(g, T_CARD, all, 10); StrokeRound(g, T_FAINT, all, 10, 1.0f);
            Font f(mem, g_fUI);
            for (int i = 0; i < (int)g_pop.items.size(); ++i) {
                Rect row(3, i * 30 + 3, rc.right - 6, 28);
                if (i == g_pop.hot) FillRound(g, T_CARD_HI, row, 7);
                RectF tb((REAL)row.X + 9, (REAL)row.Y, (REAL)row.Width - 12, (REAL)row.Height);
                DrawTextC(g, g_pop.items[i].c_str(), f, (i == g_pop.sel) ? T_ACCENT : T_TEXT,
                          tb, StringAlignmentNear, StringAlignmentCenter);
            }
        }
        BitBlt(hdc, 0, 0, rc.right, rc.bottom, mem, 0, 0, SRCCOPY);
        SelectObject(mem, old); DeleteObject(bmp); DeleteDC(mem);
        EndPaint(h, &ps); return 0;
    }
    }
    return DefWindowProc(h, m, w, l);
}

void ShowSelectPopup(HWND owner, int ctrlId, const std::vector<std::wstring>& items, int sel) {
    static bool reg = false;
    if (!reg) {
        WNDCLASSEXW wc{ sizeof(wc) };
        wc.lpfnWndProc = PopupProc;
        wc.hInstance   = hInst;
        wc.hCursor     = LoadCursor(nullptr, IDC_ARROW);
        wc.hbrBackground = CreateSolidBrush(CR_SURFACE);
        wc.lpszClassName = L"DictPopup";
        RegisterClassExW(&wc);
        reg = true;
    }
    g_pop = { items, sel, -1, owner, ctrlId };
    RECT rc; GetWindowRect(GetDlgItem(owner, ctrlId), &rc);
    int h = (int)items.size() * 30 + 6, wdt = rc.right - rc.left;
    HWND p = CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_TOPMOST, L"DictPopup", L"",
        WS_POPUP, rc.left, rc.bottom + 2, wdt, h, owner, nullptr, hInst, nullptr);
    int corner = DWMWCP_ROUND; DwmSetWindowAttribute(p, DWMWA_WINDOW_CORNER_PREFERENCE, &corner, sizeof(corner));
    ShowWindow(p, SW_SHOWNA); SetFocus(p);
}

void LayoutControls(HWND h, int W, int H) {
    float s = g_dpiScale;
    const int M = (int)(16 * s), row = (int)(36 * s), gap = (int)(10 * s);
    int x = M, y = M, innerW = W - 2 * M;

    int pinW = (int)(78 * s), recW = innerW - pinW - gap;
    MoveWindow(GetDlgItem(h, ID_BTN_RECORD), x, y, recW, row, TRUE);
    MoveWindow(GetDlgItem(h, ID_BTN_PIN), x + recW + gap, y, pinW, row, TRUE);

    y += row + gap;
    int vuH = (int)(8 * s);
    g_vuRect = { x, y, x + innerW, y + vuH };

    y += vuH + gap;
    MoveWindow(GetDlgItem(h, ID_STATIC_STATUS), x, y, innerW, (int)(18 * s), TRUE);

    y += (int)(18 * s) + gap;
    int bottom = (row + gap) * 2;
    int panelTop = y;
    int textH = H - y - M - bottom;
    if (textH < (int)(70 * s)) textH = (int)(70 * s);
    int editPad = (int)(10 * s);
    MoveWindow(GetDlgItem(h, ID_EDIT_TEXT), x + editPad, panelTop + editPad, innerW - 2 * editPad, textH - 2 * editPad, TRUE);
    MoveWindow(GetDlgItem(h, ID_STATIC_PLACEHOLDER), x + (int)(16 * s), panelTop + (int)(16 * s), innerW - (int)(32 * s), (int)(22 * s), TRUE);

    y += textH + gap;
    int halfW = (innerW - gap) / 2;
    MoveWindow(GetDlgItem(h, ID_SEL_AUDIO), x, y, halfW, row, TRUE);
    MoveWindow(GetDlgItem(h, ID_SEL_MODEL), x + halfW + gap, y, halfW, row, TRUE);

    y += row + gap;
    int thirdW = (innerW - gap * 2) / 3;
    MoveWindow(GetDlgItem(h, ID_BTN_COPY),  x, y, thirdW, row, TRUE);
    MoveWindow(GetDlgItem(h, ID_BTN_PASTE), x + thirdW + gap, y, thirdW, row, TRUE);
    MoveWindow(GetDlgItem(h, ID_BTN_CLEAR), x + (thirdW + gap) * 2, y, thirdW, row, TRUE);

    g_panelRect = { x, panelTop, x + innerW, panelTop + textH };
    InvalidateRect(h, nullptr, FALSE);
}

void LayoutWidgets(int W, int H) {
    for (int i = 0; i < (int)std::size(g_w); ++i)
        g_w[i].kind = (WK)i;

    for (auto& w : g_w) w.r = RectF(0, 0, 0, 0);
    float s = g_dpiScale;
    const REAL M = 16 * s, row = 36 * s, gap = 10 * s;
    REAL x = M, y = M, innerW = W - 2 * M;

    REAL pinW = 78 * s, recW = innerW - pinW - gap;
    g_w[(int)WK::RecordHero].r = RectF(x, y, recW, row);
    g_w[(int)WK::Pin].r        = RectF(x + recW + gap, y, pinW, row);

    y += row + gap;
    REAL vuH = 8 * s;
    g_vuRect = { (int)x, (int)y, (int)(x + innerW), (int)(y + vuH) };

    y += vuH + gap;
    REAL statusH = 18 * s;
    y += statusH + gap;  // status strip space (drawn by PaintSurface)

    int bottom = (int)((row + gap) * 2);
    REAL panelTop = y;
    int textH = H - (int)y - (int)M - bottom;
    if (textH < (int)(70 * s)) textH = (int)(70 * s);
    int editPad = (int)(10 * s);
    MoveWindow(GetDlgItem(hMainWnd, ID_EDIT_TEXT), (int)(x + editPad), (int)(panelTop + editPad),
               (int)(innerW - 2 * editPad), textH - 2 * editPad, TRUE);
    g_w[(int)WK::Transcript].r = RectF(x + editPad, panelTop + editPad, innerW - 2 * editPad, (REAL)(textH - 2 * editPad));

    y += textH + gap;
    REAL halfW = (innerW - gap) / 2;
    g_w[(int)WK::SelAudio].r = RectF(x, y, halfW, row);
    g_w[(int)WK::SelModel].r = RectF(x + halfW + gap, y, halfW, row);

    y += row + gap;
    REAL thirdW = (innerW - gap * 2) / 3;
    g_w[(int)WK::Copy].r  = RectF(x, y, thirdW, row);
    g_w[(int)WK::Paste].r = RectF(x + thirdW + gap, y, thirdW, row);
    g_w[(int)WK::Clear].r = RectF(x + (thirdW + gap) * 2, y, thirdW, row);

    g_panelRect = { (int)x, (int)panelTop, (int)(x + innerW), (int)(panelTop + textH) };
}

void DrawCard(Graphics& g, const Rect& cardRect) {
    FillRound(g, T_CARD, cardRect, (int)(16 * g_dpiScale));
    int r = (int)(16 * g_dpiScale);
    int d = r * 2;
    if (d < 1) return;
    GraphicsPath p;
    p.AddArc(cardRect.X, cardRect.Y, d, d, 180, 90);
    p.AddArc(cardRect.GetRight() - d, cardRect.Y, d, d, 270, 90);
    p.AddArc(cardRect.GetRight() - d, cardRect.GetBottom() - d, d, d, 0, 90);
    p.AddArc(cardRect.X, cardRect.GetBottom() - d, d, d, 90, 90);
    p.CloseFigure();
    Pen topLight(T_TOPLIGHT, 1.0f);
    g.DrawPath(&topLight, &p);
}

void DrawHero(Graphics& g, const Widget& w) {
    Rect rc((int)w.r.X, (int)w.r.Y, (int)w.r.Width, (int)w.r.Height);
    bool rec     = g_tx.is_recording();
    bool hover   = w.hover;
    bool pressed = w.pressed;

    Color fill = rec ? T_DANGER : (hover ? T_ACCENT_HI : T_ACCENT);
    if (rec) {
        double ph = (GetTickCount() % 1400) / 1400.0;
        int add = (int)(18 * (0.5 + 0.5 * sin(ph * 6.2831853)));
        fill = Color(255, (BYTE)std::min(255, 0xFF), (BYTE)std::min(255, 0x5C + add), (BYTE)std::min(255, 0x5C + add));
    }
    if (pressed) fill = Color(255, (BYTE)(fill.GetR() * 0.85f),
                                   (BYTE)(fill.GetG() * 0.85f),
                                   (BYTE)(fill.GetB() * 0.85f));

    Rect pill = rc; pill.Inflate(-1, -1);
    FillRound(g, fill, pill, pill.Height / 2);

    int cx = pill.X + (int)(22 * g_dpiScale), cy = pill.Y + pill.Height / 2;
    SolidBrush white(Color(255, 255, 255, 255));
    if (rec) { Rect sq(cx - 7, cy - 7, 14, 14); FillRound(g, Color(255, 255, 255, 255), sq, 3); }
    else     { g.FillEllipse(&white, cx - 7, cy - 7, 14, 14); }

    RectF tb((REAL)(pill.X + (int)(36 * g_dpiScale)), (REAL)pill.Y, (REAL)(pill.Width - (int)(44 * g_dpiScale)), (REAL)pill.Height);
    DrawTextC(g, rec ? L"Stop" : L"Record", *g_gpUISemi, Color(255, 255, 255, 255),
              tb, StringAlignmentNear, StringAlignmentCenter);
}

void DrawGhost(Graphics& g, const Widget& w, const wchar_t* label, bool active) {
    Rect rc((int)w.r.X, (int)w.r.Y, (int)w.r.Width, (int)w.r.Height);
    Rect chip = rc; chip.Inflate(-1, -1);

    float a = w.anim;
    if (a > 0.001f) {
        BYTE bgAlpha = (BYTE)(std::min(255, (int)(255 * a)));
        Color bg(T_CARD_HI.GetA(), T_CARD_HI.GetR(), T_CARD_HI.GetG(), T_CARD_HI.GetB());
        if (w.pressed) bg = Color(bgAlpha, T_CARD_LO.GetR(), T_CARD_LO.GetG(), T_CARD_LO.GetB());
        else           bg = Color(bgAlpha, T_CARD_HI.GetR(), T_CARD_HI.GetG(), T_CARD_HI.GetB());
        FillRound(g, bg, chip, (int)(10 * g_dpiScale));
    }

    BYTE tr = (BYTE)(T_DIM.GetR() + (T_TEXT.GetR() - T_DIM.GetR()) * a);
    BYTE tg = (BYTE)(T_DIM.GetG() + (T_TEXT.GetG() - T_DIM.GetG()) * a);
    BYTE tb = (BYTE)(T_DIM.GetB() + (T_TEXT.GetB() - T_DIM.GetB()) * a);
    Color tc(255, tr, tg, tb);
    if (active && a < 0.01f) tc = T_ACCENT;

    RectF textBox((REAL)chip.X, (REAL)chip.Y, (REAL)chip.Width, (REAL)chip.Height);
    DrawTextC(g, label, *g_gpUI, tc, textBox, StringAlignmentCenter, StringAlignmentCenter);
}

void DrawPinSurface(Graphics& g, const Widget& w) {
    Rect rc((int)w.r.X, (int)w.r.Y, (int)w.r.Width, (int)w.r.Height);
    Rect chip = rc; chip.Inflate(-1, -1);

    float a = w.anim;
    if (a > 0.001f) {
        BYTE bgAlpha = (BYTE)(std::min(255, (int)(255 * a)));
        Color bg = w.pressed ? Color(bgAlpha, T_CARD_LO.GetR(), T_CARD_LO.GetG(), T_CARD_LO.GetB())
                             : Color(bgAlpha, T_CARD_HI.GetR(), T_CARD_HI.GetG(), T_CARD_HI.GetB());
        FillRound(g, bg, chip, (int)(10 * g_dpiScale));
    }

    Color tc = g_pinned ? T_ACCENT : (a > 0.01f ? T_TEXT : T_FAINT);
    RectF tb((REAL)chip.X, (REAL)chip.Y, (REAL)chip.Width, (REAL)chip.Height);
    DrawTextC(g, g_pinned ? L"Pinned" : L"Pin", *g_gpUI, tc, tb, StringAlignmentCenter, StringAlignmentCenter);
}

void DrawSelectSurface(Graphics& g, const Widget& w, const std::wstring& text) {
    Rect rc((int)w.r.X, (int)w.r.Y, (int)w.r.Width, (int)w.r.Height);
    Rect field = rc; field.Inflate(-1, -1);

    float a = w.anim;
    if (a > 0.001f) {
        BYTE bgAlpha = (BYTE)(std::min(255, (int)(255 * a)));
        FillRound(g, Color(bgAlpha, T_CARD_HI.GetR(), T_CARD_HI.GetG(), T_CARD_HI.GetB()),
                  field, (int)(9 * g_dpiScale));
    } else {
        FillRound(g, T_CARD, field, (int)(9 * g_dpiScale));
    }

    Pen border(T_FAINT, 1.0f);
    Rect brd = field; brd.Inflate(-1, -1);
    StrokeRound(g, T_FAINT, brd, (int)(9 * g_dpiScale), 1.0f);

    RectF tb((REAL)field.X + (10 * g_dpiScale), (REAL)field.Y,
             (REAL)(field.Width - 28 * g_dpiScale), (REAL)field.Height);
    DrawTextC(g, text.c_str(), *g_gpUI, T_TEXT, tb, StringAlignmentNear, StringAlignmentCenter);

    int cx = field.X + field.Width - (int)(16 * g_dpiScale), cy = field.Y + field.Height / 2;
    Pen pen(T_DIM, 1.6f);
    g.DrawLine(&pen, cx - 4, cy - 2, cx, cy + 2);
    g.DrawLine(&pen, cx, cy + 2, cx + 4, cy - 2);
}

void DrawStatusStrip(Graphics& g, const RectF& strip) {
    RECT r = { (int)strip.X, (int)strip.Y, (int)(strip.X + strip.Width), (int)(strip.Y + strip.Height) };
    if (g_tx.is_busy()) {
        DrawProgress(g, r, g_progressFrac);
    } else if (g_tx.is_recording()) {
        DrawVU(g, r, g_energy);
    }
}

void PaintSurface(HWND hwnd) {
    PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps);
    RECT rcCli; GetClientRect(hwnd, &rcCli);
    int W = rcCli.right - rcCli.left, H = rcCli.bottom - rcCli.top;
    if (W <= 0 || H <= 0) { EndPaint(hwnd, &ps); return; }

    HDC mem = CreateCompatibleDC(hdc);
    HBITMAP bmp = CreateCompatibleBitmap(hdc, W, H);
    HBITMAP old = (HBITMAP)SelectObject(mem, bmp);
    {
        Graphics g(mem);
        g.SetSmoothingMode(SmoothingModeAntiAlias);
        g.SetTextRenderingHint(TextRenderingHintClearTypeGridFit);

        SolidBrush bg(T_BG); g.FillRectangle(&bg, 0, 0, W, H);

        Rect panel(g_panelRect.left, g_panelRect.top,
                   g_panelRect.right - g_panelRect.left,
                   g_panelRect.bottom - g_panelRect.top);
        DrawCard(g, panel);

        for (auto& w : g_w) {
            switch (w.kind) {
                case WK::RecordHero: DrawHero(g, w); break;
                case WK::Copy:  DrawGhost(g, w, L"Copy", false); break;
                case WK::Paste: DrawGhost(g, w, L"Paste", false); break;
                case WK::Clear: DrawGhost(g, w, L"Clear", false); break;
                case WK::Pin:   DrawPinSurface(g, w); break;
                case WK::SelAudio: DrawSelectSurface(g, w, g_audioItems.empty() ? L"No devices" : g_audioItems[g_audioSel]); break;
                case WK::SelModel: DrawSelectSurface(g, w, g_modelItems.empty() ? L"\u2014" : g_modelItems[g_modelSel]); break;
                case WK::Transcript: break;
            }
        }

        RECT vr = g_vuRect;
        RectF stripRect((REAL)vr.left, (REAL)vr.top, (REAL)(vr.right - vr.left), (REAL)(vr.bottom - vr.top));
        DrawStatusStrip(g, stripRect);

        wchar_t statusBuf[256];
        if (g_tx.is_recording()) {
            int secs = g_recordingSecs.load();
            swprintf_s(statusBuf, L"Recording  %d:%02d", secs / 60, secs % 60);
        } else if (g_tx.is_busy()) {
            int   pct = (int)(g_progressFrac*100.0f + 0.5f);
            float total = g_tx.audio_seconds();
            int   mm    = (int)total / 60;
            int   ss    = (int)total % 60;
            int   rem = (int)(g_progressRemain + 0.5f);
            swprintf_s(statusBuf, L"Transcribing %d:%02d  \u2022  %d%%  \u2022  %ds left", mm, ss, pct, rem);
        } else if (g_modelLoaded.load()) {
            if (!g_statusOverride.empty() && GetTickCount() < g_statusOverrideUntil) {
                wcscpy_s(statusBuf, g_statusOverride.c_str());
            } else if (!g_modelOk.load()) {
                swprintf_s(statusBuf, L"Model not found \u2014 check models folder");
            } else {
                swprintf_s(statusBuf, L"Ready  \u2022  %d threads", g_tx.threads());
            }
        } else {
            swprintf_s(statusBuf, L"Loading model\u2026");
        }
        REAL vy = g_vuRect.top + g_vuRect.bottom - g_vuRect.top + (REAL)(10 * g_dpiScale);
        RectF statusBox((REAL)g_vuRect.left, vy,
                        (REAL)(g_vuRect.right - g_vuRect.left), (REAL)(18 * g_dpiScale));
        DrawTextC(g, statusBuf, *g_gpSmall, T_DIM, statusBox, StringAlignmentNear, StringAlignmentNear);

        bool empty = GetWindowTextLengthW(GetDlgItem(hwnd, ID_EDIT_TEXT)) == 0;
        if (empty && !g_tx.is_busy()) {
            RectF placeholderBox(g_w[(int)WK::Transcript].r.X + (6 * g_dpiScale),
                                 g_w[(int)WK::Transcript].r.Y + (6 * g_dpiScale),
                                 g_w[(int)WK::Transcript].r.Width,
                                 (REAL)(22 * g_dpiScale));
            DrawTextC(g, L"Your transcription will appear here\u2026", *g_gpText, T_DIM,
                      placeholderBox, StringAlignmentNear, StringAlignmentNear);
        }
    }
    BitBlt(hdc, 0, 0, W, H, mem, 0, 0, SRCCOPY);
    SelectObject(mem, old); DeleteObject(bmp); DeleteDC(mem);
    EndPaint(hwnd, &ps);
}

int HitTest(POINT p) {
    for (int i = 0; i < (int)std::size(g_w); ++i)
        if (g_w[i].kind != WK::Transcript && g_w[i].r.Contains((REAL)p.x, (REAL)p.y)) return i;
    return -1;
}

void OnClick(HWND hWnd, WK kind) {
    if (!g_modelLoaded.load() && kind != WK::RecordHero) return;
    switch (kind) {
        case WK::RecordHero:
            PostMessage(hWnd, WM_HOTKEY, HK_TOGGLE, 0);
            break;
        case WK::Copy: {
            int len = GetWindowTextLengthW(GetDlgItem(hWnd, ID_EDIT_TEXT));
            if (len > 0) {
                std::vector<wchar_t> buf(len + 1);
                GetDlgItemTextW(hWnd, ID_EDIT_TEXT, buf.data(), (int)buf.size());
                std::wstring w(buf.data());
                int u8len = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, nullptr, 0, nullptr, nullptr);
                std::string u8(u8len ? u8len - 1 : 0, '\0');
                if (u8len) WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, &u8[0], u8len, nullptr, nullptr);
                SetClipboardTextUtf8(hWnd, u8);
                SetStatus(hWnd, L"Copied");
            }
            break;
        }
        case WK::Paste:
            if (g_prevForeground) {
                HWND target = g_prevForeground;
                ShowWindow(hWnd, SW_HIDE);
                Sleep(60);
                PasteIntoWindow(target);
            }
            break;
        case WK::Clear:
            SetDlgItemText(hWnd, ID_EDIT_TEXT, L"");
            UpdatePlaceholder(hWnd);
            break;
        case WK::Pin:
            g_pinned = !g_pinned;
            SetWindowPos(hWnd, g_pinned ? HWND_TOPMOST : HWND_NOTOPMOST,
                         0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
            InvalidateRect(hWnd, nullptr, FALSE);
            PersistNow();
            break;
        case WK::SelAudio:
            ShowSelectPopup(hWnd, ID_SEL_AUDIO, g_audioItems, g_audioSel);
            break;
        case WK::SelModel:
            ShowSelectPopup(hWnd, ID_SEL_MODEL, g_modelItems, g_modelSel);
            break;
        default: break;
    }
}

void UpdatePlaceholder(HWND hwnd) {
    bool empty = GetWindowTextLengthW(GetDlgItem(hwnd, ID_EDIT_TEXT)) == 0;
    ShowWindow(GetDlgItem(hwnd, ID_STATIC_PLACEHOLDER), empty ? SW_SHOW : SW_HIDE);
}

void RefreshAudioDevices(HWND hwnd) {
    g_audioItems.clear();
    std::vector<std::string> devices = Transcriber::get_audio_devices();
    for (const auto& device : devices) {
        int len = MultiByteToWideChar(CP_UTF8, 0, device.c_str(), -1, nullptr, 0);
        if (len > 0) {
            std::vector<wchar_t> wbuf(len);
            MultiByteToWideChar(CP_UTF8, 0, device.c_str(), -1, wbuf.data(), len);
            g_audioItems.push_back(wbuf.data());
        }
    }
    if (g_audioItems.empty())
        g_audioItems.push_back(L"No devices");
    g_audioSel = 0;
    g_config.capture_id = 0;
    InvalidateRect(GetDlgItem(hwnd, ID_SEL_AUDIO), nullptr, FALSE);
}

void RefreshModelList(HWND hwnd) {
    g_modelItems.clear();
    g_modelComboPaths.clear();
    std::string dir = exe_dir();
    for (int i = 0; i < 4; ++i) {
        std::string full = dir + "\\" + kModelFiles[i];
        if (GetFileAttributesA(full.c_str()) != INVALID_FILE_ATTRIBUTES) {
            g_modelItems.push_back(kModelNames[i]);
            g_modelComboPaths.push_back(kModelFiles[i]);
        }
    }
    g_modelSel = 0;
    InvalidateRect(GetDlgItem(hwnd, ID_SEL_MODEL), nullptr, FALSE);
}

void SetStatus(HWND hwnd, const wchar_t* text) {
    g_statusOverride      = text;
    g_statusOverrideUntil = GetTickCount() + 2500;
    InvalidateRect(hwnd, nullptr, FALSE);
}

void UpdateStatus(HWND hwnd) {
    wchar_t buf[256];
    if (g_tx.is_recording()) {
        int secs = g_recordingSecs.load();
        swprintf_s(buf, L"Recording  %d:%02d", secs / 60, secs % 60);
        SetStatus(hwnd, buf);
    } else if (g_tx.is_busy()) {
        int   pct = (int)(g_progressFrac*100.0f + 0.5f);
        float total = g_tx.audio_seconds();
        int   mm    = (int)total / 60;
        int   ss    = (int)total % 60;
        int   rem = (int)(g_progressRemain + 0.5f);
        swprintf_s(buf, L"Transcribing %d:%02d  \u2022  %d%%  \u2022  %ds left", mm, ss, pct, rem);
        SetStatus(hwnd, buf);
    } else if (g_modelLoaded.load()) {
        if (!g_modelOk.load()) {
            SetStatus(hwnd, L"Model not found \u2014 check models folder");
            return;
        }
        swprintf_s(buf, L"Ready  \u2022  %d threads", g_tx.threads());
        SetStatus(hwnd, buf);
    } else {
        SetStatus(hwnd, L"Loading model\u2026");
    }
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
    switch (message) {
        case WM_CTLCOLORSTATIC:
        {
            HDC hdc = (HDC)wParam;
            if (GetDlgCtrlID((HWND)lParam) == ID_STATIC_PLACEHOLDER) {
                SetTextColor(hdc, RGB(0x9A, 0xA0, 0xAB));
                SetBkColor(hdc, CR_SURFACE);
                return (LRESULT)g_brSurface;
            }
            SetTextColor(hdc, CR_TEXT);
            SetBkColor(hdc, CR_BG);
            return (LRESULT)g_brBg;
        }
        case WM_CTLCOLOREDIT:
        {
            HDC hdc = (HDC)wParam;
            SetTextColor(hdc, CR_TEXT);
            SetBkColor(hdc, CR_SURFACE);
            return (LRESULT)g_brSurface;
        }

        case WM_ERASEBKGND:
            return 1;

        case WM_PAINT:
            PaintSurface(hWnd);
            return 0;

        case WM_MOUSEMOVE: {
            POINT p{ GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
            int hot = HitTest(p);
            if (hot != g_hot) {
                if (g_hot >= 0) g_w[g_hot].hover = false;
                g_hot = hot;
                if (g_hot >= 0) g_w[g_hot].hover = true;
                TRACKMOUSEEVENT t{ sizeof(t), TME_LEAVE, hWnd, 0 }; TrackMouseEvent(&t);
                EnsureAnimating(hWnd);
            }
            return 0;
        }
        case WM_MOUSELEAVE:
            if (g_hot >= 0) { g_w[g_hot].hover = false; g_hot = -1; EnsureAnimating(hWnd); }
            return 0;
        case WM_LBUTTONDOWN:
            g_active = g_hot;
            if (g_active >= 0) { g_w[g_active].pressed = true; SetCapture(hWnd); EnsureAnimating(hWnd); }
            return 0;
        case WM_LBUTTONUP: {
            ReleaseCapture();
            POINT p{ GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
            if (g_active >= 0 && HitTest(p) == g_active) OnClick(hWnd, g_w[g_active].kind);
            if (g_active >= 0) g_w[g_active].pressed = false;
            g_active = -1; EnsureAnimating(hWnd);
            return 0;
        }

        case WM_DPICHANGED: {
            g_dpiScale = LOWORD(wParam) / 96.0f;
            if (g_dpiScale <= 0.0f) g_dpiScale = 1.0f;
            RecreateFonts(g_dpiScale);
            RECT* sug = (RECT*)lParam;
            SetWindowPos(hWnd, nullptr, sug->left, sug->top,
                         sug->right - sug->left, sug->bottom - sug->top,
                         SWP_NOZORDER | SWP_NOACTIVATE);
            HWND hEdit = GetDlgItem(hWnd, ID_EDIT_TEXT);
            if (hEdit) SendMessage(hEdit, WM_SETFONT, (WPARAM)g_fText, TRUE);
            LayoutControls(hWnd, sug->right - sug->left, sug->bottom - sug->top);
            LayoutWidgets(sug->right - sug->left, sug->bottom - sug->top);
            InvalidateRect(hWnd, nullptr, TRUE);
            return 0;
        }

        case WM_SIZE:
            LayoutControls(hWnd, LOWORD(lParam), HIWORD(lParam));
            LayoutWidgets(LOWORD(lParam), HIWORD(lParam));
            return 0;

        case WM_EXITSIZEMOVE:
            PersistNow();
            return 0;

        case WM_GETMINMAXINFO:
            ((MINMAXINFO*)lParam)->ptMinTrackSize.x = (LONG)(340 * g_dpiScale);
            ((MINMAXINFO*)lParam)->ptMinTrackSize.y = (LONG)(280 * g_dpiScale);
            return 0;

        case WM_TIMER:
            if (wParam == 3) {
                DWORD now = GetTickCount();
                float dt = (now - g_lastFrame) / 1000.0f; g_lastFrame = now;
                if (dt <= 0.0f) dt = 0.016f;
                StepAnimations(dt);
                if (!AnyAnimating()) {
                    StopAnimating(hWnd);
                }
                InvalidateRect(hWnd, nullptr, FALSE);
            }
            if (wParam == ID_TIMER_UPDATE) {
                if (g_tx.is_recording()) {
                    g_energy = g_tx.get_audio_energy();
                    InvalidateRect(hWnd, nullptr, FALSE);
                    static DWORD lastTick = 0;
                    DWORD now = GetTickCount();
                    if (now - lastTick >= 1000) {
                        g_recordingSecs.store(g_recordingSecs.load() + 1);
                        lastTick = now;
                    }
                    if (g_tx.recorded_seconds() >= kMaxRecordSeconds) {
                        g_lastAudioLen = g_tx.recorded_seconds();
                        g_busyStart = GetTickCount();
                        g_lastTick   = g_busyStart;
                        g_progress  = 0;
                        g_cancelRequested = false;
                        g_est.begin(g_timing.predict(g_lastAudioLen));
                        g_tx.stop_and_transcribe();
                        SetStatus(hWnd, L"Max length reached \u2014 transcribing\u2026");
                    }
                } else {
                    g_energy = 0.0f;
                    if (g_tx.is_busy()) {
                        DWORD now = GetTickCount();
                        float dt  = (now - g_lastTick) / 1000.0f; g_lastTick = now;
                        if (dt <= 0.0f) dt = 0.05f;
                        g_est.tick(dt, g_progressFrac, g_progressRemain);
                        InvalidateRect(hWnd, &g_vuRect, FALSE);
                    }
                }
                UpdateStatus(hWnd);
            }
            break;

        case WM_HOTKEY:
            if (wParam == HK_TOGGLE) {
                if (g_tx.is_busy()) {
                    g_tx.request_cancel();
                    g_cancelRequested = true;
                    g_est.reset_busy();
                    g_progressFrac = 0.0f;
                    g_progressRemain = 0.0f;
                    SetStatus(hWnd, L"Cancelling\u2026");
                    break;
                }
                if (!g_tx.is_recording()) {
                    if (!g_modelLoaded.load()) { SetStatus(hWnd, L"Loading model\u2026"); break; }
                    if (!g_modelOk.load()) {
                        std::wstring m = L"Model not found:\n" + to_w(g_config.model_path)
                                       + L"\n\nPut the .bin there and restart.";
                        MessageBoxW(hWnd, m.c_str(), L"Dictation", MB_OK | MB_ICONWARNING);
                        break;
                    }
                    g_prevForeground = GetForegroundWindow();
                    ShowWindow(hWnd, SW_SHOWNA);
                    g_recordingSecs = 0;
                    if (g_tx.start_recording()) {
                        SetStatus(hWnd, L"Recording\u2026");
                    } else {
                        SetStatus(hWnd, L"Microphone error");
                    }
                } else {
                    g_lastAudioLen = g_tx.recorded_seconds();
                    g_busyStart = GetTickCount();
                    g_lastTick   = g_busyStart;
                    g_progress  = 0;
                    g_cancelRequested = false;
                    g_est.begin(g_timing.predict(g_lastAudioLen));
                    g_tx.stop_and_transcribe();
                    SetStatus(hWnd, L"Transcribing\u2026");
                }
            } else if (wParam == HK_HIDE) {
                if (g_tx.is_recording()) g_tx.cancel();
                ShowWindow(hWnd, SW_HIDE);
            }
            break;

        case WM_APP_SHOW:
            ShowWindow(hWnd, SW_SHOW);
            SetForegroundWindow(hWnd);
            break;

        case WM_APP_RESULT:
        {
            float frac, remain; g_est.finish(frac, remain);
            g_progressFrac = 1.0f; g_progressRemain = 0.0f;
            double actual = (GetTickCount() - g_busyStart) / 1000.0;
            if (g_lastAudioLen > 0.5 && actual > 0.2 && !g_cancelRequested) {
                g_timing.add_sample(g_lastAudioLen, actual);
                SaveTiming(g_timing, g_config.model_path);
            }

            std::string* res = (std::string*)wParam;
            if (res && !res->empty()) {
                char log[128];
                float secs = g_tx.audio_seconds();
                snprintf(log, sizeof(log), "Transcribed  %.1fs  %d chars", secs, (int)res->size());
                LogLine(log);
                HWND hEdit = GetDlgItem(hWnd, ID_EDIT_TEXT);
                int len = GetWindowTextLengthW(hEdit);
                std::wstring cur;
                if (len > 0) { cur.resize(len + 1); int g = GetWindowTextW(hEdit, &cur[0], len + 1); cur.resize(g); }
                std::wstring combined = append_transcript(cur, to_w(*res));
                SetWindowTextW(hEdit, combined.c_str());
                SendMessageW(hEdit, EM_SETSEL, (WPARAM)combined.size(), (LPARAM)combined.size());
                SendMessageW(hEdit, EM_SCROLLCARET, 0, 0);
                UpdatePlaceholder(hWnd);

                SetClipboardTextUtf8(hWnd, *res);

                bool pasted = false;
                if (g_autoPaste && g_prevForeground &&
                    g_prevForeground != hWnd && IsWindow(g_prevForeground)) {
                    PasteIntoWindow(g_prevForeground);
                    pasted = true;
                }
                if (pasted && g_autoHide) {
                    ShowWindow(hWnd, SW_HIDE);
                }
                SetStatus(hWnd, pasted ? L"Pasted" : L"Copied");
            } else {
                SetStatus(hWnd, g_cancelRequested ? L"Cancelled" : L"No speech detected");
            }
            delete res;
        }
        break;

        case WM_APP_PROGRESS: {
            double t_now = (GetTickCount() - g_busyStart) / 1000.0;
            g_est.on_whisper(t_now, (int)wParam);
            InvalidateRect(hWnd, &g_vuRect, FALSE);
            return 0;
        }

        case WM_APP_SELECT: {
            int ctrlId = (int)wParam, idx = (int)lParam;
            if (ctrlId == ID_SEL_AUDIO) {
                g_audioSel = idx;
                g_config.capture_id = idx;
                InvalidateRect(GetDlgItem(hWnd, ID_SEL_AUDIO), nullptr, FALSE);
                PersistNow();
            } else if (ctrlId == ID_SEL_MODEL && idx >= 0 && idx < (int)g_modelComboPaths.size()) {
                g_modelSel = idx;
                g_config.model_path = exe_dir() + "\\" + g_modelComboPaths[idx];
                SeedDefaults(g_timing, g_config.model_path);
                LoadTiming(g_timing, g_config.model_path);
                g_modelLoaded = false; g_modelOk = false;
                SetStatus(hWnd, L"Loading model\u2026");
                std::thread([] {
                    bool ok = g_tx.reload(g_config);
                    g_modelOk = ok; g_modelLoaded = true;
                }).detach();
                InvalidateRect(GetDlgItem(hWnd, ID_SEL_MODEL), nullptr, FALSE);
                PersistNow();
            }
            return 0;
        }

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case ID_TRAY_EXIT:
                    DestroyWindow(hWnd);
                    break;
                case ID_TRAY_SHOW:
                    ShowWindow(hWnd, SW_SHOW);
                    SetForegroundWindow(hWnd);
                    break;
                case ID_TRAY_AUTOPASTE:
                    g_autoPaste = !g_autoPaste;
                    PersistNow();
                    break;
                case ID_TRAY_TOPMOST:
                    g_pinned = !g_pinned;
                    SetWindowPos(hWnd, g_pinned ? HWND_TOPMOST : HWND_NOTOPMOST,
                                 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
                    InvalidateRect(GetDlgItem(hWnd, ID_BTN_PIN), nullptr, FALSE);
                    PersistNow();
                    break;
                case ID_TRAY_AUTOHIDE:
                    g_autoHide = !g_autoHide;
                    PersistNow();
                    break;
                case ID_BTN_RECORD:
                    PostMessage(hWnd, WM_HOTKEY, HK_TOGGLE, 0);
                    break;
                case ID_BTN_CLEAR:
                    SetDlgItemText(hWnd, ID_EDIT_TEXT, L"");
                    UpdatePlaceholder(hWnd);
                    break;
                case ID_BTN_PIN:
                    g_pinned = !g_pinned;
                    SetWindowPos(hWnd, g_pinned ? HWND_TOPMOST : HWND_NOTOPMOST,
                                 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
                    InvalidateRect(GetDlgItem(hWnd, ID_BTN_PIN), nullptr, FALSE);
                    PersistNow();
                    break;
                case ID_BTN_COPY:
                {
                    int len = GetWindowTextLengthW(GetDlgItem(hWnd, ID_EDIT_TEXT));
                    if (len > 0) {
                        std::vector<wchar_t> buf(len + 1);
                        GetDlgItemTextW(hWnd, ID_EDIT_TEXT, buf.data(), (int)buf.size());
                        std::wstring w(buf.data());
                        int u8len = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, nullptr, 0, nullptr, nullptr);
                        std::string u8(u8len ? u8len - 1 : 0, '\0');
                        if (u8len) WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, &u8[0], u8len, nullptr, nullptr);
                        SetClipboardTextUtf8(hWnd, u8);
                        SetStatus(hWnd, L"Copied");
                    }
                }
                break;
                case ID_BTN_PASTE:
                    if (g_prevForeground) {
                        HWND target = g_prevForeground;
                        ShowWindow(hWnd, SW_HIDE);
                        Sleep(60);
                        PasteIntoWindow(target);
                    }
                    break;
                case ID_SEL_AUDIO:
                    ShowSelectPopup(hWnd, ID_SEL_AUDIO, g_audioItems, g_audioSel);
                    break;
                case ID_SEL_MODEL:
                    ShowSelectPopup(hWnd, ID_SEL_MODEL, g_modelItems, g_modelSel);
                    break;
            }
            break;

        case WM_TRAYICON:
            if (lParam == WM_RBUTTONUP) {
                POINT pt; GetCursorPos(&pt);
                ShowContextMenu(hWnd, pt);
            } else if (lParam == WM_LBUTTONDBLCLK) {
                ShowWindow(hWnd, SW_SHOW);
                SetForegroundWindow(hWnd);
            }
            break;

        case WM_CLOSE:
            ShowWindow(hWnd, SW_HIDE);
            return 0;

        case WM_DESTROY:
            PersistNow();
            PostQuitMessage(0);
            break;

        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

void ShowContextMenu(HWND hwnd, POINT pt) {
    HMENU hMenu = CreatePopupMenu();
    InsertMenu(hMenu, 0, MF_BYPOSITION | MF_STRING, ID_TRAY_SHOW, L"Show Window");
    InsertMenu(hMenu, 1, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr);
    InsertMenu(hMenu, 2, MF_BYPOSITION | MF_STRING | (g_autoPaste ? MF_CHECKED : 0), ID_TRAY_AUTOPASTE, L"Auto-paste");
    InsertMenu(hMenu, 3, MF_BYPOSITION | MF_STRING | (g_pinned    ? MF_CHECKED : 0), ID_TRAY_TOPMOST,   L"Always on top");
    InsertMenu(hMenu, 4, MF_BYPOSITION | MF_STRING | (g_autoHide  ? MF_CHECKED : 0), ID_TRAY_AUTOHIDE,  L"Auto-hide");
    InsertMenu(hMenu, 5, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr);
    InsertMenu(hMenu, 6, MF_BYPOSITION | MF_STRING, ID_TRAY_EXIT, L"Exit");
    SetForegroundWindow(hwnd);
    TrackPopupMenu(hMenu, TPM_BOTTOMALIGN | TPM_LEFTALIGN, pt.x, pt.y, 0, hwnd, nullptr);
    DestroyMenu(hMenu);
}

std::string exe_dir() {
    char buf[MAX_PATH];
    GetModuleFileNameA(nullptr, buf, MAX_PATH);
    std::string p(buf);
    return p.substr(0, p.find_last_of("\\/"));
}

std::wstring to_w(const std::string& s) {
    if (s.empty()) return L"";
    int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, nullptr, 0);
    std::wstring w(n ? n - 1 : 0, L'\0');
    if (n) MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, &w[0], n);
    return w;
}

bool SetClipboardTextUtf8(HWND owner, const std::string& utf8) {
    std::wstring w = to_w(utf8);
    if (!OpenClipboard(owner)) return false;
    EmptyClipboard();
    size_t bytes = (w.size() + 1) * sizeof(wchar_t);
    HGLOBAL h = GlobalAlloc(GMEM_MOVEABLE, bytes);
    if (h) {
        void* p = GlobalLock(h);
        memcpy(p, w.c_str(), bytes);
        GlobalUnlock(h);
        SetClipboardData(CF_UNICODETEXT, h);
    }
    CloseClipboard();
    return h != nullptr;
}

void send_ctrl_v() {
    INPUT in[4] = {};
    in[0].type = INPUT_KEYBOARD; in[0].ki.wVk = VK_CONTROL;
    in[1].type = INPUT_KEYBOARD; in[1].ki.wVk = 'V';
    in[2].type = INPUT_KEYBOARD; in[2].ki.wVk = 'V';        in[2].ki.dwFlags = KEYEVENTF_KEYUP;
    in[3].type = INPUT_KEYBOARD; in[3].ki.wVk = VK_CONTROL; in[3].ki.dwFlags = KEYEVENTF_KEYUP;
    SendInput(4, in, sizeof(INPUT));
}

void PasteIntoWindow(HWND target) {
    if (!target || !IsWindow(target)) return;
    DWORD me    = GetCurrentThreadId();
    DWORD other = GetWindowThreadProcessId(target, nullptr);
    AttachThreadInput(me, other, TRUE);
    SetForegroundWindow(target);
    SetFocus(target);
    AttachThreadInput(me, other, FALSE);
    Sleep(40);
    send_ctrl_v();
}

bool DetectGPUAvailability() {
    const char* info = whisper_print_system_info();
    if (!info) return false;
    return (strstr(info, "CUDA") != nullptr ||
            strstr(info, "Metal") != nullptr ||
            strstr(info, "HIP")   != nullptr ||
            strstr(info, "Vulkan") != nullptr);
}

std::string SelectOptimalModel(bool has_gpu) {
    std::string dir = exe_dir();
    if (has_gpu) {
        if (GetFileAttributesA((dir + "\\models\\ggml-base.en.bin").c_str()) != INVALID_FILE_ATTRIBUTES)
            return "models\\ggml-base.en.bin";
        if (GetFileAttributesA((dir + "\\models\\ggml-tiny.en.bin").c_str()) != INVALID_FILE_ATTRIBUTES)
            return "models\\ggml-tiny.en.bin";
    } else {
        if (GetFileAttributesA((dir + "\\models\\ggml-tiny.en.bin").c_str()) != INVALID_FILE_ATTRIBUTES)
            return "models\\ggml-tiny.en.bin";
        if (GetFileAttributesA((dir + "\\models\\ggml-base.en.bin").c_str()) != INVALID_FILE_ATTRIBUTES)
            return "models\\ggml-base.en.bin";
    }
    return "models\\ggml-base.en.bin";
}
