/*
  FastLED — RP2350 ISR Implementation
  ------------------------------------
  Platform-specific ISR implementation for RP2350 (Raspberry Pi Pico 2).

  RP2350 Hardware:
  - Dual Cortex-M33 cores (multi-core capable, ARMv8-M with TrustZone)
  - Two 64-bit microsecond counters (1 MHz, generated by tick block)
  - 4 hardware alarms per timer (ALARM0-ALARM3)
  - 4 separate timer interrupts (TIMER_IRQ_0 through TIMER_IRQ_3)
  - GPIO interrupts on all 30 pins (per-core interrupt control)
  - NVIC priority system (lower number = higher priority, 0-255 range on M33)

  Differences from RP2040:
  - Cortex-M33 vs M0+: 8-bit priority (0-255) vs 2-bit priority (0-3)
  - Dual timers vs single timer (uses PICO_DEFAULT_TIMER by default)
  - Alarms fire if set in the past (RP2040 does not fire for past times)
  - Timer reference from tick block (RP2040 uses watchdog-derived reference)

  Timer Capabilities:
  - Maximum alarm period: 2^32 microseconds (~71.58 minutes)
  - Minimum period: ~1 microsecond (limited by ISR overhead)
  - Frequency range: ~0.00023 Hz to 1 MHz
  - All alarms share the same 1 MHz counter

  Multi-Core Considerations:
  - Each core has independent GPIO interrupt control registers
  - Hardware alarms can fire on either core
  - Critical sections use spinlocks + interrupt disable for atomicity
  - save_and_disable_interrupts() only affects the calling core

  License: MIT (FastLED)
*/

#pragma once

// IWYU pragma: private
#include "platforms/arm/rp/is_rp.h"

#if defined(FL_IS_RP2350)

#include "fl/stl/isr/handler.h"
#include "fl/stl/compiler_control.h"
#include "fl/stl/unique_ptr.h"
#include "fl/log/log.h"
#include "fl/stl/charconv.h"

// Pico SDK headers
FL_EXTERN_C_BEGIN
// IWYU pragma: begin_keep
#include "hardware/irq.h"
// IWYU pragma: end_keep
// IWYU pragma: begin_keep
#include "hardware/timer.h"
// IWYU pragma: end_keep
// IWYU pragma: begin_keep
#include "hardware/gpio.h"
// IWYU pragma: end_keep
// IWYU pragma: begin_keep
#include "hardware/sync.h"
// IWYU pragma: end_keep
// IWYU pragma: begin_keep
#include "pico/time.h"
// IWYU pragma: end_keep
// IWYU pragma: begin_keep
#include "pico/critical_section.h"
#include "fl/stl/noexcept.h"
// IWYU pragma: end_keep
FL_EXTERN_C_END

namespace fl {
namespace isr {
namespace platforms {

// =============================================================================
// Platform-Specific Handle Storage
// =============================================================================

struct rp2350_isr_handle_data {
    // Timer-related fields
    i8 alarm_num;              // Hardware alarm number (0-3, or -1 if not timer)
    alarm_id_t alarm_id;           // Pico SDK alarm ID (for cancellation)
    u32 frequency_hz;         // Timer frequency in Hz

    // GPIO-related fields
    u8 gpio_pin;              // GPIO pin number (0xFF if not GPIO)
    u32 gpio_events;          // GPIO event mask (rise/fall/level)

    // Common fields
    bool is_timer;                 // true = timer ISR, false = GPIO ISR
    bool is_enabled;               // Current enable state
    isr_handler_t user_handler;    // User handler function
    void* user_data;               // User context
    u8 core_num;              // Core that registered this ISR (0 or 1)

