//! Public runtime loop and worker-thread primitives.

use std::cell::{Cell, RefCell};
use std::collections::{HashMap, VecDeque};
use std::future::Future;
use std::io;
use std::pin::Pin;
use std::ptr;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, MutexGuard};
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use std::time::Duration;

use super::driver::{Driver, ThreadNotifier, create_driver, monotonic_now};
use crate::platform::runtime_shared::{
    IntervalCallback, LocalBoxFuture, LocalTask, LocalTaskQueue, MICROTASK_STARVATION_THRESHOLD,
    MacroTaskQueue, SendTask,
};
use crate::trace_targets;

struct MacroTask {
    task: LocalTask,
    /// Wall time at which this task entered the local queue. Populated only
    /// in debug builds; used to emit a trace event reporting queue-wait time.
    #[cfg(debug_assertions)]
    queued_at: Duration,
}

#[thread_local]
static mut CURRENT_THREAD: *mut ThreadState = ptr::null_mut();

#[derive(Clone)]
/// Handle for queueing work onto a specific runtime thread.
pub struct ThreadHandle {
    shared: Arc<ThreadShared>,
}

/// Handle for a worker runtime thread spawned with [`spawn_worker`].
pub struct WorkerHandle {
    thread: ThreadHandle,
    completion: Arc<WorkerCompletion>,
}

#[derive(Clone)]
/// Handle returned by [`set_timeout`].
pub struct TimeoutHandle {
    id: usize,
    owner: *const ThreadState,
    _local: Rc<()>,
}

impl TimeoutHandle {
    pub fn clear(&self) {
        clear_timeout(self);
    }
}

#[derive(Clone)]
/// Handle returned by [`set_interval`].
pub struct IntervalHandle {
    id: usize,
    owner: *const ThreadState,
    _local: Rc<()>,
}

impl IntervalHandle {
    pub fn clear(&self) {
        clear_interval(self);
    }
}

/// Handle returned by [`queue_future`].
///
/// Awaiting a join handle yields the output of the queued future.
pub struct JoinHandle<T> {
    state: Rc<JoinState<T>>,
}

/// Future returned by [`yield_now`].
///
/// Awaiting this future will immediately yield control back to the runtime scheduler, allowing other queued microtasks
/// to run before the current task continues executing. Note that continuations of futures run as microtasks, so this
/// can only yield to other microtasks and not to macrotasks (driver events such as file or network I/O, timers, or
/// channel messages). To yield to macrotasks, you must allow the flow of execution to return to the runtime event loop
/// and flush the full microtask queue, for example by awaiting a timer.
pub struct YieldNow {
    yielded: bool,
}

/// Returns a handle for the current runtime thread.
///
/// If the current thread has not yet entered the runtime, the runtime state is initialized lazily.
///
/// # Panics
///
/// Panics if the runtime cannot initialize its driver for the current thread.
pub fn current_thread_handle() -> ThreadHandle {
    current_thread().handle()
}

pub(crate) fn try_current_thread_handle() -> Option<ThreadHandle> {
    unsafe { (!CURRENT_THREAD.is_null()).then(|| (*CURRENT_THREAD).handle()) }
}

pub(crate) fn with_current_driver<T>(f: impl FnOnce(&Driver) -> T) -> T {
    f(&current_thread().driver)
}

/// Queues a macrotask on the current runtime thread.
///
/// The task runs after all currently-queued macrotasks, and after all microtasks.
///
/// # Panics
///
/// Panics if the runtime cannot initialize its state for the current thread.
pub fn queue_task<F>(task: F)
where
    F: FnOnce() + 'static,
{
    #[cfg(debug_assertions)]
    tracing::trace!(
        target: trace_targets::SCHEDULER,
        event = "queue_task",
        queue = "local_macro",
        "queueing local macrotask"
    );
    push_local_macrotask(Box::new(task));
}

/// Queues a microtask on the current runtime thread.
///
/// Microtasks run before the next macrotask turn, mirroring JavaScript-style event loop
/// semantics.
///
/// # Panics
///
/// Panics if the runtime cannot initialize its state for the current thread.
pub fn queue_microtask<F>(task: F)
where
    F: FnOnce() + 'static,
{
    #[cfg(debug_assertions)]
    tracing::trace!(
        target: trace_targets::SCHEDULER,
        event = "queue_microtask",
        queue = "local_micro",
        "queueing local microtask"
    );
    current_thread()
        .local_microtasks
        .borrow_mut()
        .push_back(Box::new(task));
}

/// Schedules a one-shot timer on the current runtime thread.
///
/// # Panics
///
/// Panics if the runtime cannot initialize its state for the current thread.
pub fn set_timeout<F>(delay: Duration, callback: F) -> TimeoutHandle
where
    F: FnOnce() + 'static,
{
    let owner = current_thread_ptr();
    let id = allocate_timer_id();
    let deadline = deadline_from_now(delay);
    #[cfg(debug_assertions)]
    tracing::trace!(
        target: trace_targets::TIMER,
        event = "set_timeout",
        timer_id = id,
        delay_ns = delay.as_nanos() as u64,
        deadline_ns = deadline.as_nanos() as u64,
        "scheduling timeout"
    );
    let timer = TimerNode::timeout(id, deadline, Box::new(callback));

    current_thread().timers.borrow_mut().insert(timer);
    rearm_thread_timer();

    TimeoutHandle {
        id,
        owner,
        _local: Rc::new(()),
    }
}

