//! Guest-thread image allocation — shared by the initial thread setup in
//! `xenia-app/src/main.rs` and `ExCreateThread`. Stack, PCR, and TLS blocks
//! all come from the existing kernel bump allocators so layout is consistent.

use xenia_memory::{GuestMemory, MemoryAccess};

use crate::state::KernelState;

/// Addresses the caller passes to `Scheduler::spawn` / the initial-thread
/// setup. Matches xenia-canary's per-thread allocations: a stack, a PCR, and
/// a TLS block.
#[derive(Debug, Clone, Copy)]
pub struct ThreadImage {
    pub stack_base: u32,
    pub stack_size: u32,
    pub pcr_base: u32,
    pub tls_base: u32,
}

/// Allocate stack + PCR + TLS for one guest thread and initialize the PCR
/// fields that games read in their thread prolog.
///
/// - Stack comes from `KernelState::stack_alloc` (bump allocator at
///   0x7100_0000 upward). The returned base is the *bottom*; callers
///   compute SP as `base + size`.
/// - PCR and TLS are fixed 4 KiB pages allocated via `heap_alloc` so they
///   land in the user heap region together with other kernel metadata.
/// - `hw_thread_id` is written at PCR+0x2C so `KeGetCurrentProcessorNumber`-
///   style reads from r13 resolve correctly even though we never register
///   that export.
pub fn allocate_thread_image(
    kernel: &mut KernelState,
    mem: &GuestMemory,
    stack_size: u32,
    hw_thread_id: u8,
) -> Option<ThreadImage> {
    // Round stack size to a page and give games a minimum that matches
    // xenia-canary's 16 MiB default when callers request 0 (common for
    // ExCreateThread when the caller lets the kernel pick).
    let stack_size = if stack_size == 0 {
        0x10_0000
    } else {
        (stack_size + 0xFFF) & !0xFFF
    };
    // stack_alloc returns top-of-stack; we need the base.
    let stack_top = kernel.stack_alloc(stack_size, mem)?;
    let stack_base = stack_top - stack_size;

    let pcr_base = kernel.heap_alloc(0x1000, mem)?;
    let tls_base = kernel.heap_alloc(0x1000, mem)?;

    // PCR layout (canary xboxkrnl/xboxkrnl_module.cc, simplified):
    //   +0x000  tls_ptr               → TLS block base
    //   +0x02C  current_processor_id  → HW thread id (0..5)
    //   +0x100  current_thread        → placeholder non-zero tag
    //   +0x150  dpc_active            → 0 (no DPC queued)
    mem.write_u32(pcr_base, tls_base);
    mem.write_u32(pcr_base + 0x2C, hw_thread_id as u32);
    mem.write_u32(pcr_base + 0x100, 0x1000);
    // +0x10C  prcb_data.current_cpu — canary `pcr->prcb_data.current_cpu`
    //         (PRCB@0x100 + current_cpu@0xC). Guest spin-barriers index a
    //         per-HW-thread slot array by `lbz r11, 268(r13)` = this byte; it
    //         must equal the HW thread id (== PCR+0x2C). See state.rs PcrWriter.
    mem.write_u8(pcr_base + 0x10C, hw_thread_id);
    mem.write_u32(pcr_base + 0x150, 0);

    Some(ThreadImage {
        stack_base,
        stack_size,
        pcr_base,
        tls_base,
    })
}
