use base64::{
    engine::general_purpose::{STANDARD as BASE64, STANDARD_NO_PAD},
    Engine,
};
use smallvec::SmallVec;
use std::collections::HashMap;
use sugarloaf::{ColorType, GraphicData, GraphicId, ResizeCommand, ResizeParameter};
use tracing::debug;

/// Per-terminal state for Kitty graphics protocol.
/// This stores the accumulated command state for chunked transmissions.
/// Each terminal instance should have its own state to prevent conflicts between tabs.
#[derive(Debug, Default)]
pub struct KittyGraphicsState {
    /// Stores incomplete image transfers (chunked transmissions).
    /// Key is the image_id or image_number from the first chunk.
    incomplete_images: HashMap<u32, KittyGraphicsCommand>,

    /// Tracks the current transmission key for chunks that don't specify an image ID.
    /// This is used for continuation chunks that only have m=1 or m=0.
    current_transmission_key: u32,

    /// Counter for auto-assigned image IDs. Per kitty spec, when a
    /// client transmits an image without an explicit `i=` (or `I=`)
    /// the terminal must allocate one. We allocate from the high half
    /// of the u32 range (`0x80000000..`) so the auto-assigned IDs do
    /// not collide with client-supplied IDs (which clients typically
    /// pick from `1..0x80000000`).
    next_auto_image_id: u32,
}

impl KittyGraphicsState {
    /// Allocate a fresh image_id for an implicit transmission.
    fn allocate_image_id(&mut self) -> u32 {
        if self.next_auto_image_id < 0x80000000 {
            self.next_auto_image_id = 0x80000000;
        }
        let id = self.next_auto_image_id;
        self.next_auto_image_id =
            self.next_auto_image_id.checked_add(1).unwrap_or(0x80000000);
        id
    }
}

#[derive(Debug)]
pub struct KittyGraphicsResponse {
    pub graphic_data: Option<GraphicData>,
    pub placement_request: Option<PlacementRequest>,
    pub delete_request: Option<DeleteRequest>,
    pub response: Option<String>,
    /// True when this "response" is just a chunk-accumulation
    /// acknowledgement — the parser stored the chunk and is waiting
    /// for more. The dispatcher should treat this as a successful
    /// no-op and must NOT log a parse failure for it (yazi and other
    /// TUIs send hundreds of chunked frames per second; spamming
    /// warnings on each chunk is the bug fix this field enables).
    pub incomplete: bool,
}

impl KittyGraphicsResponse {
    /// Sentinel returned for an in-progress chunked transmission. The
    /// dispatcher recognises this as "data accumulated, no action
    /// needed", as opposed to `None` which now means "real parse
    /// error".
    fn pending_chunk() -> Self {
        Self {
            graphic_data: None,
            placement_request: None,
            delete_request: None,
            response: None,
            incomplete: true,
        }
    }
}

#[derive(Debug)]
pub struct PlacementRequest {
    pub image_id: u32,
    pub placement_id: u32,
    pub x: u32,
    pub y: u32,
    pub width: u32,
    pub height: u32,
    pub columns: u32,
    pub rows: u32,
    pub z_index: i32,
    pub unicode_placeholder: u32,
    pub cursor_movement: u8, // 0 = move cursor to after image (default), 1 = don't move cursor
}

#[derive(Debug)]
pub struct DeleteRequest {
    pub action: u8,
    pub image_id: u32,
    pub placement_id: u32,
    pub x: u32,
    pub y: u32,
    pub z_index: i32,
    pub delete_data: bool,
}

#[derive(Debug, Clone, Copy, PartialEq)]
enum Action {
    Transmit,
    TransmitAndDisplay,
    Query,
    Put,
    Delete,
    Frame,
    Animate,
    Compose,
}

#[derive(Debug, Clone, Copy, PartialEq)]
enum Format {
    Gray,      // 1 byte per pixel
    GrayAlpha, // 2 bytes per pixel
    Rgb24,     // 3 bytes per pixel
    Rgba32,    // 4 bytes per pixel
    Png,
}

#[derive(Debug, Clone, Copy, PartialEq)]
enum TransmissionMedium {
    Direct,
    File,
    TempFile,
    SharedMemory,
}

#[derive(Debug, Clone, Copy, PartialEq)]
enum Compression {
    None,
    Zlib,
}

#[derive(Debug, Clone)]
pub struct KittyGraphicsCommand {
    // Action
    action: Action,
    quiet: u8,

    /// True when `image_id` was auto-assigned because the client did
    /// not supply `i=` or `I=`. Per kitty spec we must not echo a
    /// response back for these commands even though we now have an id
    /// internally.
    implicit_id: bool,

    // Image transmission
    format: Format,
    medium: TransmissionMedium,
    width: u32,
    height: u32,
    size: u32,
    offset: u32,
    image_id: u32,
    image_number: u32,
    placement_id: u32,
    compression: Compression,
    more: bool,

    // Image display
    source_x: u32,
    source_y: u32,
    source_width: u32,
    source_height: u32,
    cell_x_offset: u32,
    cell_y_offset: u32,
    columns: u32,
    rows: u32,
    cursor_movement: u8,
    virtual_placement: bool,
    z_index: i32,
    parent_id: u32,
    parent_placement_id: u32,
    relative_x: i32,
    relative_y: i32,

    // Animation frame loading
    frame_number: u32,
    base_frame: u32,
    frame_gap: i32,
    composition_mode: u8,
    background_color: u32,

    // Animation control
    animation_state: u8,
    loop_count: u32,
    current_frame: u32,

    // Delete
    delete_action: u8,

    // Placeholder
    unicode_placeholder: u32,

    // Payload - SmallVec for stack allocation of small payloads
    // 64 bytes inline covers most control-only commands (query, delete, placement)
    // while still handling large image data by spilling to heap
    payload: SmallVec<[u8; 64]>,
}

