#include <drivers/e1000.h>
#include <drivers/pci.h>
#include <mm/vmm.h>
#include <vga.h>
#include <string.h>
#include <misc/types.h>
#include <irq.h>
#include <context.h>
#include <misc/sched.h>

// e1000 register offsets (subset).
//
// Why these specific regs:
// - CTRL/STATUS confirms MMIO reads are sane and the device is alive.
// - RAL/RAH gives us the programmed MAC address QEMU expects us to use.
#define E1000_REG_CTRL   0x0000
#define E1000_REG_STATUS 0x0008
#define E1000_REG_RAL0   0x5400
#define E1000_REG_RAH0   0x5404

// TX registers (legacy descriptor mode)
#define E1000_REG_TDBAL  0x3800
#define E1000_REG_TDBAH  0x3804
#define E1000_REG_TDLEN  0x3808
#define E1000_REG_TDH    0x3810
#define E1000_REG_TDT    0x3818
#define E1000_REG_TCTL   0x0400
#define E1000_REG_TIPG   0x0410

// RX registers (legacy descriptor mode)
#define E1000_REG_RDBAL  0x2800
#define E1000_REG_RDBAH  0x2804
#define E1000_REG_RDLEN  0x2808
#define E1000_REG_RDH    0x2810
#define E1000_REG_RDT    0x2818
#define E1000_REG_RCTL   0x0100

// Interrupt registers (RX assist)
#define E1000_REG_ICR    0x00C0
#define E1000_REG_IMS    0x00D0
#define E1000_REG_IMC    0x00D8

#define E1000_ICR_RXDMT0 (1u << 4)
#define E1000_ICR_RXO    (1u << 6)
#define E1000_ICR_RXT0   (1u << 7)

/* Use a fixed high kernel VA window for device MMIO.
 *
 * Why a fixed VA:
 * - Keeps bring-up simple and avoids depending on a full virtual allocator.
 * - Avoids colliding with the kernel heap (starts at 0xD0000000).
 *
 * This is intentionally narrow: we only map what we need for probing.
*/
#define E1000_MMIO_VA_BASE   0xF0000000u
#define E1000_MMIO_MAP_BYTES 0x7000u  /* covers up through RAH0 */

// TX ring settings
#define E1000_TX_RING_COUNT 16u
#define E1000_TX_BUF_SIZE   2048u

#define E1000_RX_RING_COUNT 32u
#define E1000_RX_BUF_SIZE   2048u

// TX descriptor command/status bits.
#define E1000_TXD_CMD_EOP  (1u << 0)
#define E1000_TXD_CMD_IFCS (1u << 1)
#define E1000_TXD_CMD_RS   (1u << 3)
#define E1000_TXD_STAT_DD  (1u << 0)

typedef struct __attribute__((packed)) e1000_tx_desc {
    uint64 addr;
    uint16 length;
    uint8 cso;
    uint8 cmd;
    uint8 status;
    uint8 css;
    uint16 special;
} e1000_tx_desc;

typedef struct __attribute__((packed)) e1000_rx_desc {
    uint64 addr;
    uint16 length;
    uint16 csum;
    uint8 status;
    uint8 errors;
    uint16 special;
} e1000_rx_desc;

typedef struct e1000_state {
    int mmio_mapped;
    uint8 bus, dev, fun;
    uint32 bar0;
    uint8 irq_line;
    volatile uint8 rx_irq_pending;

    int tx_ready;
    uint32 tx_ring_phys;
    e1000_tx_desc* tx_ring;
    uint32 tx_buf_phys;
    uint8* tx_bufs;
    uint32 tx_tail;

    int rx_ready;
    uint32 rx_ring_phys;
    e1000_rx_desc* rx_ring;
    uint32 rx_buf_phys;
    uint8* rx_bufs;
    uint32 rx_cur;

    uint8 mac[6];
} e1000_state;

static e1000_state g_e1000;

typedef struct __attribute__((packed)) eth_hdr {
    uint8 dst[6];
    uint8 src[6];
    uint16 ethertype_be;
} eth_hdr;

