use {
    core::mem::MaybeUninit,
    log::trace,
    x2apic::lapic::{LocalApic, LocalApicBuilder},
    x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode},
};

/// Safety: Using LAPIC or IDT before init() is UB
/// Using
static mut LAPIC: LocalApic = unsafe { MaybeUninit::zeroed().assume_init() };
static mut IDT: InterruptDescriptorTable = unsafe { MaybeUninit::zeroed().assume_init() };

#[repr(u8)]
enum Interrupt {
    Timer = 32,
    ApicErr = u8::MAX - 1,
    Spurious = u8::MAX,
}

pub unsafe fn init() {
    trace!("Initializing IDT and LAPIC");

    // Initialize and load the IDT
    IDT = InterruptDescriptorTable::new();
    IDT.double_fault
        .set_handler_fn(double_fault)
        .set_stack_index(super::gdt::DOUBLE_FAULT_IX);
    IDT.page_fault.set_handler_fn(page_fault);

    IDT[Interrupt::ApicErr as u8].set_handler_fn(apic_err);
    IDT[Interrupt::Spurious as u8].set_handler_fn(spurious);
    IDT[Interrupt::Timer as u8].set_handler_fn(timer);

    IDT.load();

    LAPIC = LocalApicBuilder::new()
        .timer_vector(Interrupt::Timer as usize)
        .error_vector(Interrupt::ApicErr as usize)
        .spurious_vector(Interrupt::Spurious as usize)
        .set_xapic_base(
            x2apic::lapic::xapic_base()
                + super::memory::HHDM_OFFSET.load(core::sync::atomic::Ordering::Relaxed),
        )
        .build()
        .expect("Failed to setup Local APIC");
    LAPIC.enable();

    x86_64::instructions::interrupts::enable();
}

extern "x86-interrupt" fn double_fault(stack_frame: InterruptStackFrame, error_code: u64) -> ! {
    panic!("Double fault: error code {error_code} \n{stack_frame:#?}")
}

extern "x86-interrupt" fn page_fault(
    stack_frame: InterruptStackFrame,
    error_code: PageFaultErrorCode,
) {
    panic!("Page fault ({error_code:?}): {stack_frame:?}")
}

extern "x86-interrupt" fn timer(_isf: InterruptStackFrame) {
    unsafe {
        LAPIC.end_of_interrupt();
    }
}

extern "x86-interrupt" fn apic_err(_: InterruptStackFrame) {
    panic!("Internal APIC error");
}

extern "x86-interrupt" fn spurious(_: InterruptStackFrame) {
    unsafe {
        LAPIC.end_of_interrupt();
    }
}