#[allow(unused_imports)]
use napi::bindgen_prelude::{JsObjectValue, ToNapiValue, Unknown, Object};
#[allow(unused_imports)]
use napi::JsValue;

fn nodecontext_to_js_object<'e>(
    env: &'e napi::Env,
    ctx: &{{ core_crate }}::visitor::NodeContext,
) -> napi::Result<napi::bindgen_prelude::Object<'e>> {
    let mut obj = napi::bindgen_prelude::Object::new(env)?;
    obj.set_named_property("nodeType", env.create_string(&format!("{:?}", ctx.node_type))?)?;
    obj.set_named_property("tagName", env.create_string(&ctx.tag_name)?)?;
    obj.set_named_property("depth", env.create_uint32(ctx.depth as u32)?)?;
    obj.set_named_property("indexInParent", env.create_uint32(ctx.index_in_parent as u32)?)?;
    obj.set_named_property("isInline", ctx.is_inline)?;
    let parent_tag = match &ctx.parent_tag {
        Some(s) => env.create_string(s)?.to_unknown(),
        None => {
            // SAFETY: napi_get_null returns a valid napi_value for the given env.
            let raw = unsafe { napi::bindgen_prelude::ToNapiValue::to_napi_value(env.raw(), napi::bindgen_prelude::Null)? };
            unsafe { napi::bindgen_prelude::Unknown::from_raw_unchecked(env.raw(), raw) }
        }
    };
    obj.set_named_property("parentTag", parent_tag)?;
    let mut attrs = napi::bindgen_prelude::Object::new(env)?;
    for (k, v) in &ctx.attributes {
        attrs.set_named_property(k, env.create_string(v)?)?;
    }
    obj.set_named_property("attributes", attrs)?;
    Ok(obj)
}

pub struct {{ struct_name }} {
    obj: napi::bindgen_prelude::Object<'static>,
    env: napi::Env,
}

impl std::fmt::Debug for {{ struct_name }} {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{{ struct_name }}")
    }
}

impl {{ struct_name }} {
    pub fn new(js_obj: napi::bindgen_prelude::Object<'_>) -> Self {
        // Extract and cache the napi_env from the Object BEFORE transmuting.
        // We do this once and cache it to avoid repeated unsafe transmute_copy calls.
        // SAFETY: Object<'_> is 3 pointer-sized words; first word is napi_env.
        let env = unsafe {
            let raw: [*mut std::ffi::c_void; 3] = std::mem::transmute_copy(&js_obj);
            napi::Env::from_raw(raw[0] as napi::sys::napi_env)
        };
        // SAFETY: The JS object is owned by the Node.js runtime and lives for
        // the duration of the enclosing #[napi] call. The bridge is only used
        // synchronously during that same call, so 'static is safe here.
        let obj: napi::bindgen_prelude::Object<'static> = unsafe { std::mem::transmute(js_obj) };
        Self { obj, env }
    }

    fn env(&self) -> napi::Env {
        self.env
    }
}

// SAFETY: `napi::bindgen_prelude::Object<'static>` and `napi::Env` are not
// automatically `Send` because they wrap raw V8/Node.js handles tied to a
// specific isolate/thread. This bridge is only ever invoked synchronously from
// the same thread that constructed it (the #[napi] call frame) — the `Arc<Mutex>`
// wrapper ensures exclusive access and the caller must not move the owning options
// struct across threads while the bridge is alive. Misuse (calling convert() from
// a different thread than the one that created the visitor) is the caller's
// responsibility and will trigger undefined behaviour in the V8 runtime.
unsafe impl Send for {{ struct_name }} {}
// SAFETY: see Send impl above.
unsafe impl Sync for {{ struct_name }} {}

impl {{ trait_path }} for {{ struct_name }} {
{{ method_impls }}
}