static uint16 bswap16(uint16 x)
{
    return (uint16)((x >> 8) | (x << 8));
}

static int e1000_ctx_allow(uint32 caps, uint32 cost)
{
    command_context_t* ctx = current_command_context;
    if (ctx && !cap_check(ctx->caps, caps)) return 0;
    if (ctx) {
        scheduler_account(ctx->wo, cost);
        scheduler_yield_if_needed(ctx->wo);
        if (sched_det_is_enabled()) ctx->det_seq++;
    }
    return 1;
}

static void e1000_ctx_account(uint32 cost)
{
    command_context_t* ctx = current_command_context;
    if (!ctx) return;
    scheduler_account(ctx->wo, cost);
    scheduler_yield_if_needed(ctx->wo);
    if (sched_det_is_enabled()) ctx->det_seq++;
}

// Forward declarations: used by early bring-up helpers below.
static inline uint32 e1000_mmio_read32(uint32 reg);
static inline void e1000_mmio_write32(uint32 reg, uint32 value);

// Ring init helpers are defined later but used by `e1000_init()`.
static int e1000_rx_init_once(void);
static int e1000_tx_init_once(void);

static void e1000_program_rar0_from_mac(void)
{
    // Why we do this explicitly:
    // - e1000 will drop unicast frames unless a Receive Address Register entry is valid.
    // - QEMU typically pre-programs it, but being explicit avoids "RX is dead" confusion.
    uint32 ral = (uint32)g_e1000.mac[0]
        | ((uint32)g_e1000.mac[1] << 8)
        | ((uint32)g_e1000.mac[2] << 16)
        | ((uint32)g_e1000.mac[3] << 24);
    uint32 rah = (uint32)g_e1000.mac[4]
        | ((uint32)g_e1000.mac[5] << 8)
        | (1u << 31); // AV (address valid)

    e1000_mmio_write32(E1000_REG_RAL0, ral);
    e1000_mmio_write32(E1000_REG_RAH0, rah);
}

void e1000_debug_regs_print(void)
{
    // Safe read-only snapshot of a few registers used in bring-up.
    // Useful when RX appears "silent".
    uint32 status = e1000_mmio_read32(E1000_REG_STATUS);
    uint32 tctl = e1000_mmio_read32(E1000_REG_TCTL);
    uint32 rctl = e1000_mmio_read32(E1000_REG_RCTL);

    uint32 tdbal = e1000_mmio_read32(E1000_REG_TDBAL);
    uint32 tdlen = e1000_mmio_read32(E1000_REG_TDLEN);
    uint32 tdh = e1000_mmio_read32(E1000_REG_TDH);
    uint32 tdt = e1000_mmio_read32(E1000_REG_TDT);

    uint32 rdbal = e1000_mmio_read32(E1000_REG_RDBAL);
    uint32 rdlen = e1000_mmio_read32(E1000_REG_RDLEN);
    uint32 rdh = e1000_mmio_read32(E1000_REG_RDH);
    uint32 rdt = e1000_mmio_read32(E1000_REG_RDT);

    uint32 ral0 = e1000_mmio_read32(E1000_REG_RAL0);
    uint32 rah0 = e1000_mmio_read32(E1000_REG_RAH0);
    uint32 icr = e1000_mmio_read32(E1000_REG_ICR);

    printf("e1000 regs: STATUS=%08x\n", (unsigned)status);
    printf("  TX: TCTL=%08x TDBAL=%08x TDLEN=%08x TDH=%08x TDT=%08x\n",
           (unsigned)tctl, (unsigned)tdbal, (unsigned)tdlen, (unsigned)tdh, (unsigned)tdt);
    printf("  RX: RCTL=%08x RDBAL=%08x RDLEN=%08x RDH=%08x RDT=%08x\n",
           (unsigned)rctl, (unsigned)rdbal, (unsigned)rdlen, (unsigned)rdh, (unsigned)rdt);
    printf("  RAR0: RAL=%08x RAH=%08x ICR=%08x\n", (unsigned)ral0, (unsigned)rah0, (unsigned)icr);
}

