// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
//! Board support for ESoPe-SLD-C-W-S3 board with display and touch controller support.

extern crate alloc;

// Import embedded_graphics_core types
use embedded_graphics_core::pixelcolor::Rgb565;
use embedded_graphics_framebuf::backends::FrameBufferBackend;
// --- Slint platform integration imports ---
use slint::PhysicalSize;
use slint::platform::software_renderer::Rgb565Pixel;

use alloc::alloc::{alloc, handle_alloc_error};
use alloc::boxed::Box;
use alloc::rc::Rc;
use core::alloc::Layout;
use core::cell::RefCell;

use eeprom24x::{Eeprom24x, SlaveAddr};
use embedded_hal_bus::i2c::RefCellDevice;
use esp_hal::clock::CpuClock;
use esp_hal::dma::ExternalBurstConfig;
use esp_hal::dma::{CHUNK_SIZE, DmaDescriptor, DmaTxBuf};
use esp_hal::gpio::{Level, Output, OutputConfig};
use esp_hal::i2c::master::I2c;
use esp_hal::lcd_cam::{
    LcdCam,
    lcd::{
        ClockMode, Phase, Polarity,
        dpi::{Config as DpiConfig, Dpi, Format, FrameTiming},
    },
};

// Type alias for I2C device to simplify signatures
type I2cDevice = RefCellDevice<'static, esp_hal::i2c::master::I2c<'static, esp_hal::Blocking>>;
type TouchController = sitronix_touch::TouchIC<I2cDevice>;
use esp_hal::Config as HalConfig;
use esp_hal::peripherals::Peripherals;
use esp_hal::system::{CpuControl, Stack};
use esp_hal::time::{Instant, Rate};
use esp_hal::timer::{AnyTimer, timg::TimerGroup};
use esp_println::logger::init_logger_from_env;
use log::{debug, error, info};

use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::signal::Signal;
use embassy_time::{Duration, Ticker, Timer};
use esp_hal_embassy::Executor;
use static_cell::StaticCell;

// Static storage for I2C bus
static I2C_BUS: StaticCell<RefCell<I2c<'static, esp_hal::Blocking>>> = StaticCell::new();

// Constants matching Conway's implementation
const LCD_H_RES_USIZE: usize = 320;
const LCD_V_RES_USIZE: usize = 240;
const LCD_BUFFER_SIZE: usize = LCD_H_RES_USIZE * LCD_V_RES_USIZE;

// Embassy multicore: allocate app core stack with reduced size to save memory
static mut APP_CORE_STACK: Stack<4096> = Stack::new();

static PSRAM_READY: Signal<CriticalSectionRawMutex, ()> = Signal::new();
static DMA_READY: Signal<CriticalSectionRawMutex, ()> = Signal::new();
static FRAME_READY: Signal<CriticalSectionRawMutex, ()> = Signal::new();
static mut PSRAM_BUF_PTR: *mut u8 = core::ptr::null_mut();
static mut PSRAM_BUF_LEN: usize = 0;

// === Display constants ===
const LCD_H_RES: u16 = 320;
const LCD_V_RES: u16 = 240;

// Full-screen DMA constants
const MAX_FRAME_BYTES: usize = 320 * 240 * 2;
const MAX_NUM_DMA_DESC: usize = (MAX_FRAME_BYTES + CHUNK_SIZE - 1) / CHUNK_SIZE;

esp_bootloader_esp_idf::esp_app_desc!();

#[unsafe(link_section = ".dma")]
static mut TX_DESCRIPTORS: [DmaDescriptor; MAX_NUM_DMA_DESC] =
    [DmaDescriptor::EMPTY; MAX_NUM_DMA_DESC];

#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
    error!("PANIC: {}", info);
    loop {}
}

struct EspBackend {
    window: RefCell<Option<Rc<slint::platform::software_renderer::MinimalSoftwareWindow>>>,
    peripherals: RefCell<Option<Peripherals>>,
}

impl Default for EspBackend {
    fn default() -> Self {
        EspBackend { window: RefCell::new(None), peripherals: RefCell::new(None) }
    }
}