/// Cancels a timeout previously created by [`set_timeout`].
///
/// # Panics
///
/// Panics if called from a different runtime thread than the one that created `handle`.
pub fn clear_timeout(handle: &TimeoutHandle) {
    #[cfg(debug_assertions)]
    tracing::trace!(
        target: trace_targets::TIMER,
        event = "clear_timeout",
        timer_id = handle.id,
        "clearing timeout"
    );
    clear_timer(handle.owner, handle.id);
}

/// Schedules a repeating timer on the current runtime thread.
///
/// The callback is invoked once per interval until the handle is cleared.
///
/// # Panics
///
/// Panics if the runtime cannot initialize its state for the current thread.
pub fn set_interval<F>(delay: Duration, callback: F) -> IntervalHandle
where
    F: FnMut() + 'static,
{
    let owner = current_thread_ptr();
    let id = allocate_timer_id();

    #[cfg(debug_assertions)]
    tracing::trace!(
        target: trace_targets::TIMER,
        event = "set_interval",
        timer_id = id,
        delay_ns = delay.as_nanos() as u64,
        "scheduling interval"
    );

    if delay.is_zero() {
        unimplemented!("Zero-delay intervals are not yet implemented.")
        // TODO: vibeshit got this completely wrong
        // Zero-delay intervals never touch the timer heap or arm an io_uring
        // timer (a past-deadline kernel timer would fire on every event loop
        // iteration, spinning the runtime at 100 % CPU).  Instead the callback
        // is stored in `immediate_intervals` and self-schedules as a macrotask
        // each turn, mirroring JS `setInterval(f, 0)` semantics.
        // let callback = Rc::new(RefCell::new(Box::new(callback) as Box<dyn FnMut()>));
        // current_thread()
        //     .immediate_intervals
        //     .borrow_mut()
        //     .insert(id, Rc::clone(&callback));
        // schedule_immediate_interval(owner, id);
    } else {
        let deadline = deadline_from_now(delay);
        #[cfg(debug_assertions)]
        tracing::trace!(
            target: trace_targets::TIMER,
            event = "set_interval_deadline",
            timer_id = id,
            deadline_ns = deadline.as_nanos() as u64,
            "interval deadline computed"
        );
        let timer = TimerNode::interval(
            id,
            deadline,
            delay,
            Rc::new(RefCell::new(Box::new(callback))),
        );
        current_thread().timers.borrow_mut().insert(timer);
        rearm_thread_timer();
    }

    IntervalHandle {
        id,
        owner,
        _local: Rc::new(()),
    }
}

/// Cancels an interval previously created by [`set_interval`].
///
/// # Panics
///
/// Panics if called from a different runtime thread than the one that created `handle`.
pub fn clear_interval(handle: &IntervalHandle) {
    #[cfg(debug_assertions)]
    tracing::trace!(
        target: trace_targets::TIMER,
        event = "clear_interval",
        timer_id = handle.id,
        "clearing interval"
    );
    clear_timer(handle.owner, handle.id);
}

/// Queues a future on the current runtime thread.
///
/// The future is scheduled immediately and can be awaited through the returned [`JoinHandle`].
///
/// The future will be driven to completion regardless or whether the join handle is polled or dropped, so this function
/// can be used as a convenient way to spawn detached async tasks on the current thread.
///
/// # Examples
///
/// ```
/// # let _ = || {
/// let handle = ruin_runtime::queue_future(async { 42usize });
/// # };
/// ```
///
/// # Panics
///
/// Panics if the runtime cannot initialize its state for the current thread.
pub fn queue_future<F>(future: F) -> JoinHandle<F::Output>
where
    F: Future + 'static,
    F::Output: 'static,
{
    #[cfg(debug_assertions)]
    tracing::trace!(
        target: trace_targets::ASYNC,
        event = "queue_future",
        "queueing local future"
    );
    let state = Rc::new(JoinState::new());
    let completion = Rc::clone(&state);
    let task = Rc::new(FutureTask {
        future: RefCell::new(Some(Box::pin(async move {
            let output = future.await;
            completion.complete(output);
        }))),
        queued: Cell::new(false),
    });

    task.schedule();

    JoinHandle { state }
}

/// Spawns a worker runtime thread.
///
/// `initial_task` is queued onto the worker as its first macrotask. `on_exit` runs on the parent
/// runtime thread after the worker shuts down.
///
/// # Panics
///
/// Panics if the worker thread or its driver cannot be created.
pub fn spawn_worker<Init, Exit>(initial_task: Init, on_exit: Exit) -> WorkerHandle
where
    Init: FnOnce() + Send + 'static,
    Exit: FnOnce() + 'static,
{
    tracing::debug!(
        target: trace_targets::RUNTIME,
        event = "spawn_worker",
        "spawning runtime worker thread"
    );
    let parent = current_thread();
    let (driver, notifier) = create_driver().expect("worker driver should initialize");
    let shared = Arc::new(ThreadShared::new(notifier));
    let handle = ThreadHandle {
        shared: Arc::clone(&shared),
    };
    let completion = Arc::new(WorkerCompletion {
        finished: AtomicBool::new(false),
        parent_event: parent.handle(),
    });

    parent.children.borrow_mut().push(ChildWorker {
        completion: Arc::clone(&completion),
        on_exit: Some(Box::new(on_exit)),
    });

    let worker_completion = Arc::clone(&completion);
    std::thread::Builder::new()
        .name("ruin-runtime-worker".into())
        .spawn(move || {
            install_thread(shared, driver, Some(worker_completion));
            queue_task(initial_task);
            run();
        })
        .expect("worker thread should spawn");

    WorkerHandle {
        thread: handle,
        completion,
    }
}

