//! QRCode widget — Win32 owner-drawn STATIC child rendering a QR
//! matrix generated by `qrcodegen`.
//!
//! macOS/iOS use `CIFilter("CIQRCodeGenerator")`; Android uses
//! zxing; GTK4 is stubbed. The Windows path keeps the same FFI shape
//! (`(data_ptr, size) -> handle` + `set_data(handle, data_ptr)`) and
//! pure-Rust QR generation via the `qrcodegen` crate. The widget is a
//! subclassed STATIC control whose `WM_PAINT` paints black squares
//! into the client rect from the cached `QrCode` matrix.
//!
//! Re-entrancy / thread-safety: matrix and the per-handle subclass
//! state live in thread-local storage, mirroring how textfield /
//! textarea / chart manage their per-handle metadata. The QR matrix
//! is regenerated on `set_data` and the control is invalidated.

use std::cell::RefCell;
use std::collections::HashMap;

#[cfg(target_os = "windows")]
use windows::Win32::Foundation::COLORREF;
#[cfg(target_os = "windows")]
use windows::Win32::Foundation::*;
#[cfg(target_os = "windows")]
use windows::Win32::Graphics::Gdi::{
    BeginPaint, CreateSolidBrush, DeleteObject, EndPaint, FillRect, InvalidateRect, PAINTSTRUCT,
};
#[cfg(target_os = "windows")]
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
#[cfg(target_os = "windows")]
use windows::Win32::UI::WindowsAndMessaging::*;

use super::{alloc_control_id, register_widget, WidgetKind};

fn str_from_header(ptr: *const u8) -> &'static str {
    if ptr.is_null() {
        return "";
    }
    unsafe {
        let header = ptr as *const perry_runtime::string::StringHeader;
        let len = (*header).byte_len as usize;
        let data = ptr.add(std::mem::size_of::<perry_runtime::string::StringHeader>());
        std::str::from_utf8_unchecked(std::slice::from_raw_parts(data, len))
    }
}

#[cfg(target_os = "windows")]
fn to_wide(s: &str) -> Vec<u16> {
    s.encode_utf16().chain(std::iter::once(0)).collect()
}

/// Per-handle QR code matrix. Stored as `Vec<bool>` row-major + side
/// length so the paint subclass can recompute pixel rects from the
/// current control client size.
struct QrMatrix {
    side: i32,
    bits: Vec<bool>,
}

thread_local! {
    static QR_MATRIX: RefCell<HashMap<i64, QrMatrix>> = RefCell::new(HashMap::new());
    /// HWNDs that already have the qrcode paint subclass installed.
    /// Keyed by HWND.0 as isize for Send-safety (we only borrow this
    /// across the Win32 call, never across the user callback).
    static QR_SUBCLASSED: RefCell<std::collections::HashSet<isize>> =
        RefCell::new(std::collections::HashSet::new());
}

#[cfg(target_os = "windows")]
const QR_SUBCLASS_ID: usize = 0x71_72_63_64; // 'q','r','c','d'

fn generate_matrix(text: &str) -> Option<QrMatrix> {
    use qrcodegen::{QrCode, QrCodeEcc};
    let qr = QrCode::encode_text(text, QrCodeEcc::Medium).ok()?;
    let side = qr.size();
    let mut bits = Vec::with_capacity((side as usize) * (side as usize));
    for y in 0..side {
        for x in 0..side {
            bits.push(qr.get_module(x, y));
        }
    }
    Some(QrMatrix { side, bits })
}

/// Create a QR code widget. Returns a widget handle.
pub fn create(data_ptr: *const u8, size: f64) -> i64 {
    let data = str_from_header(data_ptr);
    let display = if size > 0.0 { size as i32 } else { 200 };
    let control_id = alloc_control_id();

    #[cfg(target_os = "windows")]
    {
        let class_name = to_wide("STATIC");
        let window_text = to_wide("");
        unsafe {
            let hinstance = GetModuleHandleW(None).unwrap();
            let hwnd = CreateWindowExW(
                WINDOW_EX_STYLE::default(),
                windows::core::PCWSTR(class_name.as_ptr()),
                windows::core::PCWSTR(window_text.as_ptr()),
                WINDOW_STYLE(WS_CHILD.0 | WS_VISIBLE.0),
                0,
                0,
                display,
                display,
                super::get_parking_hwnd(),
                HMENU(control_id as *mut _),
                HINSTANCE::from(hinstance),
                None,
            )
            .unwrap();

            let handle = register_widget(hwnd, WidgetKind::Image, control_id);
            if !data.is_empty() {
                if let Some(matrix) = generate_matrix(data) {
                    QR_MATRIX.with(|m| {
                        m.borrow_mut().insert(handle, matrix);
                    });
                }
            }
            ensure_subclass(hwnd);
            let _ = InvalidateRect(hwnd, None, true);
            handle
        }
    }

    #[cfg(not(target_os = "windows"))]
    {
        let _ = (data, display);
        register_widget(0, WidgetKind::Image, control_id)
    }
}