int e1000_init(void)
{
    if (!e1000_ctx_allow(CAP_DEV_NET | CAP_ALLOC_MEMORY, SCHED_COST_ALLOC)) return -1;
    // Single entrypoint to get the NIC into a known-good state.
    // Why:
    // - Lets `e1000 regs` reflect post-init state.
    // - Avoids ordering mistakes (RX should be enabled before we rely on it).
    int rc = e1000_rx_init_once();
    if (rc != 0) return rc;
    rc = e1000_tx_init_once();
    if (rc != 0) return rc;
    (void)e1000_irq_enable_rx();
    return 0;
}

int e1000_get_mac(uint8 out_mac[6])
{
    if (!out_mac) return -1;

    // Ensure g_e1000.mac is populated.
    e1000_probe_info info;
    if (e1000_probe(&info) != 0) return -2;

    for (int i = 0; i < 6; i++) out_mac[i] = g_e1000.mac[i];
    return 0;
}

static void print_mac(const uint8 mac[6])
{
    printf("%02x:%02x:%02x:%02x:%02x:%02x",
           (unsigned)mac[0], (unsigned)mac[1], (unsigned)mac[2],
           (unsigned)mac[3], (unsigned)mac[4], (unsigned)mac[5]);
}

static void e1000_map_mmio(uint32 phys_base)
{
    // MMIO should not be cached; PCD is the usual safe choice.
    // We also set PWT to avoid surprises on some platforms.
    const uint32 flags = PTE_RW | PTE_PCD | PTE_PWT;

    uint32 phys_page = phys_base & PAGE_MASK;
    uint32 va = E1000_MMIO_VA_BASE;

    for (uint32 off = 0; off < E1000_MMIO_MAP_BYTES; off += PAGE_SIZE) {
        if (vmm_map_page(&vmm_kernel_as, va + off, phys_page + off, flags) != 0) {
            // Mapping failure will almost certainly lead to a page fault on access.
            // Keep the message short; callers can treat probe failure as "NIC not ready".
            printf("%cError: e1000 MMIO map failed.\n", 255, 0, 0);
            return;
        }
    }
}

static inline volatile uint32* e1000_mmio_reg_ptr(uint32 reg)
{
    return (volatile uint32*)(uintptr)(E1000_MMIO_VA_BASE + reg);
}

static inline void* e1000_kva_from_phys(uint32 phys)
{
    return (void*)((uintptr)KERNEL_BASE + (uintptr)phys);
}

static inline uint32 e1000_mmio_read32(uint32 reg)
{
    volatile uint32* p = e1000_mmio_reg_ptr(reg);
    return *p;
}

static inline void e1000_mmio_write32(uint32 reg, uint32 value)
{
    volatile uint32* p = e1000_mmio_reg_ptr(reg);
    *p = value;
}

static inline void e1000_compiler_barrier(void)
{
    __asm__ __volatile__("" ::: "memory");
}

static int e1000_find_first(uint8* out_bus, uint8* out_dev, uint8* out_fun, uint32* out_bar0)
{
    // Minimal device discovery: find the first Intel 82540EM/"e1000" function.
    // We can grow this into a more general NIC registry later.
    for (uint16 bus = 0; bus < 256; bus++) {
        if ((bus & 0x0Fu) == 0u) e1000_ctx_account(SCHED_COST_FS);
        for (uint8 dev = 0; dev < 32; dev++) {
            uint16 vendor0 = pci_read_config_word((uint8)bus, dev, 0, 0x00);
            if (vendor0 == 0xFFFFu) continue;

            uint8 header0 = pci_read_config_byte((uint8)bus, dev, 0, 0x0E);
            uint8 max_fun = (header0 & 0x80u) ? 7 : 0;

            for (uint8 fun = 0; fun <= max_fun; fun++) {
                uint16 vendor = pci_read_config_word((uint8)bus, dev, fun, 0x00);
                if (vendor == 0xFFFFu) continue;

                uint16 device = pci_read_config_word((uint8)bus, dev, fun, 0x02);
                if (vendor == 0x8086u && device == 0x100Eu) {
                    uint32 bar0 = pci_read_config_dword((uint8)bus, dev, fun, 0x10);
                    uint8 is_io = (uint8)(bar0 & 0x1u);
                    if (is_io) {
                        // QEMU's e1000 should expose MMIO; if it's I/O space something is off.
                        return -2;
                    }

                    if (out_bus) *out_bus = (uint8)bus;
                    if (out_dev) *out_dev = dev;
                    if (out_fun) *out_fun = fun;
                    if (out_bar0) *out_bar0 = (bar0 & ~0xFu);
                    return 0;
                }
            }
        }
    }

    return -1;
}