impl Default for KittyGraphicsCommand {
    fn default() -> Self {
        Self {
            action: Action::Transmit,
            quiet: 0,
            implicit_id: false,
            format: Format::Rgba32,
            medium: TransmissionMedium::Direct,
            width: 0,
            height: 0,
            size: 0,
            offset: 0,
            image_id: 0,
            image_number: 0,
            placement_id: 0,
            compression: Compression::None,
            more: false,
            source_x: 0,
            source_y: 0,
            source_width: 0,
            source_height: 0,
            cell_x_offset: 0,
            cell_y_offset: 0,
            columns: 0,
            rows: 0,
            cursor_movement: 0,
            virtual_placement: false,
            z_index: 0,
            parent_id: 0,
            parent_placement_id: 0,
            relative_x: 0,
            relative_y: 0,
            frame_number: 0,
            base_frame: 0,
            frame_gap: 0,
            composition_mode: 0,
            background_color: 0,
            animation_state: 0,
            loop_count: 0,
            current_frame: 0,
            delete_action: b'a',
            unicode_placeholder: 0,
            payload: SmallVec::new(),
        }
    }
}

pub fn parse(
    params: &[&[u8]],
    state: &mut KittyGraphicsState,
) -> Option<KittyGraphicsResponse> {
    let Some(&b"G") = params.first() else {
        debug!("Kitty graphics parse failed: first param is not 'G'");
        return None;
    };
    debug!(
        "Kitty graphics parse: starting with {} params",
        params.len()
    );
    for (i, param) in params.iter().enumerate() {
        debug!(
            "  param[{}] length={}, preview={:?}",
            i,
            param.len(),
            std::str::from_utf8(&param[..param.len().min(50)])
                .unwrap_or("(invalid utf8)")
        );
    }

    let mut cmd = KittyGraphicsCommand::default();

    // Parse control data if present
    if let Some(control) = params.get(1) {
        if !control.is_empty() {
            let control_data = std::str::from_utf8(control).ok()?;
            parse_control_data(&mut cmd, control_data);
        }
    }

    // Parse payload if present
    if let Some(payload) = params.get(2) {
        if !payload.is_empty() {
            cmd.payload = SmallVec::from_slice(payload);
        }
    }

    // Handle query action
    if cmd.action == Action::Query {
        let response = if cmd.quiet < 2 {
            format!("\x1b_Gi={};OK\x1b\\", cmd.image_id)
        } else {
            String::new()
        };
        return Some(KittyGraphicsResponse {
            graphic_data: None,
            placement_request: None,
            delete_request: None,
            response: Some(response),
            incomplete: false,
        });
    }

    // Handle chunked data
    // Determine the key for this chunk:
    // - If this chunk has an explicit image_id or image_number, use that.
    // - If no ID in this chunk and we are mid-transmission (a chunked
    //   command pinned `current_transmission_key`), reuse it.
    // - Otherwise the client sent a fresh command without an explicit id
    //   and we must allocate one per kitty spec.
    //
    // Importantly we only *pin* the key into `current_transmission_key`
    // when this is a chunked command (`cmd.more` is true). Pinning on
    // every command leaked into the next implicit command and made it
    // think it was a continuation chunk.
    let image_key = if cmd.image_id > 0 || cmd.image_number > 0 {
        if cmd.image_id > 0 {
            cmd.image_id
        } else {
            cmd.image_number
        }
    } else if state.current_transmission_key != 0 {
        // Continuation chunk: reuse the in-progress key
        state.current_transmission_key
    } else {
        // Fresh command without explicit id — allocate one. Mark as
        // implicit so we suppress the response per spec.
        let key = state.allocate_image_id();
        cmd.image_id = key;
        cmd.implicit_id = true;
        key
    };

    if cmd.more {
        // Pin the key for continuation chunks. Only chunked commands
        // touch `current_transmission_key` so non-chunked commands
        // don't leak state into subsequent transmissions.
        state.current_transmission_key = image_key;

        // Store chunk for later - preserve all metadata from first chunk
        use std::collections::hash_map::Entry;

        match state.incomplete_images.entry(image_key) {
            Entry::Vacant(e) => {
                // First chunk - move cmd into storage (no clone!)
                // Pre-allocate capacity if size is known to avoid reallocations
                let expected_size = cmd.size as usize;
                if expected_size > 0 && cmd.payload.capacity() < expected_size {
                    cmd.payload
                        .reserve(expected_size.saturating_sub(cmd.payload.len()));
                    debug!(
                        "First chunk for image key {}: {} bytes, reserved {} bytes total",
                        image_key,
                        cmd.payload.len(),
                        expected_size
                    );
                } else {
                    debug!(
                        "First chunk for image key {}: {} bytes",
                        image_key,
                        cmd.payload.len()
                    );
                }
                e.insert(cmd);
            }
            Entry::Occupied(mut e) => {
                // Subsequent chunk - just append payload
                let stored_cmd = e.get_mut();
                stored_cmd.payload.extend_from_slice(&cmd.payload);
                debug!(
                    "Appended chunk for image key {}: {} bytes accumulated",
                    image_key,
                    stored_cmd.payload.len()
                );
            }
        }
        // Tell the dispatcher this is an in-progress chunked
        // transmission, not an error. Returning None here would have
        // been logged as "Failed to parse" — yazi sends hundreds of
        // chunks per image preview and that flooded the warning log.
        return Some(KittyGraphicsResponse::pending_chunk());
    } else {
        // Check if we have incomplete data (even if image_id/number is 0)
        if let Some(mut stored_cmd) = state.incomplete_images.remove(&image_key) {
            // Final chunk: use metadata from stored command, append final payload
            stored_cmd.payload.extend_from_slice(&cmd.payload);
            cmd = stored_cmd; // Use stored metadata
            debug!(
                "Retrieved accumulated image key {}: total {} bytes",
                image_key,
                cmd.payload.len()
            );
            // Reset current transmission key after completing this transmission
            state.current_transmission_key = 0;
        }
    }

    // Convert to GraphicData based on action
    debug!("Kitty graphics action: {:?}, format={:?}, width={}, height={}, image_id={}, payload_len={}",
        cmd.action, cmd.format, cmd.width, cmd.height, cmd.image_id, cmd.payload.len());
    match cmd.action {
        Action::Transmit | Action::TransmitAndDisplay => {
            debug!("Creating graphic data: format={:?}, medium={:?}, compression={:?}, width={}, height={}, payload_len={}",
                cmd.format, cmd.medium, cmd.compression, cmd.width, cmd.height, cmd.payload.len());
            let graphic_data = create_graphic_data(&cmd)?;
            debug!(
                "Graphic data created successfully: {}x{}",
                graphic_data.width, graphic_data.height
            );
            let response = if cmd.quiet == 0
                && !cmd.implicit_id
                && (cmd.image_id > 0 || cmd.image_number > 0)
            {
                let id_part = if cmd.image_number > 0 {
                    format!("i={},I={}", graphic_data.id.get(), cmd.image_number)
                } else {
                    format!("i={}", cmd.image_id)
                };
                Some(format!("\x1b_G{};OK\x1b\\", id_part))
            } else {
                None
            };

            let placement_request = if cmd.action == Action::TransmitAndDisplay {
                Some(PlacementRequest {
                    image_id: cmd.image_id,
                    placement_id: cmd.placement_id,
                    x: cmd.source_x,
                    y: cmd.source_y,
                    width: cmd.source_width,
                    height: cmd.source_height,
                    columns: cmd.columns,
                    rows: cmd.rows,
                    z_index: cmd.z_index,
                    unicode_placeholder: cmd.unicode_placeholder,
                    cursor_movement: cmd.cursor_movement,
                })
            } else {
                None
            };

            Some(KittyGraphicsResponse {
                graphic_data: Some(graphic_data),
                placement_request,
                delete_request: None,
                response,
                incomplete: false,
            })
        }
        Action::Put => {
            // Handle placement request
            let placement = PlacementRequest {
                image_id: cmd.image_id,
                placement_id: cmd.placement_id,
                x: cmd.source_x,
                y: cmd.source_y,
                width: cmd.source_width,
                height: cmd.source_height,
                columns: cmd.columns,
                rows: cmd.rows,
                z_index: cmd.z_index,
                unicode_placeholder: cmd.unicode_placeholder,
                cursor_movement: cmd.cursor_movement,
            };
            let response = if cmd.quiet == 0 && !cmd.implicit_id && cmd.image_id > 0 {
                let id_part = if cmd.placement_id > 0 {
                    format!("i={},p={}", cmd.image_id, cmd.placement_id)
                } else {
                    format!("i={}", cmd.image_id)
                };
                Some(format!("\x1b_G{};OK\x1b\\", id_part))
            } else {
                None
            };
            Some(KittyGraphicsResponse {
                graphic_data: None,
                placement_request: Some(placement),
                delete_request: None,
                response,
                incomplete: false,
            })
        }
        Action::Delete => {
            // Handle delete request
            let delete_data = cmd.delete_action.is_ascii_uppercase();
            let delete = DeleteRequest {
                action: cmd.delete_action.to_ascii_lowercase(),
                image_id: cmd.image_id,
                placement_id: cmd.placement_id,
                x: cmd.source_x,
                y: cmd.source_y,
                z_index: cmd.z_index,
                delete_data,
            };
            Some(KittyGraphicsResponse {
                graphic_data: None,
                placement_request: None,
                delete_request: Some(delete),
                response: None,
                incomplete: false,
            })
        }
        Action::Query => {
            // Query is handled earlier in the function before the
            // chunking branches; the early return makes this arm
            // unreachable in practice.
            unreachable!("Query handled above")
        }
        Action::Frame | Action::Animate | Action::Compose => {
            // Animation actions are not supported. Per the kitty spec we
            // surface this so clients can detect the lack of support and
            // fall back, instead of silently dropping the command.
            // (Any chunked accumulation for this key was already drained
            // above when we entered the final-chunk branch.)
            //
            // Implicit-id transmissions still get no response, so the
            // client never sees stray APC traffic it didn't ask for.
            let response = if cmd.quiet < 2 && !cmd.implicit_id {
                let id_part = if cmd.image_id > 0 {
                    format!("i={}", cmd.image_id)
                } else if cmd.image_number > 0 {
                    format!("I={}", cmd.image_number)
                } else {
                    String::new()
                };
                if id_part.is_empty() {
                    Some("\x1b_G;EINVAL:unsupported action\x1b\\".to_string())
                } else {
                    Some(format!("\x1b_G{};EINVAL:unsupported action\x1b\\", id_part))
                }
            } else {
                None
            };
            Some(KittyGraphicsResponse {
                graphic_data: None,
                placement_request: None,
                delete_request: None,
                response,
                incomplete: false,
            })
        }
    }
}