/// Replace the QR code content of an existing widget.
pub fn set_data(handle: i64, data_ptr: *const u8) {
    let data = str_from_header(data_ptr);

    #[cfg(target_os = "windows")]
    {
        let matrix = generate_matrix(data);
        QR_MATRIX.with(|m| {
            let mut map = m.borrow_mut();
            match matrix {
                Some(matrix) => {
                    map.insert(handle, matrix);
                }
                None => {
                    map.remove(&handle);
                }
            }
        });
        if let Some(hwnd) = super::get_hwnd(handle) {
            unsafe {
                let _ = InvalidateRect(hwnd, None, true);
            }
        }
    }

    #[cfg(not(target_os = "windows"))]
    {
        let _ = (handle, data);
    }
}

#[cfg(target_os = "windows")]
fn ensure_subclass(hwnd: HWND) {
    use windows::Win32::UI::Shell::SetWindowSubclass;
    let key = hwnd.0 as isize;
    let installed = QR_SUBCLASSED.with(|s| s.borrow().contains(&key));
    if !installed {
        unsafe {
            let _ = SetWindowSubclass(hwnd, Some(qrcode_subclass_proc), QR_SUBCLASS_ID, 0);
        }
        QR_SUBCLASSED.with(|s| {
            s.borrow_mut().insert(key);
        });
    }
}

/// Paint subclass — draws the QR matrix on `WM_PAINT`. Background is
/// painted white; modules are filled black squares scaled to fit the
/// control's current client area. We pre-fill an 8-module quiet zone
/// (matches the QR spec; otherwise scanners reject the code on dense
/// backgrounds).
#[cfg(target_os = "windows")]
unsafe extern "system" fn qrcode_subclass_proc(
    hwnd: HWND,
    msg: u32,
    wparam: WPARAM,
    lparam: LPARAM,
    _id: usize,
    _refdata: usize,
) -> LRESULT {
    use windows::Win32::UI::Shell::DefSubclassProc;
    if msg == WM_PAINT {
        let mut ps = PAINTSTRUCT::default();
        let hdc = BeginPaint(hwnd, &mut ps);
        if !hdc.is_invalid() {
            let mut rect = RECT::default();
            let _ = GetClientRect(hwnd, &mut rect);
            let white = CreateSolidBrush(COLORREF(0x00_FF_FF_FF));
            FillRect(hdc, &rect, white);
            let _ = DeleteObject(white);

            let handle = super::find_handle_by_hwnd(hwnd);
            if handle > 0 {
                QR_MATRIX.with(|m| {
                    if let Some(matrix) = m.borrow().get(&handle) {
                        paint_matrix(hdc, &rect, matrix);
                    }
                });
            }
            let _ = EndPaint(hwnd, &ps);
        }
        return LRESULT(0);
    }
    DefSubclassProc(hwnd, msg, wparam, lparam)
}

#[cfg(target_os = "windows")]
unsafe fn paint_matrix(hdc: windows::Win32::Graphics::Gdi::HDC, rect: &RECT, matrix: &QrMatrix) {
    let cw = (rect.right - rect.left) as i32;
    let ch = (rect.bottom - rect.top) as i32;
    let area = cw.min(ch).max(1);
    // 4-module quiet zone on each side per QR spec (8 total). Scale
    // down so quiet + matrix fits, then re-center.
    let total_modules = matrix.side + 8;
    let scale = (area / total_modules).max(1);
    let pixel_size = scale * total_modules;
    let off_x = rect.left + (cw - pixel_size) / 2 + 4 * scale;
    let off_y = rect.top + (ch - pixel_size) / 2 + 4 * scale;

    let black = CreateSolidBrush(COLORREF(0));
    for y in 0..matrix.side {
        for x in 0..matrix.side {
            let idx = (y as usize) * (matrix.side as usize) + (x as usize);
            if matrix.bits[idx] {
                let pixel_rect = RECT {
                    left: off_x + x * scale,
                    top: off_y + y * scale,
                    right: off_x + (x + 1) * scale,
                    bottom: off_y + (y + 1) * scale,
                };
                FillRect(hdc, &pixel_rect, black);
            }
        }
    }
    let _ = DeleteObject(black);
}