static void e1000_enable_pci_bus_master(uint8 bus, uint8 dev, uint8 fun)
{
    // DMA (TX/RX rings) requires bus mastering. Enabling it early is safe as
    // long as we haven't programmed the device to actually DMA anywhere.
    uint32 dword = pci_read_config_dword(bus, dev, fun, 0x04);
    uint16 cmd = (uint16)(dword & 0xFFFFu);

    cmd |= (1u << 1); /* MEM space */
    cmd |= (1u << 2); /* bus master */

    dword = (dword & 0xFFFF0000u) | (uint32)cmd;
    pci_write_config_dword(bus, dev, fun, 0x04, dword);
}

int e1000_probe(e1000_probe_info* out)
{
    if (!e1000_ctx_allow(CAP_DEV_NET, SCHED_COST_FS)) return -1;
    uint8 bus = 0, dev = 0, fun = 0;
    uint32 bar0 = 0;

    int rc = e1000_find_first(&bus, &dev, &fun, &bar0);
    if (rc == -1) {
        return -1;
    }
    if (rc == -2) {
        return -1;
    }

    e1000_enable_pci_bus_master(bus, dev, fun);

    // Map the device registers into kernel VA so MMIO reads won't fault.
    // We keep a single fixed mapping window for now (one NIC expected in QEMU).
    if (!g_e1000.mmio_mapped || g_e1000.bar0 != bar0) {
        e1000_map_mmio(bar0);
        g_e1000.mmio_mapped = 1;
    }
    g_e1000.bus = bus;
    g_e1000.dev = dev;
    g_e1000.fun = fun;
    g_e1000.bar0 = bar0;
    g_e1000.irq_line = pci_read_config_byte(bus, dev, fun, 0x3C);

    uint32 ctrl = e1000_mmio_read32(E1000_REG_CTRL);
    uint32 status = e1000_mmio_read32(E1000_REG_STATUS);

    // Link-up bit in STATUS is bit 1 for e1000.
    int link_up = (status & (1u << 1)) ? 1 : 0;

    uint32 ral = e1000_mmio_read32(E1000_REG_RAL0);
    uint32 rah = e1000_mmio_read32(E1000_REG_RAH0);

    uint8 mac0 = (uint8)((ral >> 0) & 0xFFu);
    uint8 mac1 = (uint8)((ral >> 8) & 0xFFu);
    uint8 mac2 = (uint8)((ral >> 16) & 0xFFu);
    uint8 mac3 = (uint8)((ral >> 24) & 0xFFu);
    uint8 mac4 = (uint8)((rah >> 0) & 0xFFu);
    uint8 mac5 = (uint8)((rah >> 8) & 0xFFu);

    g_e1000.mac[0] = mac0;
    g_e1000.mac[1] = mac1;
    g_e1000.mac[2] = mac2;
    g_e1000.mac[3] = mac3;
    g_e1000.mac[4] = mac4;
    g_e1000.mac[5] = mac5;

    if (out) {
        out->bus = bus;
        out->device = dev;
        out->function = fun;
        out->bar0 = bar0;
        out->ctrl = ctrl;
        out->status = status;
        out->link_up = link_up;
        out->mac[0] = mac0;
        out->mac[1] = mac1;
        out->mac[2] = mac2;
        out->mac[3] = mac3;
        out->mac[4] = mac4;
        out->mac[5] = mac5;
    }
    return 0;
}