/// Initialize the heap and set the Slint platform.
pub fn init() {
    // Initialize peripherals first.
    let config = HalConfig::default().with_cpu_clock(CpuClock::max());
    let peripherals = esp_hal::init(config);
    init_logger_from_env();
    info!("=== ESP32-S3 ESoPe Board Initialization Starting ===");
    info!("Peripherals initialized");

    // Log memory status before PSRAM init
    let heap_start = esp_alloc::HEAP.used();
    info!("Heap usage before PSRAM init: {} bytes", heap_start);

    // Initialize the PSRAM allocator.
    esp_alloc::psram_allocator!(peripherals.PSRAM, esp_hal::psram);
    info!("PSRAM allocator initialized");

    // Log memory status after PSRAM init
    let heap_after_psram = esp_alloc::HEAP.used();
    info!(
        "Heap usage after PSRAM init: {} bytes (delta: +{})",
        heap_after_psram,
        heap_after_psram.saturating_sub(heap_start)
    );

    // Create and install the Slint backend that owns the peripherals.
    info!("Creating Slint platform backend...");
    let heap_before_backend = esp_alloc::HEAP.used();

    slint::platform::set_platform(Box::new(EspBackend {
        window: RefCell::new(None),
        peripherals: RefCell::new(Some(peripherals)),
    }))
    .expect("Slint platform already initialized");

    let heap_after_backend = esp_alloc::HEAP.used();
    info!(
        "Slint backend created. Heap usage: {} bytes (delta: +{})",
        heap_after_backend,
        heap_after_backend.saturating_sub(heap_before_backend)
    );
    info!("=== Initialization Complete ===");
}

/// FrameBufferBackend wrapper for a PSRAM-backed [Rgb565; N] slice.
pub struct PSRAMFrameBuffer<'a> {
    buf: &'a mut [Rgb565; LCD_BUFFER_SIZE],
}

impl<'a> PSRAMFrameBuffer<'a> {
    pub fn new(buf: &'a mut [Rgb565; LCD_BUFFER_SIZE]) -> Self {
        Self { buf }
    }
}

impl<'a> FrameBufferBackend for PSRAMFrameBuffer<'a> {
    type Color = Rgb565;
    fn set(&mut self, index: usize, color: Self::Color) {
        self.buf[index] = color;
    }
    fn get(&self, index: usize) -> Self::Color {
        self.buf[index]
    }
    fn nr_elements(&self) -> usize {
        LCD_BUFFER_SIZE
    }
}

impl slint::platform::Platform for EspBackend {
    fn create_window_adapter(
        &self,
    ) -> Result<Rc<dyn slint::platform::WindowAdapter>, slint::PlatformError> {
        let window = slint::platform::software_renderer::MinimalSoftwareWindow::new(
            slint::platform::software_renderer::RepaintBufferType::ReusedBuffer,
        );
        self.window.replace(Some(window.clone()));
        Ok(window)
    }

    fn duration_since_start(&self) -> core::time::Duration {
        core::time::Duration::from_millis(Instant::now().duration_since_epoch().as_millis())
    }

