#![allow(dead_code)]

use rquickjs::{
    class::{JsClass, Readable, Trace, Tracer},
    Class, Context, JsLifetime, Null, Runtime,
};
use std::sync::Mutex;

static VALIDATE: Mutex<(bool, bool, bool)> = Mutex::new((false, false, false));

pub struct A;

impl<'js> Trace<'js> for A {
    fn trace(&self, _tracer: Tracer<'_, 'js>) {
        VALIDATE.lock().unwrap().0 = true;
    }
}

pub struct B;

impl<'js> Trace<'js> for B {
    fn trace(&self, _tracer: Tracer<'_, 'js>) {
        VALIDATE.lock().unwrap().1 = true;
    }
}

pub struct C;

impl<'js> Trace<'js> for C {
    fn trace(&self, _tracer: Tracer<'_, 'js>) {
        VALIDATE.lock().unwrap().2 = true;
    }
}

#[derive(Trace, JsLifetime)]
pub struct TraceStruct {
    a: A,
    #[qjs(skip_trace)]
    b: B,
    c: C,
}

impl<'js> JsClass<'js> for TraceStruct {
    const NAME: &'static str = "TraceStruct";

    type Mutable = Readable;

    fn prototype(_ctx: &rquickjs::Ctx<'js>) -> rquickjs::Result<Option<rquickjs::Object<'js>>> {
        Ok(None)
    }

    fn constructor(
        _ctx: &rquickjs::Ctx<'js>,
    ) -> rquickjs::Result<Option<rquickjs::function::Constructor<'js>>> {
        Ok(None)
    }
}

#[derive(Trace, JsLifetime)]
pub enum TraceEnum {
    A(A),
    B(#[qjs(skip_trace)] B),
    C,
}

impl<'js> JsClass<'js> for TraceEnum {
    const NAME: &'static str = "TraceEnum";

    type Mutable = Readable;

    fn prototype(_ctx: &rquickjs::Ctx<'js>) -> rquickjs::Result<Option<rquickjs::Object<'js>>> {
        Ok(None)
    }

    fn constructor(
        _ctx: &rquickjs::Ctx<'js>,
    ) -> rquickjs::Result<Option<rquickjs::function::Constructor<'js>>> {
        Ok(None)
    }
}

pub fn main() {
    let rt = Runtime::new().unwrap();
    let ctx = Context::full(&rt).unwrap();

    ctx.with(|ctx| {
        let cls = Class::instance(ctx.clone(), TraceStruct { a: A, b: B, c: C }).unwrap();
        ctx.globals().set("t", cls).unwrap();
    });

    rt.run_gc();

    assert_eq!(*VALIDATE.lock().unwrap(), (true, false, true));

    ctx.with(|ctx| {
        ctx.globals().set("t", Null).unwrap();
    });

    rt.run_gc();

    *VALIDATE.lock().unwrap() = (false, false, false);

    ctx.with(|ctx| {
        let cls = Class::instance(ctx.clone(), TraceEnum::A(A)).unwrap();
        ctx.globals().set("t", cls).unwrap();
    });

    rt.run_gc();
    assert_eq!(*VALIDATE.lock().unwrap(), (true, false, false));
}