/// Runs the current runtime thread until no work, timers, child workers, or async operations
/// remain.
///
/// This is the main event loop entry point used by the proc-macro entry attributes.
///
/// # Panics
///
/// Panics if runtime initialization fails or if the underlying driver returns an unexpected error.
pub fn run() {
    let _span = tracing::debug_span!(
        target: trace_targets::RUNTIME,
        "runtime.run"
    )
    .entered();
    tracing::debug!(
        target: trace_targets::RUNTIME,
        event = "run_enter",
        "entering runtime event loop"
    );
    let _ = current_thread();

    loop {
        drain_all();

        let mut microtasks_run: u64 = 0;
        while let Some(task) = pop_microtask() {
            task();
            microtasks_run += 1;
            drain_all();
        }
        if microtasks_run >= MICROTASK_STARVATION_THRESHOLD {
            tracing::warn!(
                target: trace_targets::SCHEDULER,
                event = "microtask_starvation",
                count = microtasks_run,
                "microtask queue ran {microtasks_run} tasks in a single turn; macrotask handlers may be starved",
            );
        }

        if let Some(task) = pop_macrotask() {
            task();
            continue;
        }

        drain_all();

        if has_ready_work() {
            continue;
        }

        let state = current_thread();
        if !state.try_begin_shutdown() {
            continue;
        }

        drain_all();

        if has_ready_work() {
            state.shared.closing.store(false, Ordering::Release);
            continue;
        }

        if has_pending_timers() || state.has_live_children() || state.has_live_async_operations() {
            state.shared.closing.store(false, Ordering::Release);
            #[cfg(debug_assertions)]
            tracing::trace!(
                target: trace_targets::RUNTIME,
                event = "run_wait",
                pending_timers = has_pending_timers(),
                live_children = state.has_live_children(),
                live_async = state.has_live_async_operations(),
                "runtime waiting for more work"
            );
            state.driver.wait().expect("driver wait should succeed");
            continue;
        }

        // Atomically commit to exit: set `closed` while holding the remote
        // queue lock. `enqueue_macro` also checks `closed` under this same
        // lock, so there is no window in which a task can be accepted after
        // we decide to exit. If a task snuck in between the `has_ready_work`
        // check above and acquiring the lock, we abort and process it first.
        let committed = {
            let remote = lock_queue(&state.shared.remote_macrotasks);
            if remote.is_empty() {
                state.shared.closed.store(true, Ordering::Release);
                true
            } else {
                false
            }
        };

        if !committed {
            state.shared.closing.store(false, Ordering::Release);
            continue;
        }

        if let Some(completion) = &state.worker_completion {
            completion.finished.store(true, Ordering::Release);
            completion.parent_event.shared.notify();
        }

        state.shared.notify();
        tracing::debug!(
            target: trace_targets::RUNTIME,
            event = "run_exit",
            "runtime event loop exiting"
        );
        teardown_thread();
        return;
    }
}

/// Drains ready work on the current runtime thread without blocking for future work.
///
/// Unlike [`run`], this returns as soon as there are no immediately runnable
/// microtasks or macrotasks left. It is intended for host integrations that
/// need to re-enter the scheduler while an outer platform loop remains active.
pub fn run_until_stalled() {
    let _ = current_thread();

    loop {
        drain_all();

        let mut microtasks_run: u64 = 0;
        while let Some(task) = pop_microtask() {
            task();
            microtasks_run += 1;
            drain_all();
        }
        if microtasks_run >= MICROTASK_STARVATION_THRESHOLD {
            tracing::warn!(
                target: trace_targets::SCHEDULER,
                event = "microtask_starvation",
                count = microtasks_run,
                "microtask queue ran {microtasks_run} tasks in a single turn; macrotask handlers may be starved",
            );
        }

        if let Some(task) = pop_macrotask() {
            task();
            continue;
        }

        drain_all();

        if has_ready_work() {
            continue;
        }

        current_thread()
            .shared
            .closing
            .store(false, Ordering::Release);
        return;
    }
}

/// Drains already-queued work on the current runtime thread without polling the
/// driver for timers or I/O readiness.
///
/// This is intended for host integrations that need to flush application work
/// from inside a host callback without re-entering timer callbacks.
pub fn run_ready_tasks() {
    let _ = current_thread();

    loop {
        drain_remote_tasks();
        drain_completed_workers();

        let mut microtasks_run: u64 = 0;
        while let Some(task) = pop_microtask() {
            task();
            microtasks_run += 1;
            drain_remote_tasks();
            drain_completed_workers();
        }
        if microtasks_run >= MICROTASK_STARVATION_THRESHOLD {
            tracing::warn!(
                target: trace_targets::SCHEDULER,
                event = "microtask_starvation",
                count = microtasks_run,
                "microtask queue ran {microtasks_run} tasks in a single turn; macrotask handlers may be starved",
            );
        }

        if let Some(task) = pop_macrotask() {
            task();
            continue;
        }

        drain_remote_tasks();
        drain_completed_workers();

        if has_ready_work() {
            continue;
        }

        current_thread()
            .shared
            .closing
            .store(false, Ordering::Release);
        return;
    }
}