    fn run_event_loop(&self) -> Result<(), slint::PlatformError> {
        info!("=== Starting Main Event Loop ===");
        let heap_at_start = esp_alloc::HEAP.used();
        info!("Heap usage at event loop start: {} bytes", heap_at_start);

        let peripherals = self.peripherals.borrow_mut().take().expect("Peripherals already taken");

        // Read and set up the display configuration from EEPROM
        let i2c = I2c::new(peripherals.I2C0, esp_hal::i2c::master::Config::default())
            .unwrap()
            .with_sda(peripherals.GPIO1)
            .with_scl(peripherals.GPIO41);
        let i2c_bus = I2C_BUS.init(RefCell::new(i2c));
        let mut eeid = [0u8; 0x1c];
        let mut eeprom = Eeprom24x::new_24x01(RefCellDevice::new(i2c_bus), SlaveAddr::default());
        eeprom.read_data(0x00, &mut eeid).unwrap();
        let display_width = u16::from_be_bytes([eeid[8], eeid[9]]);
        let display_height = u16::from_be_bytes([eeid[10], eeid[11]]);
        info!("Display size from EEPROM: {}x{}", display_width, display_height);

        // Initialize touch controller using shared I2C bus
        info!("Initializing touch controller...");
        let touch_device = RefCellDevice::new(i2c_bus);
        let mut touch_controller = sitronix_touch::TouchIC::new_default(touch_device);
        match touch_controller.init() {
            Ok(_) => info!("Touch controller initialized successfully"),
            Err(e) => {
                error!("Failed to initialize touch controller: {:?}", e);
                // Continue without touch support
            }
        }

        // Enable panel / backlight
        let mut panel_enable = Output::new(peripherals.GPIO42, Level::Low, OutputConfig::default());
        panel_enable.set_high();

        let mut backlight = Output::new(peripherals.GPIO39, Level::Low, OutputConfig::default());
        backlight.set_high();

        let mut _touch_reset = Output::new(peripherals.GPIO2, Level::High, OutputConfig::default());

        info!("Display initialized, entering main loop...");

        // Allocate framebuffer in PSRAM with 64-byte alignment for DMA
        const FRAME_BYTES: usize = LCD_BUFFER_SIZE * 2;

        // Use aligned allocation for DMA requirements
        let layout = Layout::from_size_align(FRAME_BYTES, 64)
            .expect("Failed to create layout for framebuffer");
        let fb_ptr = unsafe { alloc(layout) };

        if fb_ptr.is_null() {
            handle_alloc_error(layout);
        }

        // Initialize the buffer with green color
        let fb_slice = unsafe { core::slice::from_raw_parts_mut(fb_ptr, FRAME_BYTES) };
        let rgb565_slice =
            unsafe { core::slice::from_raw_parts_mut(fb_ptr as *mut Rgb565, LCD_BUFFER_SIZE) };

        // Fill with green color (0, 31, 0)
        for pixel in rgb565_slice.iter_mut() {
            *pixel = Rgb565::new(0, 31, 0);
        }

        let psram_buf: &'static mut [u8] = fb_slice;

        // Verify PSRAM buffer allocation and alignment
        let buf_ptr = psram_buf.as_ptr() as usize;
        info!("PSRAM buffer allocated at address: 0x{:08X}", buf_ptr);
        info!("PSRAM buffer length: {}", psram_buf.len());
        info!("PSRAM buffer alignment modulo 64: {}", buf_ptr % 64);
        assert!(buf_ptr % 64 == 0, "PSRAM buffer must be 64-byte aligned for DMA");

        // Publish PSRAM buffer pointer and len for app core
        unsafe {
            PSRAM_BUF_PTR = psram_buf.as_mut_ptr();
            PSRAM_BUF_LEN = psram_buf.len();
        }

        // Configure DMA buffer with proper burst configuration
        info!("=== DMA Buffer Configuration ===");
        let heap_before_dma = esp_alloc::HEAP.used();
        info!("Heap usage before DMA buffer creation: {} bytes", heap_before_dma);

        let dma_tx: DmaTxBuf = unsafe {
            DmaTxBuf::new_with_config(
                &mut *core::ptr::addr_of_mut!(TX_DESCRIPTORS),
                psram_buf,
                ExternalBurstConfig::Size64,
            )
            .unwrap()
        };

        let heap_after_dma = esp_alloc::HEAP.used();
        info!(
            "Heap usage after DMA buffer creation: {} bytes (delta: +{})",
            heap_after_dma,
            heap_after_dma.saturating_sub(heap_before_dma)
        );

        // Initialize LCD DPI interface
        let lcd_cam = LcdCam::new(peripherals.LCD_CAM);

        // Read configuration from EEPROM
        let pclk_hz = ((eeid[12] as u32) * 1_000_000 + (eeid[13] as u32) * 100_000).min(13_600_000);
        let flags = eeid[25];
        let hsync_idle_low = (flags & 0x01) != 0;
        let vsync_idle_low = (flags & 0x02) != 0;
        let de_idle_high = (flags & 0x04) != 0;
        let pclk_active_neg = (flags & 0x20) != 0;

        // Log display configuration to match Conway's working values
        info!("Display configuration:");
        info!("  Resolution: {}x{}", display_width, display_height);
        info!("  PCLK: {} Hz", pclk_hz);
        info!("  Flags: 0x{:02X}", flags);
        info!("  HSYNC idle low: {}", hsync_idle_low);
        info!("  VSYNC idle low: {}", vsync_idle_low);
        info!("  DE idle high: {}", de_idle_high);
        info!("  PCLK active neg: {}", pclk_active_neg);

        let dpi_config = DpiConfig::default()
            .with_clock_mode(ClockMode {
                polarity: if pclk_active_neg { Polarity::IdleHigh } else { Polarity::IdleLow },
                phase: if pclk_active_neg { Phase::ShiftHigh } else { Phase::ShiftLow },
            })
            .with_frequency(Rate::from_hz(pclk_hz))
            .with_format(Format { enable_2byte_mode: true, ..Default::default() })
            // Use exact timing values that work with Conway's implementation
            .with_timing(FrameTiming {
                horizontal_active_width: 320,
                horizontal_total_width: 320 + 4 + 43 + 79 + 8, // =446 (Conway's working value)
                horizontal_blank_front_porch: 79 + 8,          // was 47, add 32px
                vertical_active_height: 240,
                vertical_total_height: 240 + 4 + 12 + 16, // increased blank front porch to 16
                vertical_blank_front_porch: 16,
                hsync_width: 4,
                vsync_width: 4,
                hsync_position: 43 + 4, // (= back_porch + pulse = 47) Conway's working value
            })
            .with_vsync_idle_level(if vsync_idle_low { Level::Low } else { Level::High })
            .with_hsync_idle_level(if hsync_idle_low { Level::Low } else { Level::High })
            .with_de_idle_level(if de_idle_high { Level::High } else { Level::Low })
            .with_disable_black_region(false);

        let dpi = Dpi::new(lcd_cam.lcd, peripherals.DMA_CH2, dpi_config)
            .unwrap()
            .with_vsync(peripherals.GPIO6)
            .with_hsync(peripherals.GPIO15)
            .with_de(peripherals.GPIO5)
            .with_pclk(peripherals.GPIO4)
            // Blue bus
            .with_data0(peripherals.GPIO9)
            .with_data1(peripherals.GPIO17)
            .with_data2(peripherals.GPIO46)
            .with_data3(peripherals.GPIO16)
            .with_data4(peripherals.GPIO7)
            // Green bus
            .with_data5(peripherals.GPIO8)
            .with_data6(peripherals.GPIO21)
            .with_data7(peripherals.GPIO3)
            .with_data8(peripherals.GPIO11)
            .with_data9(peripherals.GPIO18)
            .with_data10(peripherals.GPIO10)
            // Red bus
            .with_data11(peripherals.GPIO14)
            .with_data12(peripherals.GPIO20)
            .with_data13(peripherals.GPIO13)
            .with_data14(peripherals.GPIO19)
            .with_data15(peripherals.GPIO12);

        // Tell Slint the window dimensions match the display resolution
        let size = PhysicalSize::new(LCD_H_RES.into(), LCD_V_RES.into());
        self.window.borrow().as_ref().expect("Window adapter not created").set_size(size);

        // Initialize Embassy with both timers for multicore support
        info!("=== Embassy Initialization ===");
        let heap_before_embassy = esp_alloc::HEAP.used();
        info!("Heap usage before Embassy init: {} bytes", heap_before_embassy);

        let timg0 = TimerGroup::new(peripherals.TIMG0);
        let timer0: AnyTimer = timg0.timer0.into();
        let timg1 = TimerGroup::new(peripherals.TIMG1);
        let timer1: AnyTimer = timg1.timer0.into();

        info!("Initializing Embassy with dual timers for multicore support...");
        esp_hal_embassy::init([timer0, timer1]);

        let heap_after_embassy = esp_alloc::HEAP.used();
        info!(
            "Heap usage after Embassy init: {} bytes (delta: +{})",
            heap_after_embassy,
            heap_after_embassy.saturating_sub(heap_before_embassy)
        );

        // Signal that PSRAM is ready for the app core
        info!("Signaling PSRAM ready for app core...");
        PSRAM_READY.signal(());

        // Spawn app core for DMA display task (matching Conway)
        info!("=== App Core Startup ===");
        let heap_before_core = esp_alloc::HEAP.used();
        info!("Heap usage before app core startup: {} bytes", heap_before_core);

        let mut cpu_control = CpuControl::new(peripherals.CPU_CTRL);
        info!("Starting app core (Core 1) for DMA display task...");
        let _app_core = cpu_control.start_app_core(
            unsafe { &mut *core::ptr::addr_of_mut!(APP_CORE_STACK) },
            move || {
                info!("App core started! Initializing Embassy executor on Core 1...");

                // Initialize and run Embassy executor on app core
                static APP_EXECUTOR: StaticCell<Executor> = StaticCell::new();
                let executor = APP_EXECUTOR.init(Executor::new());
                info!("App core executor initialized, spawning DMA task...");

                executor.run(|spawner| match spawner.spawn(dma_display_task(dpi, dma_tx)) {
                    Ok(_) => info!("DMA display task spawned successfully on Core 1"),
                    Err(e) => error!("Failed to spawn DMA display task: {:?}", e),
                });
            },
        );

        // Initialize Embassy executor on main core for Slint rendering
        info!("=== Main Core Executor Setup ===");
        let heap_before_main_exec = esp_alloc::HEAP.used();
        info!("Heap usage before main executor init: {} bytes", heap_before_main_exec);

        static MAIN_EXECUTOR: StaticCell<Executor> = StaticCell::new();
        let executor = MAIN_EXECUTOR.init(Executor::new());
        info!("Main core executor initialized on Core 0");

        let window = self.window.borrow().as_ref().expect("Window not created").clone();

        let heap_before_rendering_spawn = esp_alloc::HEAP.used();
        info!(
            "Heap usage before Slint rendering task spawn: {} bytes",
            heap_before_rendering_spawn
        );

        executor.run(|spawner| {
            match spawner.spawn(slint_rendering_task(window, touch_controller)) {
                Ok(_) => info!("Slint rendering task spawned successfully on Core 0"),
                Err(e) => error!("Failed to spawn Slint rendering task: {:?}", e),
            }

            let heap_after_tasks = esp_alloc::HEAP.used();
            info!("Final heap usage after all tasks spawned: {} bytes", heap_after_tasks);
            info!("=== All tasks running, entering main executor loop ===");
        });
    }
}

