//! TextEncoder / TextDecoder runtime.
//!
//! `js_text_encoder_encode_llvm` returns a `BufferHeader*` (packed u8 bytes,
//! identical layout to `new Uint8Array([...])`) so the inline `bytes[i]`
//! Uint8ArrayGet path (which reads `i8` at `ptr+8+idx`) sees real byte
//! values. Previously this allocated an `ArrayHeader` with f64-per-byte
//! storage, which iteration paths after #578 read as packed u8 — yielding
//! the IEEE-754 byte pattern of the first byte instead of the byte itself
//! (issue #584).
//!
//! `TextEncoder` / `TextDecoder` are stateless wrappers — the encoder is
//! always UTF-8, so we return a small sentinel integer NaN-boxed as a
//! pointer on the codegen side. The runtime doesn't need per-instance state.

use crate::buffer::{buffer_alloc, buffer_data_mut, mark_as_uint8array, BufferHeader};
use crate::string::{js_string_from_bytes, StringHeader};

/// `new TextEncoder()` — returns a non-null sentinel integer pointer.
///
/// The returned value is a small integer (`1`) that the codegen NaN-boxes
/// with `POINTER_TAG`. TextEncoder has no state beyond "I encode UTF-8",
/// so any non-null sentinel works. We use a distinct value from the
/// decoder sentinel purely for debuggability.
#[no_mangle]
pub extern "C" fn js_text_encoder_new() -> i64 {
    1
}

/// `new TextDecoder()` — returns a non-null sentinel integer pointer.
#[no_mangle]
pub extern "C" fn js_text_decoder_new() -> i64 {
    2
}

/// `encoder.encode(str)` — UTF-8 encode `value` into a `BufferHeader`.
///
/// Takes a NaN-boxed f64 string value. Returns an i64 pointer to a freshly
/// allocated `BufferHeader` with `len` packed u8 bytes (same shape as
/// `new Uint8Array([...])`). The buffer is registered + marked as Uint8Array
/// so `instanceof Uint8Array` returns true and the standard Uint8Array
/// indexed-access / iteration / decoder paths all work.
///
/// The returned i64 is the raw `BufferHeader*` — the codegen NaN-boxes it
/// with `POINTER_TAG` before handing it to user code.
#[no_mangle]
pub extern "C" fn js_text_encoder_encode_llvm(value: f64) -> i64 {
    let str_ptr_i = crate::value::js_get_string_pointer_unified(value);
    let (data_ptr, len) = if str_ptr_i == 0 {
        (std::ptr::null::<u8>(), 0usize)
    } else {
        let str_ptr = str_ptr_i as *const StringHeader;
        unsafe {
            let l = (*str_ptr).byte_len as usize;
            let d = (str_ptr as *const u8).add(std::mem::size_of::<StringHeader>());
            (d, l)
        }
    };

    let buf = buffer_alloc(len as u32);
    unsafe {
        (*buf).length = len as u32;
        if len > 0 {
            std::ptr::copy_nonoverlapping(data_ptr, buffer_data_mut(buf), len);
        }
    }
    mark_as_uint8array(buf as usize);

    buf as i64
}

/// `decoder.decode(buf)` — UTF-8 decode a NaN-boxed `BufferHeader` value.
///
/// Returns a `*const StringHeader` as i64 — the codegen NaN-boxes with
/// `STRING_TAG`. Both TextEncoder output and `new Uint8Array([...])` share
/// the same packed-u8 BufferHeader layout, so a single read path covers both.
#[no_mangle]
pub extern "C" fn js_text_decoder_decode_llvm(value: f64) -> i64 {
    let bits = value.to_bits();

    // Unbox the pointer. Accept both POINTER_TAG NaN-boxing and raw small
    // pointer fallback (covers both `encoded` values and `new Uint8Array(...)`
    // bitcast results).
    let ptr_usize: usize = {
        const POINTER_TAG: u64 = 0x7FFD_0000_0000_0000;
        const POINTER_MASK: u64 = 0x0000_FFFF_FFFF_FFFF;
        const TAG_MASK: u64 = 0xFFFF_0000_0000_0000;
        if (bits & TAG_MASK) == POINTER_TAG {
            (bits & POINTER_MASK) as usize
        } else if !value.is_nan() && bits != 0 && bits < 0x0001_0000_0000_0000 {
            bits as usize
        } else {
            0
        }
    };

    if ptr_usize == 0 || ptr_usize < 0x1000 {
        // Empty or invalid — return empty string.
        return js_string_from_bytes(std::ptr::null(), 0) as i64;
    }

    unsafe {
        let buf = ptr_usize as *const BufferHeader;
        let len = (*buf).length as usize;
        let data = (buf as *const u8).add(std::mem::size_of::<BufferHeader>());
        js_string_from_bytes(data, len as u32) as i64
    }
}