fn drain_all() {
    drain_driver_events();
    drain_remote_tasks();
    drain_completed_workers();
}

/// Returns a future that yields back to the runtime scheduler once.
pub fn yield_now() -> YieldNow {
    YieldNow { yielded: false }
}

impl ThreadHandle {
    /// Queues a macrotask onto this runtime thread.
    ///
    /// Returns `false` if the target thread is already closed.
    pub fn queue_task<F>(&self, task: F) -> bool
    where
        F: FnOnce() + Send + 'static,
    {
        let queued = self.shared.enqueue_macro(Box::new(task));
        #[cfg(debug_assertions)]
        tracing::trace!(
            target: trace_targets::SCHEDULER,
            event = "queue_remote_task",
            queue = "remote_macro",
            queued,
            "queueing remote macrotask"
        );
        queued
    }

    /// Returns `true` if the target runtime thread has shut down.
    pub fn is_closed(&self) -> bool {
        self.shared.closed.load(Ordering::Acquire)
    }

    #[allow(dead_code)]
    pub(crate) fn begin_async_operation(&self) {
        self.shared.pending_ops.fetch_add(1, Ordering::AcqRel);
    }

    #[allow(dead_code)]
    pub(crate) fn finish_async_operation(&self) {
        let previous = self.shared.pending_ops.fetch_sub(1, Ordering::AcqRel);
        debug_assert!(previous > 0, "async operation count underflow");
        self.shared.notify();
    }
}

impl WorkerHandle {
    /// Queues a macrotask onto the worker thread.
    ///
    /// Returns `false` if the worker has already shut down.
    pub fn queue_task<F>(&self, task: F) -> bool
    where
        F: FnOnce() + Send + 'static,
    {
        self.thread.queue_task(task)
    }

    /// Returns `true` once the worker thread has fully exited.
    pub fn is_finished(&self) -> bool {
        self.completion.finished.load(Ordering::Acquire)
    }

    /// Returns a generic [`ThreadHandle`] for the worker thread.
    pub fn thread(&self) -> ThreadHandle {
        self.thread.clone()
    }
}

impl<T> Future for JoinHandle<T> {
    type Output = T;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        self.state.poll(cx)
    }
}

impl Future for YieldNow {
    type Output = ();

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        if self.yielded {
            Poll::Ready(())
        } else {
            self.yielded = true;
            cx.waker().wake_by_ref();
            Poll::Pending
        }
    }
}

type ImmediateIntervals = RefCell<HashMap<usize, Rc<RefCell<Box<dyn FnMut()>>>>>;

struct ThreadState {
    driver: Driver,
    shared: Arc<ThreadShared>,
    worker_completion: Option<Arc<WorkerCompletion>>,
    local_microtasks: RefCell<LocalTaskQueue>,
    local_macrotasks: RefCell<MacroTaskQueue<MacroTask>>,
    timers: RefCell<TimerHeap>,
    /// Zero-delay intervals bypasses the timer heap entirely. Each entry
    /// re-enqueues itself as a macrotask on every turn.
    immediate_intervals: ImmediateIntervals,
    next_timer_id: Cell<usize>,
    children: RefCell<Vec<ChildWorker>>,
}

impl ThreadState {
    fn new(
        shared: Arc<ThreadShared>,
        driver: Driver,
        worker_completion: Option<Arc<WorkerCompletion>>,
    ) -> Self {
        Self {
            driver,
            shared,
            worker_completion,
            local_microtasks: RefCell::new(VecDeque::new()),
            local_macrotasks: RefCell::new(VecDeque::new()),
            timers: RefCell::new(TimerHeap::new()),
            immediate_intervals: RefCell::new(HashMap::new()),
            next_timer_id: Cell::new(1),
            children: RefCell::new(Vec::new()),
        }
    }

    fn handle(&self) -> ThreadHandle {
        ThreadHandle {
            shared: Arc::clone(&self.shared),
        }
    }

    fn has_live_children(&self) -> bool {
        !self.children.borrow().is_empty()
    }

    fn has_live_async_operations(&self) -> bool {
        self.shared.pending_ops.load(Ordering::Acquire) != 0
    }

    fn try_begin_shutdown(&self) -> bool {
        self.shared
            .closing
            .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
            .is_ok()
    }
}

struct ThreadShared {
    notifier: ThreadNotifier,
    // The microtask queue is strictly thread-local; only macrotasks may be
    // enqueued from remote threads, keeping the microtask queue free from
    // cross-thread interference.
    remote_macrotasks: Mutex<VecDeque<SendTask>>,
    pending_ops: AtomicUsize,
    closing: AtomicBool,
    closed: AtomicBool,
}

impl ThreadShared {
    fn new(notifier: ThreadNotifier) -> Self {
        Self {
            notifier,
            remote_macrotasks: Mutex::new(VecDeque::new()),
            pending_ops: AtomicUsize::new(0),
            closing: AtomicBool::new(false),
            closed: AtomicBool::new(false),
        }
    }

    fn enqueue_macro(&self, task: SendTask) -> bool {
        // Check `closed` under the queue lock so that the exit path can
        // atomically set `closed` while holding the same lock, eliminating
        // the window where a task is accepted but then stranded at shutdown.
        let mut queue = lock_queue(&self.remote_macrotasks);
        if self.closed.load(Ordering::Acquire) {
            return false;
        }
        queue.push_back(task);
        drop(queue);
        // Notify after releasing the lock. By this point `closed` is still
        // false (we verified under the lock) and teardown_thread has not run,
        // so the ring is guaranteed to be alive.
        self.notify();
        true
    }