#[embassy_executor::task]
async fn slint_rendering_task(
    window: Rc<slint::platform::software_renderer::MinimalSoftwareWindow>,
    mut touch_controller: TouchController,
) {
    info!("[CORE 1] Slint task starting, waiting for PSRAM ready signal...");

    // Wait for PSRAM to be ready
    PSRAM_READY.wait().await;
    info!("[CORE 1] PSRAM ready signal received!");

    // Get the PSRAM buffer
    let psram_ptr = unsafe { PSRAM_BUF_PTR };
    let psram_len = unsafe { PSRAM_BUF_LEN };

    if psram_ptr.is_null() || psram_len == 0 {
        error!(
            "[CORE 1] Invalid PSRAM buffer: ptr=0x{:08X}, len={}",
            psram_ptr as usize, psram_len
        );
        return;
    }

    let fb_slice: &mut [u8] = unsafe { core::slice::from_raw_parts_mut(psram_ptr, psram_len) };

    info!(
        "[CORE 1] Slint task started on Core 1, PSRAM buffer at: 0x{:08X}, len: {}",
        psram_ptr as usize, psram_len
    );

    // Create pixel buffer for Slint rendering in PSRAM (using Box allocation)
    info!("[CORE 1] Creating pixel buffer in PSRAM...");
    let mut pixel_box: Box<[Rgb565Pixel; LCD_BUFFER_SIZE]> =
        Box::new([Rgb565Pixel(0); LCD_BUFFER_SIZE]);
    let pixel_buf: &mut [Rgb565Pixel] = &mut *pixel_box;
    info!("[CORE 1] Pixel buffer created in PSRAM, {} pixels", LCD_BUFFER_SIZE);

    // Signal that DMA is ready to be used now that everything is initialized
    info!("[CORE 1] Signaling DMA ready for Core 0...");
    DMA_READY.signal(());

    let mut ticker = Ticker::every(Duration::from_millis(200));
    let mut frame_counter = 0u32;
    let mut last_position = slint::LogicalPosition::default();
    let mut touch_down = false;

    info!("[CORE 1] Entering main rendering loop with Slint rendering and touch support...");

    loop {
        // Update Slint timers and animations
        slint::platform::update_timers_and_animations();

        // Poll touch controller for input events
        if let Ok(maybe_touch) = touch_controller.get_point0() {
            if let Some(sitronix_touch::Point { x: touchpad_x, y: touchpad_y }) = maybe_touch {
                last_position = slint::LogicalPosition::new(touchpad_x as f32, touchpad_y as f32);

                // Dispatch the pointer moved event
                window.dispatch_event(slint::platform::WindowEvent::PointerMoved {
                    position: last_position,
                });

                if !touch_down {
                    window.dispatch_event(slint::platform::WindowEvent::PointerPressed {
                        position: last_position,
                        button: slint::platform::PointerEventButton::Left,
                    });
                    if frame_counter % 60 == 0 {
                        debug!("[CORE 1] Touch pressed at ({}, {})", touchpad_x, touchpad_y);
                    }
                }

                touch_down = true;
            } else if touch_down {
                window.dispatch_event(slint::platform::WindowEvent::PointerReleased {
                    position: last_position,
                    button: slint::platform::PointerEventButton::Left,
                });
                window.dispatch_event(slint::platform::WindowEvent::PointerExited);
                touch_down = false;

                if frame_counter % 60 == 0 {
                    debug!("[CORE 1] Touch released");
                }
            }
        }

        // Use draw_if_needed to check if we need to render and get access to the renderer
        let rendered = window.draw_if_needed(|renderer| {
            // Render the Slint window to our pixel buffer
            // Slint will handle partial rendering and only update the areas that changed
            renderer.render(pixel_buf, LCD_H_RES as usize);

            if frame_counter % 60 == 0 {
                debug!("[CORE 1] Frame {} rendered by Slint", frame_counter);
            }
        });

        // Only convert and signal if something was actually rendered
        if rendered {
            // Convert pixel buffer to framebuffer
            for (i, px) in pixel_buf.iter().enumerate() {
                let fb_offset = i * 2;
                let [lo, hi] = px.0.to_le_bytes();
                fb_slice[fb_offset] = lo;
                fb_slice[fb_offset + 1] = hi;
            }

            if frame_counter % 60 == 0 {
                debug!("[CORE 1] Frame {} actually rendered by Slint", frame_counter);
            }
        } else {
            // Still convert buffer even if nothing was rendered (for first frame or fallback)
            for (i, px) in pixel_buf.iter().enumerate() {
                let fb_offset = i * 2;
                let [lo, hi] = px.0.to_le_bytes();
                fb_slice[fb_offset] = lo;
                fb_slice[fb_offset + 1] = hi;
            }

            if frame_counter % 60 == 0 {
                debug!("[CORE 1] Frame {} - no Slint rendering needed", frame_counter);
            }
        }

        // Signal that frame is ready for DMA
        FRAME_READY.signal(());

        frame_counter = frame_counter.wrapping_add(1);

        // Log periodic status
        if frame_counter % 60 == 0 {
            debug!("[CORE 1] Frame {}, continuing render loop...", frame_counter);
        }

        ticker.next().await;
    }
}