static int e1000_tx_init_once(void)
{
    if (!e1000_ctx_allow(CAP_DEV_NET | CAP_ALLOC_MEMORY, SCHED_COST_ALLOC)) return -1;
    if (g_e1000.tx_ready) return 0;

    // Ensure we can talk to the device and have its BAR0 + MAC.
    // Why probe first:
    // - Verifies MMIO access works before we touch any TX registers.
    // - Gives us a source MAC for test frames.
    e1000_probe_info info;
    if (e1000_probe(&info) != 0) {
        return -1;
    }

    // Allocate DMA memory.
    //
    // Why contiguous:
    // - e1000 descriptors/buffers only require physical addresses, but using
    //   contiguous frames simplifies bookkeeping and avoids edge cases.
    const uint32 ring_bytes = (uint32)(E1000_TX_RING_COUNT * (uint32)sizeof(e1000_tx_desc));
    const uint32 ring_pages = (ring_bytes + PAGE_SIZE - 1) / PAGE_SIZE;
    const uint32 buf_bytes = E1000_TX_RING_COUNT * E1000_TX_BUF_SIZE;
    const uint32 buf_pages = (buf_bytes + PAGE_SIZE - 1) / PAGE_SIZE;

    uint32 ring_phys = frame_alloc_contiguous(ring_pages);
    if (ring_phys == 0) return -2;
    uint32 buf_phys = frame_alloc_contiguous(buf_pages);
    if (buf_phys == 0) return -3;

    g_e1000.tx_ring_phys = ring_phys;
    g_e1000.tx_ring = (e1000_tx_desc*)e1000_kva_from_phys(ring_phys);
    g_e1000.tx_buf_phys = buf_phys;
    g_e1000.tx_bufs = (uint8*)e1000_kva_from_phys(buf_phys);
    g_e1000.tx_tail = 0;

    memset(g_e1000.tx_ring, 0, ring_pages * PAGE_SIZE);
    memset(g_e1000.tx_bufs, 0, buf_pages * PAGE_SIZE);

    // Program descriptor ring base/len.
    // The spec wants TDLEN to be a multiple of 128.
    e1000_mmio_write32(E1000_REG_TDBAL, ring_phys);
    e1000_mmio_write32(E1000_REG_TDBAH, 0);
    e1000_mmio_write32(E1000_REG_TDLEN, ring_bytes);

    // Start with empty ring.
    e1000_mmio_write32(E1000_REG_TDH, 0);
    e1000_mmio_write32(E1000_REG_TDT, 0);

    // Configure transmit control.
    //
    // Why these bits:
    // - EN enables transmission.
    // - PSP pads short frames up to the minimum Ethernet size.
    // - CT/COLD are collision tuning fields required by the hardware.
    //   Values here are the common "works everywhere" defaults.
    uint32 tctl = 0;
    tctl |= (1u << 1);  // EN
    tctl |= (1u << 3);  // PSP
    tctl |= (0x10u << 4);  // CT
    tctl |= (0x40u << 12); // COLD
    e1000_mmio_write32(E1000_REG_TCTL, tctl);

    // Inter-packet gap (recommended defaults for 8254x).
    uint32 tipg = 0;
    tipg |= 10u;
    tipg |= (8u << 10);
    tipg |= (6u << 20);
    e1000_mmio_write32(E1000_REG_TIPG, tipg);

    g_e1000.tx_ready = 1;
    return 0;
}

