//! P5-44 — Taskbar thumbnail toolbar (call controls).
//!
//! While a voice/video call is active the web client calls `set_thumbbar` from
//! `useTauriThumbbar`, which mirrors the call-embed atom + mic/sound state onto
//! three buttons on the taskbar thumbnail toolbar: **Mute/Unmute**,
//! **Deafen/Undeafen** and **End Call**. Clicking a button pushes a
//! `thumbbar-action` DOM event back to the web side (`"mute" | "deafen" | "end"`)
//! which drives the real call controls.
//!
//! Windows: `ITaskbarList3::ThumbBarAddButtons` (first call for the window) then
//! `ThumbBarUpdateButtons` (subsequent calls) — mirrors the COM + GDI/HICON idiom
//! in `set_badge_count`. Thumb-button clicks arrive as `WM_COMMAND` with
//! `HIWORD(wParam) == THBN_CLICKED`, so we subclass the main window (installed
//! once in `setup`) to catch them. The main window HWND comes from the "main"
//! webview window; the "buttons added" flag lives in managed `ThumbbarState`
//! (like lib.rs's `TrayUnreadState`) so add-vs-update works across calls.
//!
//! Other platforms are a no-op — the command stays cross-platform so the web
//! side is unconditional.

use tauri::{AppHandle, Manager};

/// Managed state shared with lib.rs (registered in `setup`). Only the Windows
/// path reads `added`; kept cross-platform so `set_thumbbar` can inject it.
#[derive(Default)]
pub struct ThumbbarState {
    #[allow(dead_code)]
    added: std::sync::atomic::AtomicBool,
}

// Thumb-button ids (LOWORD of wParam on WM_COMMAND / THBN_CLICKED).
#[cfg(target_os = "windows")]
const BTN_MUTE: u32 = 1;
#[cfg(target_os = "windows")]
const BTN_DEAFEN: u32 = 2;
#[cfg(target_os = "windows")]
const BTN_END: u32 = 3;

/// HIWORD(wParam) value on a thumb-button click (CommCtrl `THBN_CLICKED`).
#[cfg(target_os = "windows")]
const THBN_CLICKED: u16 = 0x1800;

/// uIdSubclass passed to SetWindowSubclass — identifies our subclass instance.
#[cfg(target_os = "windows")]
const SUBCLASS_ID: usize = 1;

/// Payload emitted to the web on a thumb-button click.
#[cfg(target_os = "windows")]
#[derive(serde::Serialize)]
struct Action<'a> {
    action: &'a str,
}

/// Build a single THUMBBUTTON, attaching an icon (and the THB_ICON mask) when one
/// was created. Always carries a tooltip and enabled/hidden flags.
#[cfg(target_os = "windows")]
fn thumb_button(
    id: u32,
    hidden: bool,
    tip: &str,
    icon: Option<windows::Win32::UI::WindowsAndMessaging::HICON>,
) -> windows::Win32::UI::Shell::THUMBBUTTON {
    use windows::Win32::UI::Shell::{
        THUMBBUTTON, THUMBBUTTONFLAGS, THUMBBUTTONMASK, THBF_ENABLED, THBF_HIDDEN, THB_FLAGS,
        THB_ICON, THB_TOOLTIP,
    };
    use windows::Win32::UI::WindowsAndMessaging::HICON;

    let mut mask: THUMBBUTTONMASK = THB_TOOLTIP | THB_FLAGS;
    let flags: THUMBBUTTONFLAGS = if hidden { THBF_HIDDEN } else { THBF_ENABLED };
    let mut hicon = HICON::default();
    if let Some(i) = icon {
        mask = mask | THB_ICON;
        hicon = i;
    }
    let mut sz_tip = [0u16; 260];
    for (dst, ch) in sz_tip.iter_mut().zip(tip.encode_utf16().take(259)) {
        *dst = ch;
    }
    THUMBBUTTON {
        dwMask: mask,
        iId: id,
        iBitmap: 0,
        hIcon: hicon,
        szTip: sz_tip,
        dwFlags: flags,
    }
}