#[embassy_executor::task]
async fn dma_display_task(mut dpi: Dpi<'static, esp_hal::Blocking>, mut dma_tx: DmaTxBuf) {
    info!("[CORE 0] DMA task started on Core 0, waiting for DMA ready signal...");

    // Wait for DMA to be ready (all initialization complete)
    DMA_READY.wait().await;
    info!("[CORE 0] DMA ready signal received, starting DMA transfers!");

    // Stack monitoring removed for compilation compatibility

    let mut transfer_counter = 0u32;
    // Wait for frame to be ready
    FRAME_READY.wait().await;
    loop {
        transfer_counter = transfer_counter.wrapping_add(1);

        // Log periodic DMA status
        if transfer_counter % 60 == 0 {
            debug!("[CORE 0] DMA transfer {}, performing transfer...", transfer_counter);
        }

        // Set DMA transfer length (like Conway's working example)
        let frame_bytes = LCD_BUFFER_SIZE * 2;
        let dma_buf_len = dma_tx.as_slice().len();

        if transfer_counter % 60 == 0 {
            debug!(
                "[CORE 0] Setting DMA length: {} bytes, buffer len: {} bytes",
                frame_bytes, dma_buf_len
            );
        }

        if frame_bytes > dma_buf_len {
            error!("[CORE 0] Frame size {} exceeds DMA buffer size {}", frame_bytes, dma_buf_len);
            Timer::after(Duration::from_millis(10)).await;
            continue;
        }

        dma_tx.set_length(frame_bytes);

        // Perform DMA transfer
        match dpi.send(false, dma_tx) {
            Ok(xfer) => {
                let (res, new_dpi, new_dma_tx) = xfer.wait();
                dpi = new_dpi;
                dma_tx = new_dma_tx;
                if let Err(e) = res {
                    error!("[CORE 0] DMA transfer error: {:?}", e);
                } else if transfer_counter % 60 == 0 {
                    debug!("[CORE 0] DMA transfer {} completed successfully", transfer_counter);
                }
            }
            Err((e, new_dpi, new_dma_tx)) => {
                error!("[CORE 0] DMA send error: {:?}", e);
                dpi = new_dpi;
                dma_tx = new_dma_tx;

                // Add small delay on error to prevent spinning
                Timer::after(Duration::from_millis(100)).await;
            }
        }
    }
}