static int e1000_tx_send_frame(const void* frame, uint32 len)
{
    if (!e1000_ctx_allow(CAP_DEV_NET, SCHED_COST_FS)) return -1;
    if (!frame || len == 0 || len > E1000_TX_BUF_SIZE) return -1;
    if (e1000_tx_init_once() != 0) return -2;

    uint32 idx = g_e1000.tx_tail % E1000_TX_RING_COUNT;
    e1000_tx_desc* d = &g_e1000.tx_ring[idx];

    uint8* buf = g_e1000.tx_bufs + (idx * E1000_TX_BUF_SIZE);
    uint32 buf_phys = g_e1000.tx_buf_phys + (idx * E1000_TX_BUF_SIZE);

    // Copy the frame into the DMA buffer the NIC will read.
    memcpy(buf, frame, len);

    // Give the NIC a fresh descriptor.
    //
    // Why RS (report status):
    // - Lets us poll DD to confirm the device consumed the descriptor.
    memset(d, 0, sizeof(*d));
    d->addr = (uint64)buf_phys;
    d->length = (uint16)len;
    d->cmd = (uint8)(E1000_TXD_CMD_EOP | E1000_TXD_CMD_IFCS | E1000_TXD_CMD_RS);
    d->status = 0;

    // Ensure descriptor writes are visible before updating TDT.
    e1000_compiler_barrier();

    g_e1000.tx_tail = (idx + 1) % E1000_TX_RING_COUNT;
    e1000_mmio_write32(E1000_REG_TDT, g_e1000.tx_tail);

    // Poll for completion (DD set by hardware).
    // Why polling first:
    // - Interrupts are the next step, but polling is deterministic for bring-up.
    for (uint32 spin = 0; spin < 1000000u; spin++) {
        if ((spin & 0x3FFu) == 0u) e1000_ctx_account(SCHED_COST_FS);
        if (d->status & E1000_TXD_STAT_DD) return 0;
    }

    return -3;
}

int e1000_send_frame(const void* frame, uint32 len)
{
    return e1000_tx_send_frame(frame, len);
}

static void e1000_irq_handler(void)
{
    uint32 icr = e1000_mmio_read32(E1000_REG_ICR);
    if (icr & (E1000_ICR_RXDMT0 | E1000_ICR_RXO | E1000_ICR_RXT0)) {
        g_e1000.rx_irq_pending = 1;
    }
    if (g_e1000.irq_line <= 15) {
        pic_send_eoi((int)g_e1000.irq_line);
    }
}

int e1000_irq_enable_rx(void)
{
    if (!e1000_ctx_allow(CAP_DEV_NET, SCHED_COST_FS)) return -1;
    if (g_e1000.irq_line == 0xFFu || g_e1000.irq_line > 15) return -1;

    register_interrupt_handler((int)g_e1000.irq_line, e1000_irq_handler);
    g_e1000.rx_irq_pending = 0;

    // Mask all, then enable RX-related interrupts
    e1000_mmio_write32(E1000_REG_IMC, 0xFFFFFFFFu);
    e1000_mmio_write32(E1000_REG_IMS, (E1000_ICR_RXDMT0 | E1000_ICR_RXO | E1000_ICR_RXT0));
    return 0;
}

int e1000_irq_rx_pending(void)
{
    return g_e1000.rx_irq_pending ? 1 : 0;
}

void e1000_irq_clear_rx_pending(void)
{
    g_e1000.rx_irq_pending = 0;
}