/// Update (or hide) the three thumb-toolbar buttons for the given call state.
#[tauri::command]
pub fn set_thumbbar(
    app: AppHandle,
    state: tauri::State<'_, ThumbbarState>,
    active: bool,
    muted: bool,
    deafened: bool,
) -> Result<(), String> {
    #[cfg(target_os = "windows")]
    {
        use std::sync::atomic::Ordering;
        use windows::Win32::{
            Foundation::HWND,
            System::Com::{CoCreateInstance, CLSCTX_INPROC_SERVER},
            UI::{
                Shell::{ITaskbarList3, TaskbarList},
                WindowsAndMessaging::DestroyIcon,
            },
        };

        // Nothing to do (and nothing to hide) if a toolbar was never added.
        if !active && !state.added.load(Ordering::SeqCst) {
            return Ok(());
        }

        let window = app
            .get_webview_window("main")
            .ok_or_else(|| "no main window".to_string())?;
        let hwnd = HWND(window.hwnd().map_err(|e| e.to_string())?.0 as _);

        let mic_icon = make_icon(Glyph::Mic, muted);
        let deaf_icon = make_icon(Glyph::Head, deafened);
        let end_icon = make_icon(Glyph::End, false);

        let buttons = [
            thumb_button(BTN_MUTE, !active, if muted { "Unmute" } else { "Mute" }, mic_icon),
            thumb_button(
                BTN_DEAFEN,
                !active,
                if deafened { "Undeafen" } else { "Deafen" },
                deaf_icon,
            ),
            thumb_button(BTN_END, !active, "End Call", end_icon),
        ];

        let result = unsafe {
            let taskbar: ITaskbarList3 = CoCreateInstance(&TaskbarList, None, CLSCTX_INPROC_SERVER)
                .map_err(|e| e.to_string())?;
            taskbar.HrInit().map_err(|e| e.to_string())?;

            let r = if state.added.load(Ordering::SeqCst) {
                taskbar.ThumbBarUpdateButtons(hwnd, &buttons)
            } else {
                let r = taskbar.ThumbBarAddButtons(hwnd, &buttons);
                if r.is_ok() {
                    state.added.store(true, Ordering::SeqCst);
                }
                r
            };
            r.map_err(|e| e.to_string())
        };

        // The shell copies the icons on add/update, so release ours (mirrors the
        // DestroyIcon after SetOverlayIcon in set_badge_count).
        for icon in [mic_icon, deaf_icon, end_icon].into_iter().flatten() {
            unsafe {
                let _ = DestroyIcon(icon);
            }
        }

        result?;
    }

    #[cfg(not(target_os = "windows"))]
    {
        // No-op elsewhere; bind args so the signature stays identical and no
        // unused warnings fire.
        let _ = (&app, &state, active, muted, deafened);
    }

    Ok(())
}

/// Which glyph a thumb-button icon draws.
#[cfg(target_os = "windows")]
#[derive(Clone, Copy)]
enum Glyph {
    Mic,
    Head,
    End,
}