    fn notify(&self) {
        if let Err(error) = self.notifier.notify() {
            // BrokenPipe is expected during shutdown (ring already closed).
            // Any other error is unexpected; log it rather than panicking so
            // the runtime continues to make progress.
            if error.kind() != io::ErrorKind::BrokenPipe {
                tracing::error!(
                    target: trace_targets::DRIVER,
                    event = "notify_error",
                    ?error,
                    "unexpected error sending thread notification"
                );
            }
        }
    }
}

struct ChildWorker {
    completion: Arc<WorkerCompletion>,
    on_exit: Option<LocalTask>,
}

struct WorkerCompletion {
    finished: AtomicBool,
    parent_event: ThreadHandle,
}

struct TimerNode {
    id: usize,
    deadline: Duration,
    kind: TimerKind,
}

enum TimerKind {
    Timeout(LocalTask),
    Interval {
        interval: Duration,
        callback: Rc<RefCell<Box<dyn FnMut() + 'static>>>,
    },
}

impl TimerNode {
    fn timeout(id: usize, deadline: Duration, callback: LocalTask) -> Self {
        Self {
            id,
            deadline,
            kind: TimerKind::Timeout(callback),
        }
    }

    fn interval(
        id: usize,
        deadline: Duration,
        interval: Duration,
        callback: Rc<RefCell<Box<dyn FnMut() + 'static>>>,
    ) -> Self {
        Self {
            id,
            deadline,
            kind: TimerKind::Interval { interval, callback },
        }
    }
}

struct TimerHeap {
    nodes: Vec<TimerNode>,
    positions: HashMap<usize, usize>,
}

impl TimerHeap {
    fn new() -> Self {
        Self {
            nodes: Vec::new(),
            positions: HashMap::new(),
        }
    }

    fn is_empty(&self) -> bool {
        self.nodes.is_empty()
    }

    fn peek_deadline(&self) -> Option<Duration> {
        self.nodes.first().map(|node| node.deadline)
    }

    fn insert(&mut self, node: TimerNode) {
        let index = self.nodes.len();
        self.positions.insert(node.id, index);
        self.nodes.push(node);
        self.sift_up(index);
    }

    fn remove(&mut self, id: usize) -> Option<TimerNode> {
        let index = *self.positions.get(&id)?;
        self.positions.remove(&id);

        let last = self.nodes.pop().expect("heap index should be valid");
        if index == self.nodes.len() {
            return Some(last);
        }

        let removed = std::mem::replace(&mut self.nodes[index], last);
        self.positions.insert(self.nodes[index].id, index);
        self.fix(index);
        Some(removed)
    }

    fn pop_min(&mut self) -> Option<TimerNode> {
        let id = self.nodes.first()?.id;
        self.remove(id)
    }

    fn pop_due(&mut self, now: Duration) -> Vec<TimerNode> {
        let mut due = Vec::new();
        while self.peek_deadline().is_some_and(|deadline| deadline <= now) {
            due.push(self.pop_min().expect("timer heap should contain a minimum"));
        }
        due
    }

    fn fix(&mut self, index: usize) {
        if index > 0 && self.less(index, parent(index)) {
            self.sift_up(index);
        } else {
            self.sift_down(index);
        }
    }

    fn sift_up(&mut self, mut index: usize) {
        while index > 0 {
            let parent = parent(index);
            if !self.less(index, parent) {
                break;
            }
            self.swap(index, parent);
            index = parent;
        }
    }

    fn sift_down(&mut self, mut index: usize) {
        loop {
            let left = index * 2 + 1;
            let right = left + 1;
            let mut smallest = index;

            if left < self.nodes.len() && self.less(left, smallest) {
                smallest = left;
            }
            if right < self.nodes.len() && self.less(right, smallest) {
                smallest = right;
            }
            if smallest == index {
                break;
            }

            self.swap(index, smallest);
            index = smallest;
        }
    }

    fn less(&self, lhs: usize, rhs: usize) -> bool {
        let left = &self.nodes[lhs];
        let right = &self.nodes[rhs];
        (left.deadline, left.id) < (right.deadline, right.id)
    }

    fn swap(&mut self, lhs: usize, rhs: usize) {
        self.nodes.swap(lhs, rhs);
        self.positions.insert(self.nodes[lhs].id, lhs);
        self.positions.insert(self.nodes[rhs].id, rhs);
    }
}

struct FutureTask {
    future: RefCell<Option<LocalBoxFuture>>,
    queued: Cell<bool>,
}

impl FutureTask {
    fn schedule(self: &Rc<Self>) {
        if self.queued.replace(true) {
            return;
        }

        let task = Rc::clone(self);
        queue_microtask(move || task.poll());
    }

    fn poll(self: Rc<Self>) {
        self.queued.set(false);

        let Some(mut future) = self.future.borrow_mut().take() else {
            return;
        };

        let waker = self.waker();
        let mut context = Context::from_waker(&waker);
        match future.as_mut().poll(&mut context) {
            Poll::Ready(()) => {}
            Poll::Pending => {
                *self.future.borrow_mut() = Some(future);
            }
        }
    }