static int e1000_rx_init_once(void)
{
    if (!e1000_ctx_allow(CAP_DEV_NET | CAP_ALLOC_MEMORY, SCHED_COST_ALLOC)) return -1;
    if (g_e1000.rx_ready) return 0;

    // Like TX, do a probe first so BAR0/MMIO and MAC are stable.
    e1000_probe_info info;
    if (e1000_probe(&info) != 0) return -1;

    const uint32 ring_bytes = (uint32)(E1000_RX_RING_COUNT * (uint32)sizeof(e1000_rx_desc));
    const uint32 ring_pages = (ring_bytes + PAGE_SIZE - 1) / PAGE_SIZE;
    const uint32 buf_bytes = E1000_RX_RING_COUNT * E1000_RX_BUF_SIZE;
    const uint32 buf_pages = (buf_bytes + PAGE_SIZE - 1) / PAGE_SIZE;

    uint32 ring_phys = frame_alloc_contiguous(ring_pages);
    if (ring_phys == 0) return -2;
    uint32 buf_phys = frame_alloc_contiguous(buf_pages);
    if (buf_phys == 0) return -3;

    g_e1000.rx_ring_phys = ring_phys;
    g_e1000.rx_ring = (e1000_rx_desc*)e1000_kva_from_phys(ring_phys);
    g_e1000.rx_buf_phys = buf_phys;
    g_e1000.rx_bufs = (uint8*)e1000_kva_from_phys(buf_phys);
    g_e1000.rx_cur = 0;

    memset(g_e1000.rx_ring, 0, ring_pages * PAGE_SIZE);
    memset(g_e1000.rx_bufs, 0, buf_pages * PAGE_SIZE);

    // Give each descriptor a buffer the NIC can DMA into.
    for (uint32 i = 0; i < E1000_RX_RING_COUNT; i++) {
        uint32 bphys = g_e1000.rx_buf_phys + (i * E1000_RX_BUF_SIZE);
        g_e1000.rx_ring[i].addr = (uint64)bphys;
        g_e1000.rx_ring[i].status = 0;
    }

    // Program ring base/len.
    e1000_mmio_write32(E1000_REG_RDBAL, ring_phys);
    e1000_mmio_write32(E1000_REG_RDBAH, 0);
    e1000_mmio_write32(E1000_REG_RDLEN, ring_bytes);

    // Initialize head/tail.
    // Hardware owns [RDH..RDT] as the receive queue; we set RDT to the last
    // available descriptor so the NIC can start writing immediately.
    e1000_mmio_write32(E1000_REG_RDH, 0);
    e1000_mmio_write32(E1000_REG_RDT, E1000_RX_RING_COUNT - 1);

    // Enable receiver.
    // EN: enable receive
    // BAM: accept broadcast
    // SECRC: strip Ethernet CRC from received buffers
    // BSIZE=2048 (00)
    uint32 rctl = 0;
    rctl |= (1u << 1);   // EN
    // Promiscuous bits during bring-up to make RX validation easier.
    // We'll tighten this later once ARP/IP is stable.
    rctl |= (1u << 3);   // UPE (unicast promiscuous)
    rctl |= (1u << 4);   // MPE (multicast promiscuous)
    rctl |= (1u << 15);  // BAM
    rctl |= (1u << 26);  // SECRC

    // Ensure the NIC knows our MAC for unicast filtering (even if we leave UPE on for now).
    e1000_program_rar0_from_mac();
    e1000_mmio_write32(E1000_REG_RCTL, rctl);

    g_e1000.rx_ready = 1;
    return 0;
}

// Poll and print up to `max_packets` frames.
// Returns number of packets printed.
int e1000_rx_poll_and_print(int max_packets, int spin_limit)
{
    if (!e1000_ctx_allow(CAP_DEV_NET, SCHED_COST_FS)) return -1;
    if (max_packets <= 0) return 0;
    if (spin_limit <= 0) spin_limit = 1000000;
    if (e1000_rx_init_once() != 0) return -1;

    int printed = 0;
    for (int spin = 0; spin < spin_limit && printed < max_packets; spin++) {
        if ((spin & 0x3FF) == 0) e1000_ctx_account(SCHED_COST_FS);
        uint32 idx = g_e1000.rx_cur % E1000_RX_RING_COUNT;
        e1000_rx_desc* d = &g_e1000.rx_ring[idx];
        if (!(d->status & 0x01u)) {
            continue; // DD not set yet
        }

        uint8* buf = g_e1000.rx_bufs + (idx * E1000_RX_BUF_SIZE);
        uint32 len = (uint32)d->length;

        if (len >= (uint32)sizeof(eth_hdr)) {
            eth_hdr* eh = (eth_hdr*)buf;
            uint16 et = bswap16(eh->ethertype_be);

            printf("RX %d bytes et=0x%04x dst=", (int)len, (unsigned)et);
            print_mac(eh->dst);
            printf(" src=");
            print_mac(eh->src);
            printf("\n");
        } else {
            printf("RX %d bytes\n", (int)len);
        }

        printed++;

        // Hand the descriptor back to hardware.
        d->status = 0;
        d->length = 0;
        e1000_compiler_barrier();

        e1000_mmio_write32(E1000_REG_RDT, idx);
        g_e1000.rx_cur = (idx + 1) % E1000_RX_RING_COUNT;
    }

    return printed;
}