    rp2350_isr_handle_data()
        : alarm_num(-1)
        , alarm_id(0)
        , frequency_hz(0)
        , gpio_pin(0xFF)
        , gpio_events(0)
        , is_timer(false)
        , is_enabled(true)
        , user_handler(nullptr)
        , user_data(nullptr)
        , core_num(0)
    {}
};

// Platform ID for RP2350
constexpr u8 RP2350_PLATFORM_ID = 14;

// Maximum hardware alarms
constexpr u8 FL_NUM_ALARMS = 4;

// Track allocated alarms
static bool alarm_allocated[FL_NUM_ALARMS] = {};
static critical_section_t alarm_lock;
static bool alarm_lock_initialized = false;

// Storage for alarm handles (to allow ISR to find user handler)
static rp2350_isr_handle_data* alarm_handles[FL_NUM_ALARMS] = {};

// =============================================================================
// Helper Functions
// =============================================================================

// Initialize alarm lock (called once)
static void init_alarm_lock() FL_NOEXCEPT {
    if (!alarm_lock_initialized) {
        critical_section_init(&alarm_lock);
        alarm_lock_initialized = true;
    }
}

// Allocate a hardware alarm (thread-safe, multi-core safe)
static i8 allocate_alarm() FL_NOEXCEPT {
    init_alarm_lock();

    critical_section_enter_blocking(&alarm_lock);

    i8 alarm_num = -1;
    for (u8 i = 0; i < FL_NUM_ALARMS; i++) {
        if (!alarm_allocated[i]) {
            alarm_allocated[i] = true;
            alarm_num = i;
            break;
        }
    }

    critical_section_exit(&alarm_lock);
    return alarm_num;
}

// Free a hardware alarm (thread-safe, multi-core safe)
static void free_alarm(i8 alarm_num) FL_NOEXCEPT {
    if (alarm_num < 0 || alarm_num >= FL_NUM_ALARMS) {
        return;
    }

    init_alarm_lock();

    critical_section_enter_blocking(&alarm_lock);
    alarm_allocated[alarm_num] = false;
    alarm_handles[alarm_num] = nullptr;
    critical_section_exit(&alarm_lock);
}

// Map ISR priority (1-7) to NVIC priority (0-255, lower = higher)
// RP2350 Cortex-M33 supports 8-bit priority (0-255)
static u8 map_priority_to_nvic(u8 isr_priority) FL_NOEXCEPT {
    // Clamp to valid range
    if (isr_priority < 1) isr_priority = 1;
    if (isr_priority > 7) isr_priority = 7;

    // Map ISR priorities 1-7 to NVIC priorities 224-0
    // ISR 1   -> NVIC 224 (lowest)
    // ISR 2   -> NVIC 192
    // ISR 3   -> NVIC 160
    // ISR 4   -> NVIC 128
    // ISR 5   -> NVIC 96
    // ISR 6   -> NVIC 64
    // ISR 7   -> NVIC 32 (highest)
    return (8 - isr_priority) * 32;
}

// =============================================================================
// Timer Alarm Callbacks (called from ISR context)
// =============================================================================

// Repeating alarm callback wrapper
static i64 alarm_callback_wrapper(alarm_id_t id, void* user_data) FL_NOEXCEPT {
    rp2350_isr_handle_data* handle = static_cast<rp2350_isr_handle_data*>(user_data);

    if (handle && handle->user_handler && handle->is_enabled) {
        handle->user_handler(handle->user_data);
    }

    // Return interval in microseconds for next alarm (repeating timer)
    // Return 0 to stop repeating
    if (handle && handle->frequency_hz > 0) {
        return 1000000 / handle->frequency_hz;  // Convert Hz to microseconds
    }
    return 0;  // Stop repeating (should not happen)
}

// =============================================================================
// GPIO Interrupt Callbacks (called from ISR context)
// =============================================================================

// GPIO callback storage (one per core, shared among all GPIO ISRs on that core)
static rp2350_isr_handle_data* gpio_handles[30] = {};  // 30 GPIO pins max

// GPIO interrupt handler (shared callback for all GPIOs on this core)
static void gpio_callback_wrapper(uint gpio, u32 events) FL_NOEXCEPT {
    if (gpio < 30 && gpio_handles[gpio]) {
        rp2350_isr_handle_data* handle = gpio_handles[gpio];

        // Check if this is the expected event
        if (handle->is_enabled && (events & handle->gpio_events)) {
            if (handle->user_handler) {
                handle->user_handler(handle->user_data);
            }
        }
    }
}

// =============================================================================
// RP2350 ISR Implementation (fl::isr::platform namespace)
// =============================================================================

int attach_timer_handler(const isr_config_t& config, isr_handle_t* out_handle) FL_NOEXCEPT {
    if (!config.handler) {
        FL_WARN("attachTimerHandler: handler is null");
        return -1;  // Invalid parameter
    }

    if (config.frequency_hz == 0) {
        FL_WARN("attachTimerHandler: frequency_hz is 0");
        return -2;  // Invalid frequency
    }

    // Check frequency bounds (practical limits for RP2350)
    if (config.frequency_hz > 1000000) {
        FL_WARN("attachTimerHandler: frequency " << config.frequency_hz << " Hz exceeds 1 MHz limit");
        return -2;  // Invalid frequency
    }

    // Allocate a hardware alarm
    i8 alarm_num = allocate_alarm();
    if (alarm_num < 0) {
        FL_WARN("attachTimerHandler: no free hardware alarms (max 4)");
        return -3;  // Out of resources
    }

    // Allocate handle data
    auto handle_owner = fl::make_unique<rp2350_isr_handle_data>();
    auto* handle_data = handle_owner.get();
    if (!handle_data) {
        free_alarm(alarm_num);
        FL_WARN("attachTimerHandler: failed to allocate handle data");
        return -5;  // Out of memory
    }

    handle_data->is_timer = true;
    handle_data->alarm_num = alarm_num;
    handle_data->frequency_hz = config.frequency_hz;
    handle_data->user_handler = config.handler;
    handle_data->user_data = config.user_data;
    handle_data->core_num = get_core_num();

    // Store handle for ISR access
    alarm_handles[alarm_num] = handle_data;

    // Calculate interval in microseconds
    i64 interval_us = 1000000 / config.frequency_hz;

    // Add repeating timer using Pico SDK
    handle_data->alarm_id = add_alarm_in_us(interval_us, alarm_callback_wrapper,
                                             handle_data, true);

    if (handle_data->alarm_id == 0 || handle_data->alarm_id == -1) {
        // Alarm creation failed
        free_alarm(alarm_num);
        alarm_handles[alarm_num] = nullptr;
        FL_WARN("attachTimerHandler: add_alarm_in_us failed");
        return -4;  // Internal error
    }

    // Set IRQ priority if specified
    // RP2350 has dual timers (TIMER0/TIMER1) with separate IRQs
    // Use TIMER_ALARM_IRQ_NUM macro for portability with PICO_DEFAULT_TIMER
    if (config.priority != ISR_PRIORITY_DEFAULT) {
        u8 nvic_priority = map_priority_to_nvic(config.priority);
        irq_set_priority(TIMER_ALARM_IRQ_NUM(timer_hw, alarm_num), nvic_priority);
    }

    FL_DBG("Timer started at " << config.frequency_hz << " Hz on alarm "
           << static_cast<int>(alarm_num));

    // Release ownership - pointer is now managed by the C API (alarm_handles + out_handle)
    handle_owner.release();

    // Populate output handle
    if (out_handle) {
        out_handle->platform_handle = handle_data;
        out_handle->handler = config.handler;
        out_handle->user_data = config.user_data;
        out_handle->platform_id = RP2350_PLATFORM_ID;
    }

    return 0;  // Success
}

int attach_external_handler(u8 pin, const isr_config_t& config, isr_handle_t* out_handle) FL_NOEXCEPT {
    if (!config.handler) {
        FL_WARN("attachExternalHandler: handler is null");
        return -1;  // Invalid parameter
    }

    if (pin >= 30) {
        FL_WARN("attachExternalHandler: invalid pin " << static_cast<int>(pin)
                << " (RP2350 has 30 GPIO pins)");
        return -1;  // Invalid parameter
    }

    // Check if pin already has a handler
    if (gpio_handles[pin] != nullptr) {
        FL_WARN("attachExternalHandler: pin " << static_cast<int>(pin)
                << " already has an attached handler");
        return -3;  // Resource already in use
    }

    // Allocate handle data
    auto handle_owner = fl::make_unique<rp2350_isr_handle_data>();
    auto* handle_data = handle_owner.get();
    if (!handle_data) {
        FL_WARN("attachExternalHandler: failed to allocate handle data");
        return -5;  // Out of memory
    }

    handle_data->is_timer = false;
    handle_data->gpio_pin = pin;
    handle_data->user_handler = config.handler;
    handle_data->user_data = config.user_data;
    handle_data->core_num = get_core_num();

    // Determine GPIO event mask from flags
    u32 events = 0;
    if (config.flags & ISR_FLAG_EDGE_RISING) {
        events |= GPIO_IRQ_EDGE_RISE;
    }
    if (config.flags & ISR_FLAG_EDGE_FALLING) {
        events |= GPIO_IRQ_EDGE_FALL;
    }
    if (config.flags & ISR_FLAG_LEVEL_HIGH) {
        events |= GPIO_IRQ_LEVEL_HIGH;
    }
    if (config.flags & ISR_FLAG_LEVEL_LOW) {
        events |= GPIO_IRQ_LEVEL_LOW;
    }

    // Default to both edges if no flags specified
    if (events == 0) {
        events = GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL;
    }

    handle_data->gpio_events = events;

    // Store handle for ISR access
    gpio_handles[pin] = handle_data;

    // Set up GPIO interrupt using Pico SDK
    // Note: gpio_set_irq_enabled_with_callback has a quirk - the callback is
    // shared across all GPIOs on this core, and the gpio parameter is ignored.
    // We work around this by dispatching in gpio_callback_wrapper.
    gpio_set_irq_enabled_with_callback(pin, events, true, gpio_callback_wrapper);

    // Set IRQ priority if specified
    if (config.priority != ISR_PRIORITY_DEFAULT) {
        u8 nvic_priority = map_priority_to_nvic(config.priority);
        irq_set_priority(IO_IRQ_BANK0, nvic_priority);
    }

    FL_DBG("GPIO interrupt attached on pin " << static_cast<int>(pin)
           << " with events 0x" << fl::to_hex(events));

    // Release ownership - pointer is now managed by the C API (gpio_handles + out_handle)
    handle_owner.release();

    // Populate output handle
    if (out_handle) {
        out_handle->platform_handle = handle_data;
        out_handle->handler = config.handler;
        out_handle->user_data = config.user_data;
        out_handle->platform_id = RP2350_PLATFORM_ID;
    }

    return 0;  // Success
}

int detach_handler(isr_handle_t& handle) FL_NOEXCEPT {
    if (!handle.is_valid() || handle.platform_id != RP2350_PLATFORM_ID) {
        FL_WARN("detachHandler: invalid handle");
        return -1;  // Invalid handle
    }

    rp2350_isr_handle_data* handle_data = static_cast<rp2350_isr_handle_data*>(handle.platform_handle);
    if (!handle_data) {
        FL_WARN("detachHandler: null handle data");
        return -1;  // Invalid handle
    }

    if (handle_data->is_timer) {
        // Cancel the alarm
        if (handle_data->alarm_id > 0) {
            cancel_alarm(handle_data->alarm_id);
        }

        // Free the alarm resource
        free_alarm(handle_data->alarm_num);
    } else {
        // Disable GPIO interrupt
        if (handle_data->gpio_pin < 30) {
            gpio_set_irq_enabled(handle_data->gpio_pin, handle_data->gpio_events, false);
            gpio_handles[handle_data->gpio_pin] = nullptr;
        }
    }

    delete handle_data;  // ok bare allocation (C API teardown)
    handle.platform_handle = nullptr;
    handle.platform_id = 0;

    FL_DBG("Handler detached");
    return 0;  // Success
}

int enable_handler(const isr_handle_t& handle) FL_NOEXCEPT {
    if (!handle.is_valid() || handle.platform_id != RP2350_PLATFORM_ID) {
        FL_WARN("enableHandler: invalid handle");
        return -1;  // Invalid handle
    }

    rp2350_isr_handle_data* handle_data = static_cast<rp2350_isr_handle_data*>(handle.platform_handle);
    if (!handle_data) {
        FL_WARN("enableHandler: null handle data");
        return -1;  // Invalid handle
    }

    if (handle_data->is_enabled) {
        return 0;  // Already enabled
    }

    if (handle_data->is_timer) {
        // For timer, we need to recreate the alarm
        i64 interval_us = 1000000 / handle_data->frequency_hz;
        handle_data->alarm_id = add_alarm_in_us(interval_us, alarm_callback_wrapper,
                                                 handle_data, true);
        if (handle_data->alarm_id == 0 || handle_data->alarm_id == -1) {
            FL_WARN("enableHandler: failed to restart alarm");
            return -2;  // Failed to restart
        }
    } else {
        // Re-enable GPIO interrupt
        gpio_set_irq_enabled(handle_data->gpio_pin, handle_data->gpio_events, true);
    }

    handle_data->is_enabled = true;
    return 0;  // Success
}

int disable_handler(const isr_handle_t& handle) FL_NOEXCEPT {
    if (!handle.is_valid() || handle.platform_id != RP2350_PLATFORM_ID) {
        FL_WARN("disableHandler: invalid handle");
        return -1;  // Invalid handle
    }

    rp2350_isr_handle_data* handle_data = static_cast<rp2350_isr_handle_data*>(handle.platform_handle);
    if (!handle_data) {
        FL_WARN("disableHandler: null handle data");
        return -1;  // Invalid handle
    }

    if (!handle_data->is_enabled) {
        return 0;  // Already disabled
    }

    if (handle_data->is_timer) {
        // Cancel the alarm but keep the handle data
        if (handle_data->alarm_id > 0) {
            cancel_alarm(handle_data->alarm_id);
            handle_data->alarm_id = 0;
        }
    } else {
        // Disable GPIO interrupt
        gpio_set_irq_enabled(handle_data->gpio_pin, handle_data->gpio_events, false);
    }

    handle_data->is_enabled = false;
    return 0;  // Success
}

bool is_handler_enabled(const isr_handle_t& handle) FL_NOEXCEPT {
    if (!handle.is_valid() || handle.platform_id != RP2350_PLATFORM_ID) {
        return false;
    }

    rp2350_isr_handle_data* handle_data = static_cast<rp2350_isr_handle_data*>(handle.platform_handle);
    if (!handle_data) {
        return false;
    }

    return handle_data->is_enabled;
}

const char* get_error_string(int error_code) FL_NOEXCEPT {
    switch (error_code) {
        case 0: return "Success";
        case -1: return "Invalid parameter";
        case -2: return "Invalid frequency";
        case -3: return "Out of resources";
        case -4: return "Internal error";
        case -5: return "Out of memory";
        default: return "Unknown error";
    }
}

const char* get_platform_name() FL_NOEXCEPT {
    return "RP2350";
}

u32 get_max_timer_frequency() FL_NOEXCEPT {
    return 1000000;  // 1 MHz (limited by microsecond counter resolution)
}

u32 get_min_timer_frequency() FL_NOEXCEPT {
    return 1;  // 1 Hz (practical minimum given 32-bit alarm period)
}

u8 get_max_priority() FL_NOEXCEPT {
    // RP2350 Cortex-M33 supports priority levels 0-255 in hardware
    // We expose 1-7 in the ISR API and map to 0-255 in NVIC
    return 7;
}

bool requires_assembly_handler(u8 priority) FL_NOEXCEPT {
    // ARM Cortex-M33: All priority levels support C handlers
    (void)priority;
    return false;
}

} // namespace platforms
} // namespace isr
} // namespace fl

#endif // FL_IS_RP2350
