/**
 * @file main.c
 * @brief ESP32-S3 GameBoy - FIXED Audio Frequency Calculation!
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_heap_caps.h"
#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"
#include "driver/sdmmc_host.h"
#include "driver/sdspi_host.h"
#include "driver/i2s.h"

#include "hardware_config.h"
#include "st7789.h"

#define DISPLAY_WIDTH LCD_WIDTH
#define DISPLAY_HEIGHT LCD_HEIGHT
#undef LCD_WIDTH
#undef LCD_HEIGHT

// ============================================
// APU Constants
// ============================================
#define SAMPLE_RATE 32768
#define SAMPLES_PER_FRAME 546  // 32768 Hz / 60 FPS = 546 samples/frame
#define SAMPLES_PER_BUFFER 512

// GameBoy CPU frequency
#define GB_CPU_FREQ 4194304.0f

// Samples per GB CPU cycle
#define CYCLES_PER_SAMPLE (GB_CPU_FREQ / SAMPLE_RATE)  // ~128

static const char *TAG = "GB";

// ============================================
// APU Registers (directly mapped)
// ============================================
static uint8_t apu_regs[48] = {0};
static uint8_t wave_ram[16] = {0};

// Master control
static bool master_enable = false;
static uint8_t master_vol_left = 7;
static uint8_t master_vol_right = 7;
static uint8_t panning = 0xFF;

// Channel 1 state
static struct {
    bool active;
    bool dac_on;  // DAC enable bit (NR12 bit 3-7 != 0)
    uint8_t duty;
    uint8_t volume;
    uint16_t freq_raw;
    float phase;
} ch1 = {0};

// Channel 2 state
static struct {
    bool active;
    bool dac_on;  // DAC enable bit (NR22 bit 3-7 != 0)
    uint8_t duty;
    uint8_t volume;
    uint16_t freq_raw;
    float phase;
} ch2 = {0};

// Channel 3 state
static struct {
    bool active;
    bool dac_on;
    uint8_t volume_shift;
    uint16_t freq_raw;
    float phase;
} ch3 = {0};

// Channel 4 state
static struct {
    bool active;
    uint8_t volume;
    uint16_t lfsr;
    uint8_t divisor;
    uint8_t shift;
    bool width_mode;
    float timer;
} ch4 = {.lfsr = 0x7FFF};

// Audio system
static bool audio_enabled = false;
static int16_t *audio_buffer = NULL;
static SemaphoreHandle_t apu_mutex = NULL;

// Debug
static int audio_write_count = 0;

// Duty waveforms (8 steps each) - BIPOLAR for proper square waves!
static const int8_t duty_table[4][8] = {
    {-1, -1, -1, -1, -1, -1, -1,  1},  // 12.5% duty cycle
    { 1, -1, -1, -1, -1, -1, -1,  1},  // 25% duty cycle
    { 1, -1, -1, -1, -1,  1,  1,  1},  // 50% duty cycle
    {-1,  1,  1,  1,  1,  1,  1, -1},  // 75% duty cycle (inverted)
};

// ============================================
// Peanut-GB Audio Callbacks
// ============================================

uint8_t audio_read(const uint16_t addr);
void audio_write(const uint16_t addr, const uint8_t val);

uint8_t audio_read(const uint16_t addr)
{
    if (addr >= 0xFF30 && addr <= 0xFF3F) {
        return wave_ram[addr - 0xFF30];
    }
    if (addr == 0xFF26) {
        uint8_t status = master_enable ? 0x80 : 0x00;
        if (ch1.active) status |= 0x01;
        if (ch2.active) status |= 0x02;
        if (ch3.active) status |= 0x04;
        if (ch4.active) status |= 0x08;
        return status | 0x70;
    }
    if (addr >= 0xFF10 && addr <= 0xFF3F) {
        return apu_regs[addr - 0xFF10];
    }
    return 0xFF;
}

void audio_write(const uint16_t addr, const uint8_t val)
{
    if (apu_mutex) xSemaphoreTake(apu_mutex, portMAX_DELAY);
    
    audio_write_count++;
    
    // Wave RAM
    if (addr >= 0xFF30 && addr <= 0xFF3F) {
        wave_ram[addr - 0xFF30] = val;
        if (apu_mutex) xSemaphoreGive(apu_mutex);
        return;
    }
    
    // Store raw register
    if (addr >= 0xFF10 && addr <= 0xFF3F) {
        apu_regs[addr - 0xFF10] = val;
    }
    
    switch (addr) {
        // === NR52 - Master Control ===
        case 0xFF26:
            master_enable = (val & 0x80) != 0;
            if (!master_enable) {
                ch1.active = ch2.active = ch3.active = ch4.active = false;
                memset(apu_regs, 0, 0x17);
            }
            break;
            
        // === Channel 1 - Square with Sweep ===
        case 0xFF11:  // NR11 - Duty & Length
            ch1.duty = (val >> 6) & 3;
            break;

        case 0xFF12:  // NR12 - Volume Envelope
            ch1.volume = (val >> 4) & 0x0F;
            ch1.dac_on = (val & 0xF8) != 0;  // DAC enable check
            if (!ch1.dac_on) ch1.active = false;
            break;
            
        case 0xFF13:  // NR13 - Freq Low
            ch1.freq_raw = (ch1.freq_raw & 0x700) | val;
            break;
            
        case 0xFF14:  // NR14 - Freq High + Trigger
            ch1.freq_raw = (ch1.freq_raw & 0xFF) | ((val & 0x07) << 8);
            if (val & 0x80) {
                ch1.active = ch1.dac_on;  // Only activate if DAC is on
                ch1.phase = 0;
                ch1.volume = (apu_regs[0x02] >> 4) & 0x0F;
            }
            break;
            
        // === Channel 2 - Square ===
        case 0xFF16:  // NR21 - Duty & Length
            ch2.duty = (val >> 6) & 3;
            break;

        case 0xFF17:  // NR22 - Volume Envelope
            ch2.volume = (val >> 4) & 0x0F;
            ch2.dac_on = (val & 0xF8) != 0;  // DAC enable check
            if (!ch2.dac_on) ch2.active = false;
            break;
            
        case 0xFF18:  // NR23 - Freq Low
            ch2.freq_raw = (ch2.freq_raw & 0x700) | val;
            break;
            
        case 0xFF19:  // NR24 - Freq High + Trigger
            ch2.freq_raw = (ch2.freq_raw & 0xFF) | ((val & 0x07) << 8);
            if (val & 0x80) {
                ch2.active = ch2.dac_on;  // Only activate if DAC is on
                ch2.phase = 0;
                ch2.volume = (apu_regs[0x07] >> 4) & 0x0F;
            }
            break;
            
        // === Channel 3 - Wave ===
        case 0xFF1A:  // NR30 - DAC Enable
            ch3.dac_on = (val & 0x80) != 0;
            if (!ch3.dac_on) ch3.active = false;
            break;
            
        case 0xFF1C:  // NR32 - Volume
            ch3.volume_shift = (val >> 5) & 3;
            break;
            
        case 0xFF1D:  // NR33 - Freq Low
            ch3.freq_raw = (ch3.freq_raw & 0x700) | val;
            break;
            
        case 0xFF1E:  // NR34 - Freq High + Trigger
            ch3.freq_raw = (ch3.freq_raw & 0xFF) | ((val & 0x07) << 8);
            if (val & 0x80) {
                ch3.active = ch3.dac_on;
                ch3.phase = 0;
            }
            break;
            
        // === Channel 4 - Noise ===
        case 0xFF21:  // NR42 - Volume Envelope
            ch4.volume = (val >> 4) & 0x0F;
            if ((val & 0xF8) == 0) ch4.active = false;
            break;
            
        case 0xFF22:  // NR43 - Polynomial Counter
            ch4.shift = (val >> 4) & 0x0F;
            ch4.width_mode = (val >> 3) & 1;
            ch4.divisor = val & 0x07;
            break;
            
        case 0xFF23:  // NR44 - Trigger
            if (val & 0x80) {
                ch4.active = true;
                ch4.lfsr = 0x7FFF;
                ch4.timer = 0;
                ch4.volume = (apu_regs[0x11] >> 4) & 0x0F;
            }
            break;
            
        // === Master Volume & Panning ===
        case 0xFF24:  // NR50
            master_vol_left = (val >> 4) & 7;
            master_vol_right = val & 7;
            break;
            
        case 0xFF25:  // NR51
            panning = val;
            break;
    }
    
    if (apu_mutex) xSemaphoreGive(apu_mutex);
}

// ============================================
// Audio Sample Generation
// ============================================

static inline float get_frequency(uint16_t freq_raw)
{
    // GameBoy frequency formula: f = 131072 / (2048 - freq_raw)
    if (freq_raw >= 2048) return 0;
    return 131072.0f / (2048.0f - freq_raw);
}

static inline float get_wave_frequency(uint16_t freq_raw)
{
    // Wave channel: f = 65536 / (2048 - freq_raw)
    if (freq_raw >= 2048) return 0;
    return 65536.0f / (2048.0f - freq_raw);
}

static void generate_samples(int16_t *buffer, int num_samples)
{
    if (apu_mutex) xSemaphoreTake(apu_mutex, portMAX_DELAY);
    
    for (int i = 0; i < num_samples; i++) {
        int32_t left = 0;
        int32_t right = 0;
        
        if (!master_enable) {
            buffer[i * 2] = 0;
            buffer[i * 2 + 1] = 0;
            continue;
        }
        
        // === Channel 1 - Square Wave ===
        if (ch1.active && ch1.dac_on && ch1.volume > 0 && ch1.freq_raw > 0) {
            float freq = get_frequency(ch1.freq_raw);
            float phase_inc = freq / SAMPLE_RATE;

            ch1.phase += phase_inc;
            if (ch1.phase >= 1.0f) ch1.phase -= 1.0f;

            int step = (int)(ch1.phase * 8) & 7;
            int sample = duty_table[ch1.duty][step] * ch1.volume;

            if (panning & 0x10) left += sample;
            if (panning & 0x01) right += sample;
        }

        // === Channel 2 - Square Wave ===
        if (ch2.active && ch2.dac_on && ch2.volume > 0 && ch2.freq_raw > 0) {
            float freq = get_frequency(ch2.freq_raw);
            float phase_inc = freq / SAMPLE_RATE;

            ch2.phase += phase_inc;
            if (ch2.phase >= 1.0f) ch2.phase -= 1.0f;

            int step = (int)(ch2.phase * 8) & 7;
            int sample = duty_table[ch2.duty][step] * ch2.volume;

            if (panning & 0x20) left += sample;
            if (panning & 0x02) right += sample;
        }
        
        // === Channel 3 - Wave ===
        if (ch3.active && ch3.dac_on && ch3.freq_raw > 0) {
            float freq = get_wave_frequency(ch3.freq_raw);
            float phase_inc = freq / SAMPLE_RATE;

            ch3.phase += phase_inc;
            if (ch3.phase >= 1.0f) ch3.phase -= 1.0f;

            int pos = (int)(ch3.phase * 32) & 31;
            int byte_idx = pos / 2;
            int sample_raw;
            if (pos & 1) {
                sample_raw = wave_ram[byte_idx] & 0x0F;
            } else {
                sample_raw = wave_ram[byte_idx] >> 4;
            }

            // Volume shift: 0=mute, 1=100%, 2=50%, 3=25% - FIXED!
            int sample = 0;
            if (ch3.volume_shift > 0) {
                int shift = ch3.volume_shift - 1;  // 1→0, 2→1, 3→2
                sample = (sample_raw >> shift) - 8;  // Center around 0
            }
            
            if (panning & 0x40) left += sample;
            if (panning & 0x04) right += sample;
        }
        
        // === Channel 4 - Noise ===
        if (ch4.active && ch4.volume > 0) {
            // Noise frequency calculation
            int divisor = (ch4.divisor == 0) ? 8 : (ch4.divisor * 16);
            float noise_freq = 524288.0f / divisor / (1 << (ch4.shift + 1));
            float timer_inc = noise_freq / SAMPLE_RATE;
            
            ch4.timer += timer_inc;
            while (ch4.timer >= 1.0f) {
                ch4.timer -= 1.0f;
                
                // LFSR step
                int bit = (ch4.lfsr ^ (ch4.lfsr >> 1)) & 1;
                ch4.lfsr = (ch4.lfsr >> 1) | (bit << 14);
                if (ch4.width_mode) {
                    ch4.lfsr &= ~(1 << 6);
                    ch4.lfsr |= (bit << 6);
                }
            }
            
            int sample = (ch4.lfsr & 1) ? 0 : ch4.volume;
            
            if (panning & 0x80) left += sample;
            if (panning & 0x08) right += sample;
        }
        
        // Apply master volume (0-7) - FIXED scaling to prevent clipping!
        // Each channel outputs -15 to +15 max (volume 0-15)
        // With 4 channels: max = 60, min = -60
        // Scale by 32 for good amplitude: ±60 * 32 * 8 = ±15360 (fits in 16-bit)
        left = left * (master_vol_left + 1) * 32;
        right = right * (master_vol_right + 1) * 32;

        // Clamp to 16-bit range (safety)
        if (left > 32767) left = 32767;
        if (left < -32768) left = -32768;
        if (right > 32767) right = 32767;
        if (right < -32768) right = -32768;
        
        buffer[i * 2] = (int16_t)left;
        buffer[i * 2 + 1] = (int16_t)right;
    }
    
    if (apu_mutex) xSemaphoreGive(apu_mutex);
}

// ============================================
// Peanut-GB Setup
// ============================================

#define ENABLE_SOUND 1
#define ENABLE_LCD 1

#include "peanut_gb.h"

// Undefine peanut_gb's LCD definitions (they're for GameBoy, not our display)
#undef LCD_WIDTH
#undef LCD_HEIGHT

#define LCD_WIDTH DISPLAY_WIDTH
#define LCD_HEIGHT DISPLAY_HEIGHT

#define SD_MOUNT_POINT "/sd"
#define DEFAULT_ROM "/sd/tetris.gb"

static struct gb_s gb;
static uint8_t *rom_data = NULL;
static size_t rom_size = 0;
static uint16_t *line_buffer = NULL;
static uint16_t *frame_buffer = NULL;  // Full screen buffer in PSRAM
static int current_line = 0;

// Double-buffering for parallel display/emulation
static uint16_t *render_buffer = NULL;  // Buffer being rendered to
static uint16_t *display_buffer = NULL; // Buffer being displayed
static SemaphoreHandle_t frame_ready_sem = NULL;
static SemaphoreHandle_t frame_done_sem = NULL;

static const uint16_t gb_palette[4] = {
    0x9FE7, 0x6BE4, 0x3760, 0x0C20
};

static uint8_t gb_rom_read(struct gb_s *gb, const uint_fast32_t addr)
{
    return (addr < rom_size) ? rom_data[addr] : 0xFF;
}

static uint8_t gb_cart_ram_read(struct gb_s *gb, const uint_fast32_t addr)
{
    return 0xFF;
}

static void gb_cart_ram_write(struct gb_s *gb, const uint_fast32_t addr, const uint8_t val)
{
}

static void gb_error(struct gb_s *gb, const enum gb_error_e err, const uint16_t addr)
{
    ESP_LOGE(TAG, "GB Error %d at 0x%04X", err, addr);
}

static void gb_lcd_draw_line(struct gb_s *gb, const uint8_t pixels[160], const uint_fast8_t line)
{
    // Draw into RENDER buffer (double-buffering for parallel display)
#if GB_PIXEL_PERFECT_SCALING
    // Dynamic scaling based on GB_SCALE_FACTOR
    // Vertical: Scale GameBoy line (0-143) to output Y coordinate
    int y_base = (line * GB_RENDER_HEIGHT) / 144;
    if (y_base >= GB_RENDER_HEIGHT) return;

    // Horizontal scaling: 160 GameBoy pixels -> GB_RENDER_WIDTH output pixels
    // Dynamic pixel-width algorithm ensures every pixel is filled without gaps
    int x_dst = 0;
    for (int x = 0; x < 160; x++) {
        uint16_t c = gb_palette[pixels[x] & 0x03];
        uint16_t swapped = (c >> 8) | (c << 8);  // RGB->BGR

        // Calculate how wide this pixel should be at current scaling
        int next_x_dst = ((x + 1) * GB_RENDER_WIDTH) / 160;
        int pixel_width = next_x_dst - x_dst;

        // Fill pixel_width positions with this color (no gaps!)
        for (int w = 0; w < pixel_width && x_dst + w < GB_RENDER_WIDTH; w++) {
            int dst = (y_base + GB_OFFSET_Y) * GB_SCREEN_WIDTH + (x_dst + w + GB_OFFSET_X);
            render_buffer[dst] = swapped;
        }

        x_dst = next_x_dst;
    }

    // Vertical scaling: duplicate lines as needed based on scaling factor
    int ny = ((line + 1) * GB_RENDER_HEIGHT) / 144;
    if (ny > y_base + 1 && ny < GB_RENDER_HEIGHT) {
        memcpy(&render_buffer[(y_base + 1 + GB_OFFSET_Y) * GB_SCREEN_WIDTH + GB_OFFSET_X],
               &render_buffer[(y_base + GB_OFFSET_Y) * GB_SCREEN_WIDTH + GB_OFFSET_X],
               GB_RENDER_WIDTH * 2);
    }
#else
    // Full screen stretch: 160x144 -> 320x240
    int y = (line * 5) / 3;
    if (y >= GB_SCREEN_HEIGHT) return;

    // Horizontal doubling: 160 -> 320, with BYTE SWAP for display
    for (int x = 0; x < 160; x++) {
        uint16_t c = gb_palette[pixels[x] & 0x03];
        uint16_t swapped = (c >> 8) | (c << 8);  // RGB->BGR
        int dst = y * GB_SCREEN_WIDTH + x * 2;
        render_buffer[dst] = swapped;
        render_buffer[dst + 1] = swapped;
    }

    // Vertical scaling: duplicate line if needed
    int ny = ((line + 1) * 5) / 3;
    if (ny > y + 1 && ny < GB_SCREEN_HEIGHT) {
        memcpy(&render_buffer[(y + 1) * GB_SCREEN_WIDTH],
               &render_buffer[y * GB_SCREEN_WIDTH],
               GB_SCREEN_WIDTH * 2);  // 320 pixels * 2 bytes
    }
#endif
}

static esp_err_t init_sdcard(void)
{
    ESP_LOGI(TAG, "Init SD...");
    esp_vfs_fat_sdmmc_mount_config_t cfg = {
        .format_if_mount_failed = false,
        .max_files = 5,
        .allocation_unit_size = 16 * 1024
    };
    sdmmc_card_t *card;
    sdmmc_host_t host = SDSPI_HOST_DEFAULT();
    host.max_freq_khz = 400;
    host.slot = SD_SPI_HOST;
    sdspi_device_config_t slot = SDSPI_DEVICE_CONFIG_DEFAULT();
    slot.gpio_cs = SD_PIN_CS;
    slot.host_id = host.slot;
    esp_err_t ret = esp_vfs_fat_sdspi_mount(SD_MOUNT_POINT, &host, &slot, &cfg, &card);
    if (ret == ESP_OK) ESP_LOGI(TAG, "✓ SD OK!");
    return ret;
}

static bool load_rom(const char *path)
{
    FILE *f = fopen(path, "rb");
    if (!f) return false;
    fseek(f, 0, SEEK_END);
    rom_size = ftell(f);
    fseek(f, 0, SEEK_SET);
    rom_data = malloc(rom_size);
    if (!rom_data) { fclose(f); return false; }
    fread(rom_data, 1, rom_size, f);
    fclose(f);
    ESP_LOGI(TAG, "✓ ROM: %d bytes", rom_size);
    return true;
}

static esp_err_t init_audio(void)
{
    ESP_LOGI(TAG, "Init Audio...");
    apu_mutex = xSemaphoreCreateMutex();
    
    i2s_config_t cfg = {
        .mode = I2S_MODE_MASTER | I2S_MODE_TX,
        .sample_rate = SAMPLE_RATE,
        .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
        .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
        .communication_format = I2S_COMM_FORMAT_STAND_I2S,
        .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
        .dma_buf_count = 8,
        .dma_buf_len = SAMPLES_PER_BUFFER,
        .use_apll = false,
        .tx_desc_auto_clear = true,
    };
    
    i2s_pin_config_t pins = {
        .bck_io_num = I2S_PIN_BCLK,
        .ws_io_num = I2S_PIN_LRC,
        .data_out_num = I2S_PIN_DIN,
        .data_in_num = I2S_PIN_NO_CHANGE
    };
    
    esp_err_t ret = i2s_driver_install(I2S_NUM, &cfg, 0, NULL);
    if (ret != ESP_OK) return ret;
    
    ret = i2s_set_pin(I2S_NUM, &pins);
    if (ret != ESP_OK) return ret;
    
    i2s_zero_dma_buffer(I2S_NUM);
    
    audio_buffer = heap_caps_malloc(SAMPLES_PER_BUFFER * 4, MALLOC_CAP_DMA);
    if (!audio_buffer) return ESP_ERR_NO_MEM;
    
    audio_enabled = true;
    ESP_LOGI(TAG, "✓ Audio OK! BCLK=%d LRC=%d DIN=%d", 
             I2S_PIN_BCLK, I2S_PIN_LRC, I2S_PIN_DIN);
    return ESP_OK;
}

static void audio_task(void *arg)
{
    ESP_LOGI(TAG, "🎵 Audio task started");
    int16_t *buffer = heap_caps_malloc(SAMPLES_PER_BUFFER * 4, MALLOC_CAP_DMA);

    while (audio_enabled) {
        // Generate smaller buffers (512 samples) for lower latency
        generate_samples(buffer, SAMPLES_PER_BUFFER);
        size_t written;
        i2s_write(I2S_NUM, buffer, SAMPLES_PER_BUFFER * 4, &written, portMAX_DELAY);
    }
    free(buffer);
    vTaskDelete(NULL);
}

static void display_task(void *arg)
{
#if GB_PIXEL_PERFECT_SCALING
    // Clear screen to black once (for borders that don't change)
    st7789_fill_screen(0x0000);

    // Allocate temp buffer for compacted GameBoy region (240x216 = 103KB)
    uint16_t *compact_buffer = heap_caps_malloc(GB_RENDER_WIDTH * GB_RENDER_HEIGHT * 2, MALLOC_CAP_DMA);
#endif

    while (1) {
        // Wait for frame to be ready
        xSemaphoreTake(frame_ready_sem, portMAX_DELAY);

#if GB_PIXEL_PERFECT_SCALING
        // Copy GameBoy region to compact buffer (remove gaps from black borders)
        for (int y = 0; y < GB_RENDER_HEIGHT; y++) {
            memcpy(&compact_buffer[y * GB_RENDER_WIDTH],
                   &display_buffer[(y + GB_OFFSET_Y) * GB_SCREEN_WIDTH + GB_OFFSET_X],
                   GB_RENDER_WIDTH * 2);
        }
        // Transfer only GameBoy content (240x216 = 33% less data than 320x240!)
        st7789_draw_buffer_preswapped(compact_buffer,
                                       GB_OFFSET_X, GB_OFFSET_Y,
                                       GB_RENDER_WIDTH, GB_RENDER_HEIGHT);
#else
        // Full screen mode - draw entire buffer
        st7789_draw_buffer_preswapped(display_buffer, 0, 0, GB_SCREEN_WIDTH, GB_SCREEN_HEIGHT);
#endif

        // Signal frame display is done
        xSemaphoreGive(frame_done_sem);
    }
}

static void emulation_task(void *arg)
{
    int frame = 0;
    TickType_t last = xTaskGetTickCount();
    int16_t *frame_audio = heap_caps_malloc(SAMPLES_PER_FRAME * 4, MALLOC_CAP_DMA);

    while (1) {
        TickType_t frame_start = xTaskGetTickCount();

        // Run emulation - renders into render_buffer
        gb_run_frame(&gb);

        // Swap buffers
        uint16_t *temp = render_buffer;
        render_buffer = display_buffer;
        display_buffer = temp;

        // Signal display task that new frame is ready
        xSemaphoreGive(frame_ready_sem);

        // Wait for display to finish with previous frame
        xSemaphoreTake(frame_done_sem, portMAX_DELAY);

        frame++;
        TickType_t frame_end = xTaskGetTickCount();
        int frame_time_ms = (frame_end - frame_start) * portTICK_PERIOD_MS;

        if (frame % 60 == 0) {  // Every second
            ESP_LOGI(TAG, "Frame %d | time=%dms (%.1f FPS) | writes=%d | sound=%s | ch1=%d ch2=%d ch3=%d ch4=%d",
                     frame, frame_time_ms, 1000.0f / frame_time_ms,
                     audio_write_count, master_enable ? "ON" : "OFF",
                     ch1.active, ch2.active, ch3.active, ch4.active);
        }

        // GameBoy runs at 59.7275 FPS = 16.7424ms per frame
        vTaskDelayUntil(&last, pdMS_TO_TICKS(17));  // 17ms ≈ 58.8 FPS
    }
}

void app_main(void)
{
    ESP_LOGI(TAG, "");
    ESP_LOGI(TAG, "╔═══════════════════════════════════════╗");
    ESP_LOGI(TAG, "║  ESP32-S3 GameBoy - FIXED AUDIO!     ║");
    ESP_LOGI(TAG, "╚═══════════════════════════════════════╝");
    
    nvs_flash_init();
    st7789_init();
    st7789_set_backlight(80);
    st7789_fill_screen(0x001F);
    vTaskDelay(pdMS_TO_TICKS(500));

    // Check PSRAM availability
    size_t psram_total = heap_caps_get_total_size(MALLOC_CAP_SPIRAM);
    size_t psram_free = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
    ESP_LOGI(TAG, "PSRAM: %d KB total, %d KB free", psram_total / 1024, psram_free / 1024);

    // Allocate TWO frame buffers for double-buffering
    // Size depends on scaling mode
    size_t buffer_size = GB_SCREEN_WIDTH * GB_SCREEN_HEIGHT * 2;  // RGB565 = 2 bytes/pixel
    ESP_LOGI(TAG, "Buffer size: %d KB (%dx%d)", buffer_size / 1024, GB_SCREEN_WIDTH, GB_SCREEN_HEIGHT);

    // Try PSRAM first, fallback to regular RAM if needed
    size_t min_psram_needed = buffer_size * 2 + 50000;  // 2 buffers + margin
    if (psram_free > min_psram_needed) {
        render_buffer = heap_caps_malloc(buffer_size, MALLOC_CAP_SPIRAM);
        display_buffer = heap_caps_malloc(buffer_size, MALLOC_CAP_SPIRAM);
        ESP_LOGI(TAG, "Double buffers allocated in PSRAM");
    }

    if (!render_buffer || !display_buffer) {
        ESP_LOGW(TAG, "PSRAM alloc failed, trying regular RAM...");
        if (render_buffer) free(render_buffer);
        if (display_buffer) free(display_buffer);
        render_buffer = heap_caps_malloc(buffer_size, MALLOC_CAP_8BIT);
        display_buffer = heap_caps_malloc(buffer_size, MALLOC_CAP_8BIT);
        ESP_LOGI(TAG, "Double buffers allocated in internal RAM");
    }

    if (!render_buffer || !display_buffer) {
        ESP_LOGE(TAG, "No memory for double framebuffers!");
        while(1) vTaskDelay(1000);
    }

    // Clear buffers to black (for letterboxing in pixel-perfect mode)
    memset(render_buffer, 0, buffer_size);
    memset(display_buffer, 0, buffer_size);

    // Create semaphores for buffer synchronization
    frame_ready_sem = xSemaphoreCreateBinary();
    frame_done_sem = xSemaphoreCreateBinary();
    xSemaphoreGive(frame_done_sem);  // Initially, display is "done"
    
    if (init_sdcard() != ESP_OK) {
        st7789_fill_screen(0xF800);
        while(1) vTaskDelay(1000);
    }
    
    st7789_fill_screen(0x07E0);
    vTaskDelay(pdMS_TO_TICKS(300));
    
    if (!load_rom(DEFAULT_ROM)) {
        st7789_fill_screen(0xF800);
        ESP_LOGE(TAG, "ROM load failed!");
        while(1) vTaskDelay(1000);
    }
    
    if (gb_init(&gb, &gb_rom_read, &gb_cart_ram_read, &gb_cart_ram_write, &gb_error, NULL) != GB_INIT_NO_ERROR) {
        st7789_fill_screen(0xF800);
        while(1) vTaskDelay(1000);
    }
    
    gb_init_lcd(&gb, &gb_lcd_draw_line);

    if (init_audio() == ESP_OK) {
        audio_enabled = true;
        // Run audio on Core 1, emulator on Core 0 for better performance
        xTaskCreatePinnedToCore(audio_task, "audio", 4096, NULL, 5, NULL, 1);
    }
    
    ESP_LOGI(TAG, "");
    ESP_LOGI(TAG, "═══════════════════════════════════════");
    ESP_LOGI(TAG, "✓ TETRIS with FIXED AUDIO! 🎮🔊");
    ESP_LOGI(TAG, "═══════════════════════════════════════");

    st7789_fill_screen(0x0000);

    // Start display task on Core 0 (parallel to emulation!)
    xTaskCreatePinnedToCore(display_task, "display", 4096, NULL, 5, NULL, 0);

    // Start emulation task on Core 1 (with audio for cache locality)
    xTaskCreatePinnedToCore(emulation_task, "emulation", 8192, NULL, 5, NULL, 1);

    // Keep app_main running (don't exit)
    while (1) {
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}