fn parse_control_data(cmd: &mut KittyGraphicsCommand, control_data: &str) {
    // First pass: parse action to determine context
    for pair in control_data.split(',') {
        if let Some((key, value)) = pair.split_once('=') {
            if key == "a" {
                cmd.action = parse_action(value);
                break;
            }
        }
    }

    // Second pass: parse remaining keys based on action context
    for pair in control_data.split(',') {
        if let Some((key, value)) = pair.split_once('=') {
            match key {
                // Action (already parsed)
                "a" => {}
                "q" => cmd.quiet = value.parse().unwrap_or(0),

                // Image transmission
                "f" => cmd.format = parse_format(value),
                "t" => cmd.medium = parse_transmission_medium(value),
                "s" => match cmd.action {
                    Action::Animate => cmd.animation_state = value.parse().unwrap_or(0),
                    _ => cmd.width = value.parse().unwrap_or(0),
                },
                "v" => match cmd.action {
                    Action::Animate => cmd.loop_count = value.parse().unwrap_or(0),
                    _ => cmd.height = value.parse().unwrap_or(0),
                },
                "S" => cmd.size = value.parse().unwrap_or(0),
                "O" => cmd.offset = value.parse().unwrap_or(0),
                "i" => cmd.image_id = value.parse().unwrap_or(0),
                "I" => cmd.image_number = value.parse().unwrap_or(0),
                "p" => cmd.placement_id = value.parse().unwrap_or(0),
                "o" => cmd.compression = parse_compression(value),
                "m" => cmd.more = value == "1",

                // Context-dependent keys
                "x" => match cmd.action {
                    Action::Delete => cmd.source_x = value.parse().unwrap_or(0),
                    _ => cmd.source_x = value.parse().unwrap_or(0),
                },
                "y" => match cmd.action {
                    Action::Delete => cmd.source_y = value.parse().unwrap_or(0),
                    _ => cmd.source_y = value.parse().unwrap_or(0),
                },
                "w" => cmd.source_width = value.parse().unwrap_or(0),
                "h" => cmd.source_height = value.parse().unwrap_or(0),
                "X" => match cmd.action {
                    Action::Frame | Action::Compose => {
                        cmd.composition_mode = value.parse().unwrap_or(0)
                    }
                    _ => cmd.cell_x_offset = value.parse().unwrap_or(0),
                },
                "Y" => match cmd.action {
                    Action::Frame => cmd.background_color = value.parse().unwrap_or(0),
                    _ => cmd.cell_y_offset = value.parse().unwrap_or(0),
                },
                "c" => match cmd.action {
                    Action::Frame | Action::Compose => {
                        cmd.base_frame = value.parse().unwrap_or(0)
                    }
                    Action::Animate => cmd.current_frame = value.parse().unwrap_or(0),
                    _ => cmd.columns = value.parse().unwrap_or(0),
                },
                "r" => match cmd.action {
                    Action::Frame | Action::Compose | Action::Animate => {
                        cmd.frame_number = value.parse().unwrap_or(0)
                    }
                    _ => cmd.rows = value.parse().unwrap_or(0),
                },
                "z" => match cmd.action {
                    Action::Frame | Action::Animate => {
                        cmd.frame_gap = value.parse().unwrap_or(0)
                    }
                    _ => cmd.z_index = value.parse().unwrap_or(0),
                },

                // Other display keys
                "C" => cmd.cursor_movement = value.parse().unwrap_or(0),
                "U" => cmd.virtual_placement = value == "1",
                "P" => cmd.parent_id = value.parse().unwrap_or(0),
                "Q" => cmd.parent_placement_id = value.parse().unwrap_or(0),
                "H" => cmd.relative_x = value.parse().unwrap_or(0),
                "V" => cmd.relative_y = value.parse().unwrap_or(0),

                // Delete
                "d" => {
                    cmd.delete_action = value.as_bytes().first().copied().unwrap_or(b'a')
                }

                // Placeholder
                "u" => cmd.unicode_placeholder = value.parse().unwrap_or(0),

                _ => {} // Ignore unknown keys
            }
        }
    }
}