    fn waker(self: &Rc<Self>) -> Waker {
        unsafe {
            Waker::from_raw(RawWaker::new(
                Rc::into_raw(Rc::clone(self)).cast::<()>(),
                &FUTURE_TASK_WAKER_VTABLE,
            ))
        }
    }
}

static FUTURE_TASK_WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(
    future_task_clone,
    future_task_wake,
    future_task_wake_by_ref,
    future_task_drop,
);

struct JoinState<T> {
    result: RefCell<Option<T>>,
    waker: RefCell<Option<Waker>>,
    ready: Cell<bool>,
}

impl<T> JoinState<T> {
    fn new() -> Self {
        Self {
            result: RefCell::new(None),
            waker: RefCell::new(None),
            ready: Cell::new(false),
        }
    }

    fn complete(&self, value: T) {
        *self.result.borrow_mut() = Some(value);
        self.ready.set(true);

        if let Some(waker) = self.waker.borrow_mut().take() {
            waker.wake();
        }
    }

    fn poll(&self, cx: &mut Context<'_>) -> Poll<T> {
        if self.ready.get() {
            return Poll::Ready(
                self.result
                    .borrow_mut()
                    .take()
                    .expect("join handle polled after completion"),
            );
        }

        *self.waker.borrow_mut() = Some(cx.waker().clone());
        Poll::Pending
    }
}

unsafe fn future_task_clone(data: *const ()) -> RawWaker {
    let task = unsafe { Rc::<FutureTask>::from_raw(data.cast::<FutureTask>()) };
    let clone = Rc::clone(&task);
    let _ = Rc::into_raw(task);
    RawWaker::new(Rc::into_raw(clone).cast::<()>(), &FUTURE_TASK_WAKER_VTABLE)
}

unsafe fn future_task_wake(data: *const ()) {
    let task = unsafe { Rc::<FutureTask>::from_raw(data.cast::<FutureTask>()) };
    task.schedule();
}

unsafe fn future_task_wake_by_ref(data: *const ()) {
    let task = unsafe { Rc::<FutureTask>::from_raw(data.cast::<FutureTask>()) };
    task.schedule();
    let _ = Rc::into_raw(task);
}

unsafe fn future_task_drop(data: *const ()) {
    drop(unsafe { Rc::<FutureTask>::from_raw(data.cast::<FutureTask>()) });
}

fn current_thread() -> &'static ThreadState {
    unsafe {
        if CURRENT_THREAD.is_null() {
            let (driver, notifier) = create_driver().expect("runtime driver should initialize");
            let shared = Arc::new(ThreadShared::new(notifier));
            let state = Box::new(ThreadState::new(shared, driver, None));
            let state = Box::into_raw(state);
            (*state).driver.bind_current_thread();
            CURRENT_THREAD = state;
        }

        &*CURRENT_THREAD
    }
}

fn current_thread_ptr() -> *const ThreadState {
    current_thread() as *const ThreadState
}

fn install_thread(
    shared: Arc<ThreadShared>,
    driver: Driver,
    worker_completion: Option<Arc<WorkerCompletion>>,
) {
    unsafe {
        debug_assert!(CURRENT_THREAD.is_null(), "thread runtime already installed");
        let state = Box::new(ThreadState::new(shared, driver, worker_completion));
        let state = Box::into_raw(state);
        (*state).driver.bind_current_thread();
        CURRENT_THREAD = state;
    }
}

fn teardown_thread() {
    unsafe {
        let state = CURRENT_THREAD;
        CURRENT_THREAD = ptr::null_mut();

        if !state.is_null() {
            (*state).driver.unbind_current_thread();
            drop(Box::from_raw(state));
        }
    }
}

fn drain_driver_events() {
    loop {
        let ready = current_thread()
            .driver
            .poll()
            .expect("driver poll should succeed");

        let Some(ready) = ready else {
            break;
        };

        let state = current_thread();
        if ready.wake {
            #[cfg(debug_assertions)]
            tracing::trace!(
                target: trace_targets::DRIVER,
                event = "drain_wake",
                "draining driver wake notifications"
            );
            let _ = state
                .driver
                .drain_wake()
                .expect("wake drain should succeed");
        }
        if ready.timer {
            #[cfg(debug_assertions)]
            tracing::trace!(
                target: trace_targets::TIMER,
                event = "drain_timer",
                "draining expired runtime timers"
            );
            let _ = state
                .driver
                .drain_timer()
                .expect("timer drain should succeed");
            dispatch_expired_timers();
        }
    }
}

fn drain_remote_tasks() {
    // Swap the entire remote queue under the lock and release immediately,
    // minimizing the time the lock is held and avoiding per-item allocation.
    let drained = {
        let mut remote = lock_queue(&current_thread().shared.remote_macrotasks);
        std::mem::take(&mut *remote)
    };

    if !drained.is_empty() {
        let mut local = current_thread().local_macrotasks.borrow_mut();
        for task in drained {
            // SendTask (Box<dyn FnOnce() + Send>) coerces to LocalTask
            // (Box<dyn FnOnce()>) by dropping the Send bound.
            let task: LocalTask = task;
            local.push_back(make_macro_task(task));
        }
    }
}