/// Draw a simple white monochrome glyph onto a 32x32 32-bpp DIB and wrap it in an
/// `HICON`. Mirrors the CreateDIBSection → alpha-fixup → CreateIconIndirect idiom
/// in `set_badge_count`. Returns `None` on any GDI failure (the button is then
/// added tooltip-only). `slashed` overlays a transparent diagonal cut to signal
/// the muted / deafened state.
#[cfg(target_os = "windows")]
fn make_icon(
    glyph: Glyph,
    slashed: bool,
) -> Option<windows::Win32::UI::WindowsAndMessaging::HICON> {
    use windows::core::BOOL;
    use windows::Win32::Foundation::COLORREF;
    use windows::Win32::Graphics::Gdi::{
        Arc, CreateBitmap, CreateCompatibleDC, CreateDIBSection, CreatePen, CreateSolidBrush,
        DeleteDC, DeleteObject, Ellipse, GetDC, LineTo, MoveToEx, ReleaseDC, RoundRect,
        SelectObject, BITMAPINFO, BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS, PS_SOLID,
    };
    use windows::Win32::UI::WindowsAndMessaging::{CreateIconIndirect, ICONINFO};

    unsafe {
        let size = 32i32;
        let hdc_screen = GetDC(None);
        let hdc = CreateCompatibleDC(Some(hdc_screen));

        let bmi = BITMAPINFO {
            bmiHeader: BITMAPINFOHEADER {
                biSize: std::mem::size_of::<BITMAPINFOHEADER>() as u32,
                biWidth: size,
                biHeight: -size,
                biPlanes: 1,
                biBitCount: 32,
                biCompression: BI_RGB.0,
                ..Default::default()
            },
            ..Default::default()
        };
        let mut bits: *mut std::ffi::c_void = std::ptr::null_mut();
        let hbm_color = CreateDIBSection(Some(hdc), &bmi, DIB_RGB_COLORS, &mut bits, None, 0).ok()?;
        if !bits.is_null() {
            std::ptr::write_bytes(bits as *mut u8, 0, (size * size * 4) as usize);
        }
        let old_bm = SelectObject(hdc, hbm_color.into());

        let white = COLORREF(0x00FF_FFFF);
        let hbrush = CreateSolidBrush(white);
        let old_brush = SelectObject(hdc, hbrush.into());
        let hpen = CreatePen(PS_SOLID, 2, white);
        let old_pen = SelectObject(hdc, hpen.into());

        match glyph {
            Glyph::Mic => {
                // Capsule mic head + stand.
                let _ = RoundRect(hdc, 13, 5, 19, 19, 6, 6);
                let _ = MoveToEx(hdc, 16, 19, None);
                let _ = LineTo(hdc, 16, 25);
                let _ = MoveToEx(hdc, 11, 25, None);
                let _ = LineTo(hdc, 21, 25);
            }
            Glyph::Head => {
                // Headphone band + two ear cups.
                let _ = Arc(hdc, 6, 7, 26, 27, 6, 17, 26, 17);
                let _ = RoundRect(hdc, 6, 16, 11, 26, 2, 2);
                let _ = RoundRect(hdc, 21, 16, 26, 26, 2, 2);
            }
            Glyph::End => {
                // Filled disc (end-call button).
                let _ = Ellipse(hdc, 6, 6, 26, 26);
            }
        }

        if slashed {
            // Draw the slash in black (pixel 0), which the alpha fixup below
            // leaves fully transparent — carving a visible diagonal gap.
            let black_pen = CreatePen(PS_SOLID, 4, COLORREF(0));
            let prev = SelectObject(hdc, black_pen.into());
            let _ = MoveToEx(hdc, 6, 6, None);
            let _ = LineTo(hdc, 26, 26);
            SelectObject(hdc, prev);
            let _ = DeleteObject(black_pen.into());
        }

        SelectObject(hdc, old_brush);
        SelectObject(hdc, old_pen);
        SelectObject(hdc, old_bm);
        let _ = DeleteObject(hbrush.into());
        let _ = DeleteObject(hpen.into());

        // GDI leaves alpha at 0; mark every painted pixel opaque so Windows uses
        // per-pixel alpha instead of the opaque mask (same fix as set_badge_count).
        let pixel_count = (size * size) as usize;
        let pixels = std::slice::from_raw_parts_mut(bits as *mut u32, pixel_count);
        for pixel in pixels.iter_mut() {
            if *pixel != 0 {
                *pixel |= 0xFF00_0000u32;
            }
        }

        let hbm_mask = CreateBitmap(size, size, 1, 1, None);
        if hbm_mask.0 as usize == 0 {
            let _ = DeleteObject(hbm_color.into());
            let _ = DeleteDC(hdc);
            let _ = ReleaseDC(None, hdc_screen);
            return None;
        }

        let icon_info = ICONINFO {
            fIcon: BOOL(1),
            xHotspot: 0,
            yHotspot: 0,
            hbmMask: hbm_mask,
            hbmColor: hbm_color,
        };
        let hicon = CreateIconIndirect(&icon_info).ok();

        let _ = DeleteObject(hbm_color.into());
        let _ = DeleteObject(hbm_mask.into());
        let _ = DeleteDC(hdc);
        let _ = ReleaseDC(None, hdc_screen);

        hicon
    }
}