int e1000_rx_poll_frame(uint8* out_buf, uint32 out_buf_cap, uint32* out_len, int spin_limit)
{
    if (!e1000_ctx_allow(CAP_DEV_NET, SCHED_COST_FS)) return -1;
    if (!out_buf || out_buf_cap == 0 || !out_len) return -1;
    if (spin_limit <= 0) spin_limit = 1000000;
    if (e1000_rx_init_once() != 0) return -2;

    for (int spin = 0; spin < spin_limit; spin++) {
        if ((spin & 0x3FF) == 0) e1000_ctx_account(SCHED_COST_FS);
        uint32 idx = g_e1000.rx_cur % E1000_RX_RING_COUNT;
        e1000_rx_desc* d = &g_e1000.rx_ring[idx];
        if (!(d->status & 0x01u)) continue;

        uint8* buf = g_e1000.rx_bufs + (idx * E1000_RX_BUF_SIZE);
        uint32 len = (uint32)d->length;

        uint32 copy_len = len;
        if (copy_len > out_buf_cap) copy_len = out_buf_cap;
        memcpy(out_buf, buf, copy_len);
        *out_len = len;

        // Hand the descriptor back to hardware.
        d->status = 0;
        d->length = 0;
        e1000_compiler_barrier();
        e1000_mmio_write32(E1000_REG_RDT, idx);
        g_e1000.rx_cur = (idx + 1) % E1000_RX_RING_COUNT;

        return 1;
    }

    return 0;
}

int e1000_tx_test_send(const char* message)
{
    // Build a small Ethernet frame we can transmit without any IP stack.
    //
    // Why broadcast:
    // - Avoids ARP/neighbor discovery; it's guaranteed to be deliverable at L2.
    // - This is strictly a TX-path validation checkpoint.
    uint8 frame[64];
    memset(frame, 0, sizeof(frame));

    // dst = ff:ff:ff:ff:ff:ff
    for (int i = 0; i < 6; i++) frame[i] = 0xFF;
    // src = NIC MAC
    for (int i = 0; i < 6; i++) frame[6 + i] = g_e1000.mac[i];
    // EtherType = 0x88B5 (locally used/test)
    frame[12] = 0x88;
    frame[13] = 0xB5;

    // Payload
    uint32 payload_off = 14;
    uint32 payload_cap = (uint32)sizeof(frame) - payload_off;
    uint32 payload_len = 0;
    if (!message) message = "EYN-OS e1000 tx-test";
    while (message[payload_len] && payload_len < payload_cap) {
        frame[payload_off + payload_len] = (uint8)message[payload_len];
        payload_len++;
    }

    // Ethernet minimum frame size (without FCS) is 60 bytes.
    uint32 total_len = payload_off + payload_len;
    if (total_len < 60u) total_len = 60u;
    if (total_len > (uint32)sizeof(frame)) total_len = (uint32)sizeof(frame);

    int rc = e1000_tx_send_frame(frame, total_len);
    if (rc == 0) {
        printf("%cTX ok (%d bytes).\n", 0, 255, 0, (int)total_len);
    } else {
        printf("%cTX failed (%d).\n", 255, 0, 0, rc);
    }
    return rc;
}

int e1000_probe_and_print(void)
{
    e1000_probe_info info;
    int rc = e1000_probe(&info);
    if (rc == -1) {
        printf("%cError: e1000 not found (expected 8086:100E).\n", 255, 0, 0);
        return -1;
    }
    if (rc == -2) {
        printf("%cError: e1000 BAR0 is I/O space, expected MMIO.\n", 255, 0, 0);
        return -1;
    }
    if (rc != 0) {
        printf("%cError: e1000 probe failed (%d).\n", 255, 0, 0, rc);
        return -1;
    }

    printf("e1000 @ %02x:%02x.%d bar0=%08x\n",
           (unsigned)info.bus, (unsigned)info.device, (int)info.function, (unsigned)info.bar0);
    printf("  CTRL=%08x STATUS=%08x link=%s\n",
           (unsigned)info.ctrl, (unsigned)info.status, info.link_up ? "up" : "down");
    printf("  MAC=%02x:%02x:%02x:%02x:%02x:%02x\n",
           (unsigned)info.mac[0], (unsigned)info.mac[1], (unsigned)info.mac[2],
           (unsigned)info.mac[3], (unsigned)info.mac[4], (unsigned)info.mac[5]);

    return 0;
}