fn parse_action(value: &str) -> Action {
    match value {
        "t" => Action::Transmit,
        "T" => Action::TransmitAndDisplay,
        "q" => Action::Query,
        "p" => Action::Put,
        "d" => Action::Delete,
        "f" => Action::Frame,
        "a" => Action::Animate,
        "c" => Action::Compose,
        _ => Action::Transmit,
    }
}

fn parse_format(value: &str) -> Format {
    match value {
        "8" => Format::Gray,
        "16" => Format::GrayAlpha,
        "24" => Format::Rgb24,
        "32" => Format::Rgba32,
        "100" => Format::Png,
        _ => Format::Rgba32,
    }
}

fn parse_transmission_medium(value: &str) -> TransmissionMedium {
    match value {
        "d" => TransmissionMedium::Direct,
        "f" => TransmissionMedium::File,
        "t" => TransmissionMedium::TempFile,
        "s" => TransmissionMedium::SharedMemory,
        _ => TransmissionMedium::Direct,
    }
}

fn parse_compression(value: &str) -> Compression {
    match value {
        "z" => Compression::Zlib,
        _ => Compression::None,
    }
}

fn create_graphic_data(cmd: &KittyGraphicsCommand) -> Option<GraphicData> {
    // Get pixel data based on transmission medium
    let raw_data = match cmd.medium {
        TransmissionMedium::Direct => {
            // Decode base64 payload
            debug!("Decoding base64 payload, length={}", cmd.payload.len());
            match BASE64.decode(&cmd.payload) {
                Ok(data) => {
                    debug!("Base64 decoded successfully: {} bytes", data.len());
                    data
                }
                Err(e) => {
                    debug!("Base64 decode failed: {:?}", e);
                    return None;
                }
            }
        }
        TransmissionMedium::File | TransmissionMedium::TempFile => {
            // Read from file
            use std::fs::File;
            use std::io::Read;
            use std::path::Path;

            // Decode base64 payload to get file path
            // Try with standard decoder first, then without padding if that fails
            debug!(
                "Decoding base64 file path, payload length={}",
                cmd.payload.len()
            );
            let path_bytes = match BASE64.decode(&cmd.payload) {
                Ok(bytes) => {
                    debug!(
                        "Base64 decoded file path with padding: {} bytes",
                        bytes.len()
                    );
                    bytes
                }
                Err(_) => {
                    // Try without padding requirement
                    match STANDARD_NO_PAD.decode(&cmd.payload) {
                        Ok(bytes) => {
                            debug!(
                                "Base64 decoded file path without padding: {} bytes",
                                bytes.len()
                            );
                            bytes
                        }
                        Err(e) => {
                            debug!("Base64 decode failed (both with and without padding): {:?}", e);
                            return None;
                        }
                    }
                }
            };
            let path_str = std::str::from_utf8(&path_bytes).ok()?;
            debug!("File path: {}", path_str);
            let path = Path::new(path_str);

            // Security checks
            if !path.is_file() {
                return None;
            }

            // Check for sensitive paths
            let path_str_lower = path_str.to_lowercase();
            if path_str_lower.contains("/proc/")
                || path_str_lower.contains("/sys/")
                || path_str_lower.contains("/dev/")
            {
                return None;
            }

            // For temp files, verify it contains "tty-graphics-protocol"
            if cmd.medium == TransmissionMedium::TempFile
                && !path_str.contains("tty-graphics-protocol")
            {
                return None;
            }

            let mut file = File::open(path).ok()?;
            let mut data = Vec::new();

            if cmd.size > 0 {
                // Read specific size from offset
                if cmd.offset > 0 {
                    use std::io::Seek;
                    file.seek(std::io::SeekFrom::Start(cmd.offset as u64))
                        .ok()?;
                }
                data.resize(cmd.size as usize, 0);
                file.read_exact(&mut data).ok()?;
            } else {
                // Read entire file
                file.read_to_end(&mut data).ok()?;
            }

            // Delete temp file if requested
            if cmd.medium == TransmissionMedium::TempFile {
                let _ = std::fs::remove_file(path);
            }

            data
        }
        TransmissionMedium::SharedMemory => {
            #[cfg(unix)]
            {
                use std::ffi::CString;
                use std::os::unix::io::RawFd;

                // Payload contains the base64-encoded shared memory name
                debug!(
                    "Decoding shared memory name from base64, payload length={}",
                    cmd.payload.len()
                );
                let shm_name_bytes = match BASE64.decode(&cmd.payload) {
                    Ok(bytes) => {
                        debug!("Base64 decoded shm name: {} bytes", bytes.len());
                        bytes
                    }
                    Err(e) => {
                        debug!("Failed to decode shm name from base64: {:?}", e);
                        return None;
                    }
                };
                let shm_name_str = std::str::from_utf8(&shm_name_bytes).ok()?;
                let shm_name = CString::new(shm_name_str).ok()?;

                debug!(
                    "Opening shared memory: {}, expected size: {}",
                    shm_name_str,
                    cmd.width as usize * cmd.height as usize * 3 // RGB24
                );

                unsafe {
                    // Open shared memory
                    let fd: RawFd = libc::shm_open(shm_name.as_ptr(), libc::O_RDONLY, 0);

                    if fd < 0 {
                        let err = std::io::Error::last_os_error();
                        let errno = err.raw_os_error().unwrap_or(-1);
                        debug!(
                            "Failed to open shared memory '{}': {} (errno: {})",
                            shm_name_str, err, errno
                        );
                        return None;
                    }

                    // Get size of shared memory
                    let mut stat: libc::stat = std::mem::zeroed();
                    if libc::fstat(fd, &mut stat) < 0 {
                        libc::close(fd);
                        libc::shm_unlink(shm_name.as_ptr());
                        debug!("Failed to fstat shared memory");
                        return None;
                    }

                    let shm_size = stat.st_size as usize;
                    debug!("Shared memory size: {} bytes", shm_size);

                    // Use cmd.size if specified, otherwise use the full shm size
                    let data_size = if cmd.size > 0 {
                        cmd.size as usize
                    } else {
                        shm_size
                    };

                    if data_size > shm_size {
                        libc::close(fd);
                        libc::shm_unlink(shm_name.as_ptr());
                        debug!(
                            "Requested size {} exceeds shared memory size {}",
                            data_size, shm_size
                        );
                        return None;
                    }

                    // Map shared memory
                    let ptr = libc::mmap(
                        std::ptr::null_mut(),
                        data_size,
                        libc::PROT_READ,
                        libc::MAP_SHARED,
                        fd,
                        cmd.offset as libc::off_t,
                    );

                    if ptr == libc::MAP_FAILED {
                        libc::close(fd);
                        debug!("Failed to mmap shared memory");
                        return None;
                    }

                    // Copy data from shared memory
                    let data =
                        std::slice::from_raw_parts(ptr as *const u8, data_size).to_vec();

                    // Cleanup
                    libc::munmap(ptr, data_size);
                    libc::close(fd);
                    libc::shm_unlink(shm_name.as_ptr());

                    debug!("Successfully read {} bytes from shared memory", data.len());
                    data
                }
            }
            #[cfg(windows)]
            {
                use std::ffi::OsStr;
                use std::os::windows::ffi::OsStrExt;
                use windows_sys::Win32::Foundation::CloseHandle;
                use windows_sys::Win32::System::Memory::OpenFileMappingW;
                use windows_sys::Win32::System::Memory::{
                    MapViewOfFile, UnmapViewOfFile, VirtualQuery, FILE_MAP_READ,
                    MEMORY_BASIC_INFORMATION,
                };

                // Payload contains the base64-encoded shared memory name
                debug!(
                    "Decoding shared memory name from base64, payload length={}",
                    cmd.payload.len()
                );
                let shm_name_bytes = match BASE64.decode(&cmd.payload) {
                    Ok(bytes) => {
                        debug!("Base64 decoded shm name: {} bytes", bytes.len());
                        bytes
                    }
                    Err(e) => {
                        debug!("Failed to decode shm name from base64: {:?}", e);
                        return None;
                    }
                };
                let shm_name_str = std::str::from_utf8(&shm_name_bytes).ok()?;

                debug!("Opening shared memory: {}", shm_name_str);

                unsafe {
                    // Convert to wide string for Windows API
                    let wide_name: Vec<u16> = OsStr::new(shm_name_str)
                        .encode_wide()
                        .chain(std::iter::once(0))
                        .collect();

                    // Open the file mapping
                    let handle = OpenFileMappingW(FILE_MAP_READ, 0, wide_name.as_ptr());

                    if handle.is_null() {
                        let err = std::io::Error::last_os_error();
                        debug!(
                            "Failed to open shared memory '{}': {}",
                            shm_name_str, err
                        );
                        return None;
                    }

                    // Map view of file
                    let base_ptr = MapViewOfFile(handle, FILE_MAP_READ, 0, 0, 0);

                    if base_ptr.Value.is_null() {
                        let err = std::io::Error::last_os_error();
                        debug!("Failed to map view of file: {}", err);
                        CloseHandle(handle);
                        return None;
                    }

                    // Query memory to get size
                    let mut mem_info: MEMORY_BASIC_INFORMATION = std::mem::zeroed();
                    if VirtualQuery(
                        base_ptr.Value,
                        &mut mem_info,
                        std::mem::size_of::<MEMORY_BASIC_INFORMATION>(),
                    ) == 0
                    {
                        debug!("Failed to query memory information");
                        UnmapViewOfFile(base_ptr);
                        CloseHandle(handle);
                        return None;
                    }

                    let shm_size = mem_info.RegionSize;
                    debug!("Shared memory size: {} bytes", shm_size);

                    // Use cmd.size if specified, otherwise use the full shm size
                    let data_size = if cmd.size > 0 {
                        cmd.size as usize
                    } else {
                        shm_size
                    };

                    // Validate offset and size
                    if cmd.offset as usize + data_size > shm_size {
                        debug!(
                            "Requested offset {} + size {} exceeds shared memory size {}",
                            cmd.offset, data_size, shm_size
                        );
                        UnmapViewOfFile(base_ptr);
                        CloseHandle(handle);
                        return None;
                    }

                    // Copy data from shared memory
                    let data_ptr = (base_ptr.Value as *const u8).add(cmd.offset as usize);
                    let data = std::slice::from_raw_parts(data_ptr, data_size).to_vec();

                    // Cleanup
                    UnmapViewOfFile(base_ptr);
                    CloseHandle(handle);

                    debug!("Successfully read {} bytes from shared memory", data.len());
                    data
                }
            }
            #[cfg(not(any(unix, windows)))]
            {
                debug!("SharedMemory transmission not supported on this platform");
                return None;
            }
        }
    };

    // Decompress if needed
    let pixel_data = match cmd.compression {
        Compression::None => raw_data,
        Compression::Zlib => {
            use flate2::read::ZlibDecoder;
            use std::io::Read;

            let mut decoder = ZlibDecoder::new(&raw_data[..]);
            let mut decompressed = Vec::new();
            decoder.read_to_end(&mut decompressed).ok()?;
            decompressed
        }
    };

    // Parse based on format
    match cmd.format {
        Format::Png => {
            // Decode PNG data
            use image_rs::ImageFormat;

            debug!("Decoding PNG, pixel_data length: {}", pixel_data.len());
            let img = match image_rs::load_from_memory_with_format(
                &pixel_data,
                ImageFormat::Png,
            ) {
                Ok(img) => {
                    debug!("PNG decoded successfully: {}x{}", img.width(), img.height());
                    img
                }
                Err(e) => {
                    debug!("PNG decode failed: {:?}", e);
                    return None;
                }
            };
            let rgba_img = img.to_rgba8();
            let (width, height) = (rgba_img.width() as usize, rgba_img.height() as usize);
            let pixels = rgba_img.into_raw();

            // Check if image is opaque
            let is_opaque = pixels.chunks(4).all(|chunk| chunk[3] == 255);

            // Create resize command if columns/rows specified
            // When both c= and r= are given, stretch to fill (no aspect ratio).
            // When only one is given, compute the other preserving aspect ratio.
            let resize = if cmd.columns > 0 || cmd.rows > 0 {
                let both_specified = cmd.columns > 0 && cmd.rows > 0;
                Some(ResizeCommand {
                    width: if cmd.columns > 0 {
                        ResizeParameter::Cells(cmd.columns)
                    } else {
                        ResizeParameter::Auto
                    },
                    height: if cmd.rows > 0 {
                        ResizeParameter::Cells(cmd.rows)
                    } else {
                        ResizeParameter::Auto
                    },
                    preserve_aspect_ratio: !both_specified,
                })
            } else {
                None
            };

            Some(GraphicData {
                id: GraphicId::new(cmd.image_id as u64),
                width,
                height,
                color_type: ColorType::Rgba,
                pixels,
                is_opaque,
                resize,
                display_width: None,
                display_height: None,
                transmit_time: std::time::Instant::now(),
            })
        }
        Format::Gray | Format::GrayAlpha | Format::Rgb24 | Format::Rgba32 => {
            let bytes_per_pixel = match cmd.format {
                Format::Gray => 1,
                Format::GrayAlpha => 2,
                Format::Rgb24 => 3,
                Format::Rgba32 => 4,
                _ => unreachable!(),
            };

            // Validate data size
            let expected_size =
                cmd.width as usize * cmd.height as usize * bytes_per_pixel;
            if pixel_data.len() < expected_size {
                debug!(
                    "Pixel data size insufficient: got {} bytes, expected at least {}",
                    pixel_data.len(),
                    expected_size
                );
                return None;
            }

            // Truncate to expected size if we have extra data (e.g., from shared memory padding)
            let pixel_data = if pixel_data.len() > expected_size {
                pixel_data[..expected_size].to_vec()
            } else {
                pixel_data
            };

            // Convert all formats to RGBA (GPU only supports RGBA)
            let (pixels, is_opaque) = match cmd.format {
                Format::Gray => {
                    // 1 bpp: R=G=B=gray, A=255
                    let mut rgba =
                        Vec::with_capacity(cmd.width as usize * cmd.height as usize * 4);
                    for &g in &pixel_data {
                        rgba.extend_from_slice(&[g, g, g, 255]);
                    }
                    (rgba, true)
                }
                Format::GrayAlpha => {
                    // 2 bpp: R=G=B=gray, A=alpha
                    let mut rgba =
                        Vec::with_capacity(cmd.width as usize * cmd.height as usize * 4);
                    let mut opaque = true;
                    for chunk in pixel_data.chunks_exact(2) {
                        let g = chunk[0];
                        let a = chunk[1];
                        if a != 255 {
                            opaque = false;
                        }
                        rgba.extend_from_slice(&[g, g, g, a]);
                    }
                    (rgba, opaque)
                }
                Format::Rgb24 => {
                    // 3 bpp: add A=255
                    let mut rgba =
                        Vec::with_capacity(cmd.width as usize * cmd.height as usize * 4);
                    for chunk in pixel_data.chunks_exact(3) {
                        rgba.extend_from_slice(&[chunk[0], chunk[1], chunk[2], 255]);
                    }
                    (rgba, true)
                }
                Format::Rgba32 => {
                    // Already RGBA
                    let is_opaque = pixel_data.chunks(4).all(|chunk| chunk[3] == 255);
                    (pixel_data, is_opaque)
                }
                _ => unreachable!(),
            };

            // Create resize command if columns/rows specified
            // When both c= and r= are given, stretch to fill (no aspect ratio).
            // When only one is given, compute the other preserving aspect ratio.
            let resize = if cmd.columns > 0 || cmd.rows > 0 {
                let both_specified = cmd.columns > 0 && cmd.rows > 0;
                Some(ResizeCommand {
                    width: if cmd.columns > 0 {
                        ResizeParameter::Cells(cmd.columns)
                    } else {
                        ResizeParameter::Auto
                    },
                    height: if cmd.rows > 0 {
                        ResizeParameter::Cells(cmd.rows)
                    } else {
                        ResizeParameter::Auto
                    },
                    preserve_aspect_ratio: !both_specified,
                })
            } else {
                None
            };

            Some(GraphicData {
                id: GraphicId::new(cmd.image_id as u64),
                width: cmd.width as usize,
                height: cmd.height as usize,
                color_type: ColorType::Rgba, // Always RGBA after conversion
                pixels,
                is_opaque,
                resize,
                display_width: None,
                display_height: None,
                transmit_time: std::time::Instant::now(),
            })
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn parse_kitty_graphics_protocol(
        keys: &str,
        payload: &str,
    ) -> Option<KittyGraphicsResponse> {
        // Convert keys and payload to the format expected by parse()
        let params = if keys.is_empty() && payload.is_empty() {
            vec![b"G".as_ref()]
        } else if payload.is_empty() {
            vec![b"G".as_ref(), keys.as_bytes()]
        } else {
            vec![b"G".as_ref(), keys.as_bytes(), payload.as_bytes()]
        };

        let mut state = KittyGraphicsState::default();
        parse(&params, &mut state)
    }

    #[test]
    fn test_parse_basic_transmit() {
        // 1x1 RGBA pixel (4 bytes) - base64 encoded [255, 0, 0, 255] (red pixel)
        let payload = "/wAA/w==";
        let result = parse_kitty_graphics_protocol("a=t,f=32,s=1,v=1", payload);
        assert!(result.is_some());

        let response = result.unwrap();
        assert!(response.graphic_data.is_some());
        assert!(response.placement_request.is_none());
        assert!(response.delete_request.is_none());
    }

    #[test]
    fn test_parse_transmit_and_display() {
        // 1x1 RGBA pixel - base64 encoded [255, 0, 0, 255] (red pixel)
        let payload = "/wAA/w==";
        let result = parse_kitty_graphics_protocol("a=T,f=32,s=1,v=1,i=1", payload);
        assert!(result.is_some());

        let response = result.unwrap();
        assert!(response.graphic_data.is_some());
        assert!(response.placement_request.is_some());

        let placement = response.placement_request.unwrap();
        assert_eq!(placement.image_id, 1);
    }

    #[test]
    fn test_parse_placement() {
        let result = parse_kitty_graphics_protocol("a=p,i=1,x=10,y=20,c=5,r=3,z=2", "");
        assert!(result.is_some());

        let response = result.unwrap();
        assert!(response.graphic_data.is_none());
        assert!(response.placement_request.is_some());

        let placement = response.placement_request.unwrap();
        assert_eq!(placement.image_id, 1);
        assert_eq!(placement.x, 10);
        assert_eq!(placement.y, 20);
        assert_eq!(placement.columns, 5);
        assert_eq!(placement.rows, 3);
        assert_eq!(placement.z_index, 2);
    }

    #[test]
    fn test_parse_delete() {
        let result = parse_kitty_graphics_protocol("a=d,d=i,i=1", "");
        assert!(result.is_some());

        let response = result.unwrap();
        assert!(response.delete_request.is_some());

        let delete = response.delete_request.unwrap();
        assert_eq!(delete.action, b'i');
        assert_eq!(delete.image_id, 1);
        assert!(!delete.delete_data);
    }

    #[test]
    fn test_parse_delete_uppercase() {
        let result = parse_kitty_graphics_protocol("a=d,d=I,i=1", "");
        assert!(result.is_some());

        let response = result.unwrap();
        assert!(response.delete_request.is_some());

        let delete = response.delete_request.unwrap();
        assert_eq!(delete.action, b'i');
        assert_eq!(delete.image_id, 1);
        assert!(delete.delete_data);
    }

    #[test]
    fn test_parse_query() {
        let result = parse_kitty_graphics_protocol("a=q,i=1", "");
        assert!(result.is_some());

        let response = result.unwrap();
        assert!(response.response.is_some());
        assert!(response.response.unwrap().contains("OK"));
    }

    #[test]
    fn test_parse_with_compression() {
        // zlib compressed single RGBA pixel [255, 0, 0, 255]
        let payload = "eJz7z8DwHwAE/wH/";
        let result = parse_kitty_graphics_protocol("a=t,f=32,s=1,v=1,o=z", payload);
        assert!(result.is_some());

        let response = result.unwrap();
        assert!(response.graphic_data.is_some());
    }

    #[test]
    fn test_parse_with_unicode_placeholder() {
        let result = parse_kitty_graphics_protocol("a=p,i=1,u=128512", ""); // 😀
        assert!(result.is_some());

        let response = result.unwrap();
        assert!(response.placement_request.is_some());

        let placement = response.placement_request.unwrap();
        assert_eq!(placement.unicode_placeholder, 128512);
    }

    #[test]
    fn test_parse_png_format() {
        // Small 1x1 red PNG
        let png_data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==";
        let result = parse_kitty_graphics_protocol("a=t,f=100,i=1", png_data);
        assert!(result.is_some());

        let response = result.unwrap();
        assert!(response.graphic_data.is_some());
    }

    #[test]
    fn test_animation_frame_returns_unsupported_error() {
        // a=f (transmit animation frame) is not implemented; per spec we
        // surface EINVAL:unsupported action so clients can fall back.
        let payload = "AAAA";
        let result = parse_kitty_graphics_protocol("a=f,i=1,r=2,s=1,v=1,f=32", payload);
        let response = result.expect("animation actions must produce a response");
        assert!(response.graphic_data.is_none());
        assert!(response.placement_request.is_none());
        assert!(response.delete_request.is_none());
        let body = response.response.expect("error response expected");
        assert!(
            body.contains("i=1"),
            "response should echo image id: {body}"
        );
        assert!(
            body.contains("EINVAL:unsupported action"),
            "response should contain EINVAL: {body}"
        );
        assert!(body.starts_with("\x1b_G"), "response should be APC: {body}");
        assert!(
            body.ends_with("\x1b\\"),
            "response should end with ST: {body}"
        );
    }

    #[test]
    fn test_animation_control_returns_unsupported_error() {
        // a=a (animation control)
        let result = parse_kitty_graphics_protocol("a=a,i=42,s=3", "");
        let response = result.expect("animate action must produce a response");
        let body = response.response.expect("error response expected");
        assert!(body.contains("i=42"));
        assert!(body.contains("EINVAL:unsupported action"));
    }

    #[test]
    fn test_animation_compose_returns_unsupported_error() {
        // a=c (compose frames)
        let result = parse_kitty_graphics_protocol("a=c,i=7,r=1,c=2", "");
        let response = result.expect("compose action must produce a response");
        let body = response.response.expect("error response expected");
        assert!(body.contains("i=7"));
        assert!(body.contains("EINVAL:unsupported action"));
    }

    #[test]
    fn test_animation_error_uses_image_number_when_no_id() {
        // When only I= is given, the response should echo I=
        let result = parse_kitty_graphics_protocol("a=f,I=99,r=2,s=1,v=1,f=32", "AAAA");
        let response = result.expect("animation action must produce a response");
        let body = response.response.expect("error response expected");
        assert!(body.contains("I=99"), "expected I=99 in {body}");
        assert!(body.contains("EINVAL:unsupported action"));
    }

    #[test]
    fn test_animation_error_suppressed_when_quiet_2() {
        // q=2 should suppress error responses too
        let result =
            parse_kitty_graphics_protocol("a=f,i=1,r=2,s=1,v=1,f=32,q=2", "AAAA");
        let response = result.expect("response struct should still exist");
        assert!(
            response.response.is_none(),
            "q=2 should suppress error response"
        );
    }

    #[test]
    fn test_parse_invalid_action() {
        let result = parse_kitty_graphics_protocol("a=x", "");
        assert!(result.is_some()); // Falls back to Transmit
    }

    #[test]
    fn test_parse_empty_keys() {
        let mut state = KittyGraphicsState::default();

        // Empty params should return None
        let result = parse(&[], &mut state);
        assert!(result.is_none());

        // Just "G" with no control data returns an empty graphic
        let result = parse(&[b"G"], &mut state);
        assert!(result.is_some());
        let response = result.unwrap();
        assert!(response.graphic_data.is_some());
        let graphic = response.graphic_data.unwrap();
        assert_eq!(graphic.width, 0);
        assert_eq!(graphic.height, 0);
        assert!(graphic.pixels.is_empty());

        // "G" with empty control data also returns an empty graphic
        let result = parse(&[b"G", b""], &mut state);
        assert!(result.is_some());
    }

    #[test]
    fn test_incomplete_image_accumulation() {
        // Use a single state instance across all chunks
        let mut state = KittyGraphicsState::default();

        // First chunk - 1x1 RGBA pixel split into chunks
        // Total base64 for [255, 0, 0, 255] is "/wAA/w=="
        let params1 = vec![b"G".as_ref(), b"a=t,f=32,s=1,v=1,m=1,i=100", b"/wA"];
        let result1 = parse(&params1, &mut state).expect(
            "intermediate chunks must return a `pending_chunk` response, not None",
        );
        assert!(result1.incomplete, "first chunk must be marked incomplete");
        assert!(result1.graphic_data.is_none());

        // Second chunk - need to specify action and image id
        let params2 = vec![b"G".as_ref(), b"a=t,m=1,i=100", b"A/"];
        let result2 = parse(&params2, &mut state).expect("pending_chunk for chunk 2");
        assert!(result2.incomplete, "second chunk must be marked incomplete");

        // Final chunk - need to specify action, image id, and dimensions
        let params3 = vec![b"G".as_ref(), b"a=t,f=32,s=1,v=1,m=0,i=100", b"w=="];
        let result3 = parse(&params3, &mut state);
        assert!(result3.is_some()); // Should return complete image

        let response = result3.unwrap();
        assert!(!response.incomplete, "final chunk must not be incomplete");
        assert!(response.graphic_data.is_some());
    }

    #[test]
    fn test_pending_chunk_distinct_from_parse_error() {
        // Regression for the yazi log spam: an in-progress chunked
        // transmission must be distinguishable from a real parse error
        // so the dispatcher can suppress the warning. Real errors
        // (security check fail, missing dimensions, etc.) still return
        // None.
        let mut state = KittyGraphicsState::default();

        // m=1: pending — Some(incomplete=true)
        let params = vec![b"G".as_ref(), b"a=t,f=32,s=1,v=1,m=1,i=42", b"/wA"];
        let resp = parse(&params, &mut state).expect("pending must be Some");
        assert!(resp.incomplete);

        // Real error path: blocked path → None
        let proc_path = BASE64.encode("/proc/self/environ".as_bytes());
        let bad = vec![
            b"G".as_ref(),
            b"a=t,t=f,f=32,s=1,v=1,i=99",
            proc_path.as_bytes(),
        ];
        let resp = parse(&bad, &mut KittyGraphicsState::default());
        assert!(resp.is_none(), "real parse errors should still return None");
    }

    #[test]
    fn test_file_transmission_medium() {
        // Create a temporary file
        use std::io::Write;
        let temp_path = std::env::temp_dir().join("test_kitty_image.rgba");
        let temp_path = temp_path.to_str().unwrap();
        let mut file = std::fs::File::create(temp_path).unwrap();
        file.write_all(&[255, 0, 0, 255]).unwrap(); // 1x1 red pixel
        drop(file);

        // Encode the file path as base64 (as kitty does)
        let encoded_path = BASE64.encode(temp_path.as_bytes());
        let result =
            parse_kitty_graphics_protocol("a=t,t=f,f=32,s=1,v=1,i=1", &encoded_path);
        assert!(result.is_some());

        let response = result.unwrap();
        assert!(response.graphic_data.is_some());

        // Cleanup
        let _ = std::fs::remove_file(temp_path);
    }

    #[test]
    fn test_temp_file_transmission_medium() {
        // Create a temporary file with required naming
        use std::io::Write;
        let temp_path = std::env::temp_dir().join("tty-graphics-protocol-test.rgba");
        let temp_path = temp_path.to_str().unwrap();
        let mut file = std::fs::File::create(temp_path).unwrap();
        file.write_all(&[255, 0, 0, 255]).unwrap(); // 1x1 red pixel
        drop(file);

        // Encode the file path as base64 (as kitty does)
        let encoded_path = BASE64.encode(temp_path.as_bytes());
        let result =
            parse_kitty_graphics_protocol("a=t,t=t,f=32,s=1,v=1,i=1", &encoded_path);

        // File should be deleted after reading
        assert!(!std::path::Path::new(temp_path).exists());

        assert!(result.is_some());
        let response = result.unwrap();
        assert!(response.graphic_data.is_some());
    }

    #[test]
    fn test_security_checks() {
        // Should reject sensitive paths - encode as base64
        let proc_path = BASE64.encode("/proc/self/environ".as_bytes());
        let result = parse_kitty_graphics_protocol("a=t,t=f,f=32,s=1,v=1", &proc_path);
        assert!(result.is_none());

        let sys_path = BASE64.encode("/sys/class/net".as_bytes());
        let result = parse_kitty_graphics_protocol("a=t,t=f,f=32,s=1,v=1", &sys_path);
        assert!(result.is_none());

        let dev_path = BASE64.encode("/dev/null".as_bytes());
        let result = parse_kitty_graphics_protocol("a=t,t=f,f=32,s=1,v=1", &dev_path);
        assert!(result.is_none());
    }

    #[test]
    fn test_quiet_mode() {
        // q=1 should suppress OK response for placement
        let result = parse_kitty_graphics_protocol("a=p,i=1,q=1", "");
        assert!(result.is_some());

        let response = result.unwrap();
        // Placement with q=1 should not have response
        assert!(response.response.is_none());

        // q=2 should suppress all responses including query
        let result = parse_kitty_graphics_protocol("a=q,i=1,q=2", "");
        assert!(result.is_some());

        let response = result.unwrap();
        // Query with q=2 should have empty response
        assert_eq!(response.response, Some(String::new()));
    }
}