/// Window subclass proc: catches thumb-button clicks (`WM_COMMAND` /
/// `THBN_CLICKED`) and forwards them to the web as `thumbbar-action`. `dwrefdata`
/// is a leaked `Box<AppHandle>` installed by `setup`; it is reclaimed on
/// `WM_NCDESTROY`.
#[cfg(target_os = "windows")]
unsafe extern "system" fn subclass_proc(
    hwnd: windows::Win32::Foundation::HWND,
    umsg: u32,
    wparam: windows::Win32::Foundation::WPARAM,
    lparam: windows::Win32::Foundation::LPARAM,
    _uidsubclass: usize,
    dwrefdata: usize,
) -> windows::Win32::Foundation::LRESULT {
    use windows::Win32::Foundation::LRESULT;
    use windows::Win32::UI::Shell::{DefSubclassProc, RemoveWindowSubclass};
    use windows::Win32::UI::WindowsAndMessaging::{WM_COMMAND, WM_NCDESTROY};

    match umsg {
        WM_COMMAND => {
            let w = wparam.0;
            let notif = ((w >> 16) & 0xFFFF) as u16;
            let id = (w & 0xFFFF) as u32;
            if notif == THBN_CLICKED {
                let action = match id {
                    BTN_MUTE => Some("mute"),
                    BTN_DEAFEN => Some("deafen"),
                    BTN_END => Some("end"),
                    _ => None,
                };
                if let Some(action) = action {
                    if dwrefdata != 0 {
                        // Borrow (do not take ownership of) the leaked AppHandle.
                        let app = &*(dwrefdata as *const AppHandle);
                        let detail =
                            serde_json::to_string(&Action { action }).unwrap_or_default();
                        super::emit_to_web(app, "thumbbar-action", &detail);
                    }
                    return LRESULT(0);
                }
            }
            DefSubclassProc(hwnd, umsg, wparam, lparam)
        }
        WM_NCDESTROY => {
            let _ = RemoveWindowSubclass(hwnd, Some(subclass_proc), SUBCLASS_ID);
            if dwrefdata != 0 {
                drop(Box::from_raw(dwrefdata as *mut AppHandle));
            }
            DefSubclassProc(hwnd, umsg, wparam, lparam)
        }
        _ => DefSubclassProc(hwnd, umsg, wparam, lparam),
    }
}

/// Called once from `native::setup`. Registers `ThumbbarState` and, on Windows,
/// subclasses the main window so thumb-button clicks reach the web client.
pub fn setup(app: &AppHandle) -> tauri::Result<()> {
    app.manage(ThumbbarState::default());

    #[cfg(target_os = "windows")]
    {
        use windows::Win32::Foundation::HWND;
        use windows::Win32::UI::Shell::SetWindowSubclass;

        if let Some(window) = app.get_webview_window("main") {
            if let Ok(handle) = window.hwnd() {
                let hwnd = HWND(handle.0 as _);
                // Leak an AppHandle for the proc; reclaimed on WM_NCDESTROY.
                let refdata = Box::into_raw(Box::new(app.clone())) as usize;
                unsafe {
                    let _ = SetWindowSubclass(hwnd, Some(subclass_proc), SUBCLASS_ID, refdata);
                }
            }
        }
    }

    Ok(())
}