fn drain_completed_workers() {
    let state = current_thread();
    let mut exited = Vec::new();

    {
        let mut children = state.children.borrow_mut();
        let mut index = 0;
        while index < children.len() {
            if children[index].completion.finished.load(Ordering::Acquire) {
                let child = children.swap_remove(index);
                exited.push(child);
            } else {
                index += 1;
            }
        }
    }

    if exited.is_empty() {
        return;
    }

    let mut local = state.local_macrotasks.borrow_mut();
    for mut child in exited {
        if let Some(task) = child.on_exit.take() {
            local.push_back(make_macro_task(task));
        }
    }
}

fn pop_microtask() -> Option<LocalTask> {
    current_thread().local_microtasks.borrow_mut().pop_front()
}

fn pop_macrotask() -> Option<LocalTask> {
    current_thread()
        .local_macrotasks
        .borrow_mut()
        .pop_front()
        .map(|entry| {
            #[cfg(debug_assertions)]
            {
                let now = deadline_from_now(Duration::ZERO);
                let wait = now.saturating_sub(entry.queued_at);
                tracing::trace!(
                    target: trace_targets::SCHEDULER,
                    event = "macrotask_dequeued",
                    wait_ns = wait.as_nanos() as u64,
                    "macrotask dequeued after waiting in queue"
                );
            }
            entry.task
        })
}

fn push_local_macrotask(task: LocalTask) {
    current_thread()
        .local_macrotasks
        .borrow_mut()
        .push_back(make_macro_task(task));
}

fn make_macro_task(task: LocalTask) -> MacroTask {
    MacroTask {
        task,
        #[cfg(debug_assertions)]
        queued_at: deadline_from_now(Duration::ZERO),
    }
}

fn has_ready_work() -> bool {
    let state = current_thread();
    if !state.local_microtasks.borrow().is_empty() || !state.local_macrotasks.borrow().is_empty() {
        return true;
    }

    if !lock_queue(&state.shared.remote_macrotasks).is_empty() {
        return true;
    }

    false
}

fn has_pending_timers() -> bool {
    let state = current_thread();
    !state.timers.borrow().is_empty() || !state.immediate_intervals.borrow().is_empty()
}

fn allocate_timer_id() -> usize {
    let state = current_thread();
    let id = state.next_timer_id.get();
    let next = id.checked_add(1).expect("timer ID space exhausted");
    state.next_timer_id.set(next);
    id
}

fn clear_timer(owner: *const ThreadState, id: usize) {
    assert!(
        ptr::eq(current_thread_ptr(), owner),
        "timer handles must be cleared on their originating thread"
    );

    let state = current_thread();
    if state.timers.borrow_mut().remove(id).is_some() {
        rearm_thread_timer();
    } else {
        // May be a zero-delay immediate interval; removal is a no-op if absent.
        state.immediate_intervals.borrow_mut().remove(&id);
    }
}

// TODO: vibeshit got this completely wrong
// /// Enqueues one macrotask turn for a zero-delay interval.
// ///
// /// The macrotask checks whether the interval is still live (not cleared), fires
// /// the callback, and re-enqueues itself for the next turn.
// fn schedule_immediate_interval(owner: *const ThreadState, id: usize) {
//     push_local_macrotask(Box::new(move || {
//         let callback = current_thread()
//             .immediate_intervals
//             .borrow()
//             .get(&id)
//             .map(Rc::clone);
//         let Some(callback) = callback else {
//             return; // interval was cleared before this turn ran
//         };
//         (callback.borrow_mut())();
//         // Re-enqueue for the next turn if still live.
//         if current_thread()
//             .immediate_intervals
//             .borrow()
//             .contains_key(&id)
//         {
//             schedule_immediate_interval(owner, id);
//         }
//     }));
// }

fn dispatch_expired_timers() {
    let now = deadline_from_now(Duration::ZERO);
    let due = current_thread().timers.borrow_mut().pop_due(now);

    if due.is_empty() {
        rearm_thread_timer();
        return;
    }

    for timer in due {
        match timer.kind {
            TimerKind::Timeout(callback) => push_local_macrotask(callback),
            TimerKind::Interval { interval, callback } => {
                // Mirror JavaScript's setInterval semantics:
                //   1. Schedule the next tick BEFORE queuing the callback,
                //      so a slow callback does not shift the schedule.
                //   2. Fire at most once per dispatch regardless of how many
                //      ticks were missed; the interval simply drifts forward
                //      rather than back-filling a burst of callbacks.
                let next_deadline = timer
                    .deadline
                    .checked_add(interval)
                    .unwrap_or(Duration::MAX);
                let next_timer =
                    TimerNode::interval(timer.id, next_deadline, interval, Rc::clone(&callback));
                current_thread().timers.borrow_mut().insert(next_timer);

                push_local_macrotask(Box::new(move || {
                    (callback.borrow_mut())();
                }));
            }
        }
    }

    rearm_thread_timer();
}

fn rearm_thread_timer() {
    let deadline = current_thread().timers.borrow().peek_deadline();
    current_thread()
        .driver
        .rearm_timer(deadline)
        .expect("driver timer rearm should succeed");
}

fn deadline_from_now(delay: Duration) -> Duration {
    monotonic_now()
        .expect("monotonic clock should be available")
        .checked_add(delay)
        .unwrap_or(Duration::MAX)
}

const fn parent(index: usize) -> usize {
    (index - 1) / 2
}

fn lock_queue(queue: &Mutex<VecDeque<SendTask>>) -> MutexGuard<'_, VecDeque<SendTask>> {
    queue.lock().expect("runtime queue poisoned")
}

#[cfg(test)]
mod tests {
    use crate::op::completion::completion_for_current_thread;

    use super::{
        clear_interval, current_thread_handle, queue_future, queue_microtask, queue_task, run,
        set_interval, set_timeout, spawn_worker, yield_now,
    };
    use std::cell::{Cell, RefCell};
    use std::rc::Rc;
    use std::sync::{Arc, Mutex};
    use std::thread;
    use std::time::Duration;

    #[test]
    fn runtime_executes_local_and_remote_work() {
        let log = Arc::new(Mutex::new(Vec::<String>::new()));
        let main_handle = current_thread_handle();

        {
            let log = Arc::clone(&log);
            queue_task(move || log.lock().unwrap().push("main task".into()));
        }
        {
            let log = Arc::clone(&log);
            queue_microtask(move || log.lock().unwrap().push("main microtask".into()));
        }
        {
            let log = Arc::clone(&log);
            queue_future(async move {
                log.lock().unwrap().push("main future start".into());
                yield_now().await;
                log.lock().unwrap().push("main future end".into());
            });
        }
        {
            let log = Arc::clone(&log);
            set_timeout(Duration::from_millis(5), move || {
                log.lock().unwrap().push("main timeout".into());
            });
        }
        {
            let log = Arc::clone(&log);
            let handle_slot = Rc::new(RefCell::new(None));
            let handle_slot_clone = Rc::clone(&handle_slot);
            let tick_count = Rc::new(Cell::new(0usize));
            let tick_count_clone = Rc::clone(&tick_count);
            let interval = set_interval(Duration::from_millis(3), move || {
                let next = tick_count_clone.get() + 1;
                tick_count_clone.set(next);
                log.lock().unwrap().push(format!("main interval {next}"));
                if next == 2 {
                    let handle = handle_slot_clone.borrow_mut().take().unwrap();
                    clear_interval(&handle);
                }
            });
            *handle_slot.borrow_mut() = Some(interval);
        }

        {
            let worker_log = Arc::clone(&log);
            let exit_log = Arc::clone(&log);
            let main_handle_for_worker = main_handle.clone();
            spawn_worker(
                move || {
                    let log = Arc::clone(&worker_log);
                    queue_task({
                        let log = Arc::clone(&log);
                        move || log.lock().unwrap().push("worker task".into())
                    });
                    queue_microtask({
                        let log = Arc::clone(&log);
                        move || log.lock().unwrap().push("worker microtask".into())
                    });
                    queue_future({
                        let log = Arc::clone(&log);
                        async move {
                            log.lock().unwrap().push("worker future start".into());
                            yield_now().await;
                            log.lock().unwrap().push("worker future end".into());
                        }
                    });
                    set_timeout(Duration::from_millis(7), move || {
                        main_handle_for_worker.queue_task({
                            let log = Arc::clone(&log);
                            move || log.lock().unwrap().push("worker timeout to main".into())
                        });
                    });
                },
                {
                    let log = Arc::clone(&exit_log);
                    move || log.lock().unwrap().push("worker exit".into())
                },
            );
        }

        run();

        let log = log.lock().unwrap();
        assert!(log.iter().any(|entry| entry == "main task"));
        assert!(log.iter().any(|entry| entry == "main microtask"));
        assert!(log.iter().any(|entry| entry == "main future start"));
        assert!(log.iter().any(|entry| entry == "main future end"));
        assert!(log.iter().any(|entry| entry == "main timeout"));
        assert!(log.iter().any(|entry| entry == "main interval 1"));
        assert!(log.iter().any(|entry| entry == "main interval 2"));
        assert!(log.iter().any(|entry| entry == "worker task"));
        assert!(log.iter().any(|entry| entry == "worker microtask"));
        assert!(log.iter().any(|entry| entry == "worker future start"));
        assert!(log.iter().any(|entry| entry == "worker future end"));
        assert!(log.iter().any(|entry| entry == "worker timeout to main"));
        assert!(log.iter().any(|entry| entry == "worker exit"));
    }

    #[test]
    fn runtime_waits_for_cross_thread_operation_completion() {
        let observed = Arc::new(Mutex::new(None::<usize>));

        {
            let observed = Arc::clone(&observed);
            queue_task(move || {
                let (completion, source) = completion_for_current_thread::<usize>();

                thread::spawn(move || {
                    source.complete(7);
                });

                queue_future(async move {
                    let value = completion.await;
                    *observed.lock().unwrap() = Some(value);
                });
            });
        }

        run();

        assert_eq!(*observed.lock().unwrap(), Some(7));
    }

    #[test]
    fn zero_interval_fires_once_per_turn_without_spinning() {
        // set_interval(Duration::ZERO, ..) must not busy-spin the event loop.
        // Each tick is one macrotask turn; we verify the count advances across
        // turns by awaiting a sleep between checks.
        let count = Rc::new(Cell::new(0usize));
        let count_clone = Rc::clone(&count);
        let handle_slot: Rc<RefCell<Option<super::IntervalHandle>>> = Rc::new(RefCell::new(None));
        let handle_slot_clone = Rc::clone(&handle_slot);

        let handle = set_interval(Duration::ZERO, move || {
            let next = count_clone.get() + 1;
            count_clone.set(next);
            if next == 5 {
                let handle = handle_slot_clone.borrow_mut().take().unwrap();
                clear_interval(&handle);
            }
        });
        *handle_slot.borrow_mut() = Some(handle);

        run();

        assert_eq!(count.get(), 5);
    }
}
