#![cfg(not(target_os = "macos"))]

use std::{
    collections::{HashMap, HashSet},
    ffi::{CStr, CString, c_char, c_void},
    fmt,
    path::{Path, PathBuf},
    sync::{Arc, Mutex},
};

use crate::audio::io::AudioIO;
use crate::message::{Lv2ControlPortInfo, Lv2PluginState, Lv2StatePortValue, Lv2StateProperty};
use crate::midi::io::MidiEvent;
use crate::mutex::UnsafeMutex;
use lilv::{World, instance::ActiveInstance, node::Node, plugin::Plugin};
use lv2_raw::{
    LV2_ATOM__DOUBLE, LV2_ATOM__FRAMETIME, LV2_ATOM__INT, LV2_ATOM__LONG, LV2_ATOM__OBJECT,
    LV2_ATOM__SEQUENCE, LV2_URID__MAP, LV2_URID__UNMAP, LV2Atom, LV2AtomDouble, LV2AtomEvent,
    LV2AtomLong, LV2AtomObjectBody, LV2AtomPropertyBody, LV2AtomSequence, LV2AtomSequenceBody,
    LV2Feature, LV2Urid, LV2UridMap, LV2UridMapHandle, lv2_atom_pad_size,
    lv2_atom_sequence_append_event, lv2_atom_sequence_begin, lv2_atom_sequence_is_end,
    lv2_atom_sequence_next,
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Lv2PluginInfo {
    pub uri: String,
    pub name: String,
    pub class_label: String,
    pub bundle_uri: String,
    pub required_features: Vec<String>,
    pub audio_inputs: usize,
    pub audio_outputs: usize,
    pub midi_inputs: usize,
    pub midi_outputs: usize,
}

pub struct Lv2Host {
    world: World,
    sample_rate: f64,
    loaded_plugins: HashMap<String, LoadedPlugin>,
}

#[derive(Debug, Clone, Copy, Default)]
pub struct Lv2TransportInfo {
    pub transport_sample: usize,
    pub playing: bool,
    pub bpm: f64,
    pub tsig_num: u32,
    pub tsig_denom: u32,
}

type Lv2WorkerStatus = u32;
const LV2_WORKER_SUCCESS: Lv2WorkerStatus = 0;
const LV2_WORKER_ERR_UNKNOWN: Lv2WorkerStatus = 1;
const LV2_WORKER__SCHEDULE: &str = "http://lv2plug.in/ns/ext/worker#schedule";
const LV2_WORKER__INTERFACE: &str = "http://lv2plug.in/ns/ext/worker#interface";

// Midnam interface
const LV2_MIDNAM__INTERFACE: &str = "http://ardour.org/lv2/midnam#interface";

#[repr(C)]
struct Lv2MidnamInterface {
    midnam: Option<unsafe extern "C" fn(instance: Lv2Handle) -> *mut c_char>,
    model: Option<unsafe extern "C" fn(instance: Lv2Handle) -> *mut c_char>,
    free: Option<unsafe extern "C" fn(ptr: *mut c_char)>,
}

#[repr(C)]
struct Lv2WorkerSchedule {
    handle: *mut c_void,
    schedule_work:
        Option<unsafe extern "C" fn(handle: *mut c_void, size: u32, data: *const c_void) -> u32>,
}

type Lv2WorkerRespondFunc =
    Option<unsafe extern "C" fn(handle: *mut c_void, size: u32, data: *const c_void) -> u32>;

#[repr(C)]
struct Lv2WorkerInterface {
    work: Option<
        unsafe extern "C" fn(
            handle: *mut c_void,
            respond: Lv2WorkerRespondFunc,
            respond_handle: *mut c_void,
            size: u32,
            data: *const c_void,
        ) -> u32,
    >,
    work_response:
        Option<unsafe extern "C" fn(handle: *mut c_void, size: u32, data: *const c_void) -> u32>,
    end_run: Option<unsafe extern "C" fn(handle: *mut c_void)>,
}

struct WorkerScheduleState {
    jobs: UnsafeMutex<Vec<Vec<u8>>>,
    responses: UnsafeMutex<Vec<Vec<u8>>>,
}

struct WorkerFeature {
    _uri: CString,
    _schedule: Box<Lv2WorkerSchedule>,
    feature: LV2Feature,
    state: Box<WorkerScheduleState>,
}

unsafe impl Send for WorkerFeature {}

#[derive(Clone, Copy)]
enum PortBinding {
    AudioInput(usize),
    AudioOutput(usize),
    AtomInput(usize),
    AtomOutput(usize),
    Scalar(usize),
}

pub struct Lv2Processor {
    uri: String,
    plugin_name: String,
    sample_rate: f64,
    instance: Option<ActiveInstance>,
    _world: World,
    _urid_feature: UridMapFeature,
    _state_path_feature: StatePathFeature,
    _instantiate_features: InstantiateFeatureSet,
    port_bindings: Vec<PortBinding>,
    scalar_values: Arc<Mutex<Vec<f32>>>,
    audio_inputs: Vec<Arc<AudioIO>>,
    audio_outputs: Vec<Arc<AudioIO>>,
    main_audio_inputs: usize,
    main_audio_outputs: usize,
    atom_inputs: Vec<AtomBuffer>,
    atom_outputs: Vec<AtomBuffer>,
    atom_sequence_urid: LV2Urid,
    atom_object_urid: LV2Urid,
    atom_long_urid: LV2Urid,
    atom_double_urid: LV2Urid,
    atom_frame_time_urid: LV2Urid,
    midi_event_urid: LV2Urid,
    time_position_urid: LV2Urid,
    time_frame_urid: LV2Urid,
    time_speed_urid: LV2Urid,
    time_bpm_urid: LV2Urid,
    time_bar_urid: LV2Urid,
    time_bar_beat_urid: LV2Urid,
    time_beats_per_bar_urid: LV2Urid,
    time_beat_unit_urid: LV2Urid,
    midi_inputs: usize,
    midi_outputs: usize,
    has_worker_interface: bool,
    control_ports: Vec<ControlPortInfo>,
    midnam_note_names: UnsafeMutex<HashMap<u8, String>>,
}

struct LoadedPlugin {
    instance: ActiveInstance,
    _urid_feature: UridMapFeature,
    _state_path_feature: StatePathFeature,
    _instantiate_features: InstantiateFeatureSet,
}

#[derive(Default)]
struct UridMapState {
    next_urid: LV2Urid,
    by_uri: HashMap<String, LV2Urid>,
    by_urid: HashMap<LV2Urid, CString>,
}

struct UridMapFeature {
    _map_uri: CString,
    _unmap_uri: CString,
    map_feature: LV2Feature,
    unmap_feature: LV2Feature,
    _map: Box<LV2UridMap>,
    _unmap: Box<LV2UridUnmap>,
    _state: Box<Mutex<UridMapState>>,
}

unsafe impl Send for UridMapFeature {}

#[repr(C)]
struct LV2UridUnmap {
    handle: LV2UridMapHandle,
    unmap: extern "C" fn(handle: LV2UridMapHandle, urid: LV2Urid) -> *const c_char,
}

struct InstantiateFeatureSet {
    _feature_uris: Vec<CString>,
    features: Vec<LV2Feature>,
    _worker_feature: WorkerFeature,
    _option_values: Vec<u32>,
    _options: Vec<LV2OptionsOption>,
    _flag_feature_data: Box<u8>,
}

unsafe impl Send for InstantiateFeatureSet {}

#[repr(C)]
#[derive(Clone, Copy)]
struct LV2OptionsOption {
    context: u32,
    subject: u32,
    key: u32,
    size: u32,
    type_: u32,
    value: *const c_void,
}

#[derive(Debug, Clone)]
struct ControlPortInfo {
    index: u32,
    name: String,
    min: f32,
    max: f32,
}

struct AtomBuffer {
    words: Vec<u64>,
}

impl AtomBuffer {
    fn new(len_bytes: usize) -> Self {
        let words = len_bytes.div_ceil(std::mem::size_of::<u64>()).max(1);
        Self {
            words: vec![0; words],
        }
    }

    fn bytes_mut(&mut self) -> &mut [u8] {
        let full_len = self.words.len() * std::mem::size_of::<u64>();
        let raw = self.words.as_mut_ptr().cast::<u8>();
        unsafe { std::slice::from_raw_parts_mut(raw, full_len) }
    }

    fn ptr_mut(&mut self) -> *mut u8 {
        self.words.as_mut_ptr().cast::<u8>()
    }
}

#[derive(Debug)]
struct RawStateProperty {
    key: u32,
    type_: u32,
    flags: u32,
    value: Vec<u8>,
}

struct StateSaveContext {
    properties: Vec<RawStateProperty>,
}

struct StateRestoreContext {
    properties: Vec<RawStateProperty>,
    by_key: HashMap<u32, usize>,
}

type Lv2Handle = *mut c_void;
type Lv2StateHandle = *mut c_void;
type Lv2StateStatus = u32;
type Lv2StateStoreFn = Option<
    unsafe extern "C" fn(
        handle: Lv2StateHandle,
        key: u32,
        value: *const c_void,
        size: usize,
        type_: u32,
        flags: u32,
    ) -> Lv2StateStatus,
>;
type Lv2StateRetrieveFn = Option<
    unsafe extern "C" fn(
        handle: Lv2StateHandle,
        key: u32,
        size: *mut usize,
        type_: *mut u32,
        flags: *mut u32,
    ) -> *const c_void,
>;
const LV2_STATE_STATUS_SUCCESS: Lv2StateStatus = 0;
const LV2_STATE_STATUS_ERR_NO_PROPERTY: Lv2StateStatus = 5;

#[repr(C)]
struct Lv2StateInterface {
    save: Option<
        unsafe extern "C" fn(
            instance: Lv2Handle,
            store: Lv2StateStoreFn,
            handle: Lv2StateHandle,
            flags: u32,
            features: *const *const LV2Feature,
        ) -> Lv2StateStatus,
    >,
    restore: Option<
        unsafe extern "C" fn(
            instance: Lv2Handle,
            retrieve: Lv2StateRetrieveFn,
            handle: Lv2StateHandle,
            flags: u32,
            features: *const *const LV2Feature,
        ) -> Lv2StateStatus,
    >,
}

#[repr(C)]
struct Lv2StateMapPath {
    handle: *mut c_void,
    abstract_path: Option<extern "C" fn(*mut c_void, *const c_char) -> *mut c_char>,
    absolute_path: Option<extern "C" fn(*mut c_void, *const c_char) -> *mut c_char>,
}

#[repr(C)]
struct Lv2StateMakePath {
    handle: *mut c_void,
    path: Option<extern "C" fn(*mut c_void, *const c_char) -> *mut c_char>,
}

#[repr(C)]
struct Lv2StateFreePath {
    handle: *mut c_void,
    free_path: Option<extern "C" fn(*mut c_void, *mut c_char)>,
}

#[derive(Default)]
struct StatePathContext {
    base_dir: PathBuf,
    copy_counter: u64,
}

struct StatePathFeature {
    _map_uri: CString,
    _make_uri: CString,
    _free_uri: CString,
    _map: Box<Lv2StateMapPath>,
    _make: Box<Lv2StateMakePath>,
    _free: Box<Lv2StateFreePath>,
    map_feature: LV2Feature,
    make_feature: LV2Feature,
    free_feature: LV2Feature,
    _context: Box<Mutex<StatePathContext>>,
}

unsafe impl Send for StatePathFeature {}

const LV2_STATE_INTERFACE_URI: &str = "http://lv2plug.in/ns/ext/state#interface";
const LV2_STATE_MAP_PATH_URI: &str = "http://lv2plug.in/ns/ext/state#mapPath";
const LV2_STATE_MAKE_PATH_URI: &str = "http://lv2plug.in/ns/ext/state#makePath";
const LV2_STATE_FREE_PATH_URI: &str = "http://lv2plug.in/ns/ext/state#freePath";
const LV2_OPTIONS__OPTIONS: &str = "http://lv2plug.in/ns/ext/options#options";
const LV2_BUF_SIZE__BOUNDED_BLOCK_LENGTH: &str =
    "http://lv2plug.in/ns/ext/buf-size#boundedBlockLength";
const LV2_BUF_SIZE__MIN_BLOCK_LENGTH: &str = "http://lv2plug.in/ns/ext/buf-size#minBlockLength";
const LV2_BUF_SIZE__MAX_BLOCK_LENGTH: &str = "http://lv2plug.in/ns/ext/buf-size#maxBlockLength";
const LV2_BUF_SIZE__NOMINAL_BLOCK_LENGTH: &str =
    "http://lv2plug.in/ns/ext/buf-size#nominalBlockLength";
const LV2_URID__MAP_URI_TYPO_COMPAT: &str = "http://lv2plug.in/ns//ext/urid#map";

const LV2_ATOM_BUFFER_BYTES: usize = 8192;

impl fmt::Debug for Lv2Processor {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Lv2Processor")
            .field("uri", &self.uri)
            .field("audio_inputs", &self.audio_inputs.len())
            .field("audio_outputs", &self.audio_outputs.len())
            .field("main_audio_inputs", &self.main_audio_inputs)
            .field("main_audio_outputs", &self.main_audio_outputs)
            .finish()
    }
}

impl Lv2Processor {
    pub fn new(sample_rate: f64, buffer_size: usize, uri: &str) -> Result<Self, String> {
        let world = World::new();
        world.load_all();

        let uri_node = world.new_uri(uri);
        let plugin = world
            .plugins()
            .plugin(&uri_node)
            .ok_or_else(|| format!("Plugin not found for URI: {uri}"))?;
        if !plugin.verify() {
            return Err(format!("Plugin failed verification: {uri}"));
        }

        let mut urid_feature = UridMapFeature::new()?;
        let mut state_path_feature = StatePathFeature::new(default_state_base_dir());
        let (instance, instantiate_features) = instantiate_plugin(
            &plugin,
            sample_rate,
            uri,
            &mut urid_feature,
            &mut state_path_feature,
        )?;
        let active_instance = unsafe { instance.activate() };

        let input_port = world.new_uri("http://lv2plug.in/ns/lv2core#InputPort");
        let output_port = world.new_uri("http://lv2plug.in/ns/lv2core#OutputPort");
        let audio_port = world.new_uri("http://lv2plug.in/ns/lv2core#AudioPort");
        let control_port = world.new_uri("http://lv2plug.in/ns/lv2core#ControlPort");
        let toggled_property = world.new_uri("http://lv2plug.in/ns/lv2core#toggled");
        let integer_property = world.new_uri("http://lv2plug.in/ns/lv2core#integer");
        let enumeration_property = world.new_uri("http://lv2plug.in/ns/lv2core#enumeration");
        let port_group_predicate = world.new_uri("http://lv2plug.in/ns/ext/port-groups#group");
        let main_input_group_predicate =
            world.new_uri("http://lv2plug.in/ns/ext/port-groups#mainInput");
        let main_output_group_predicate =
            world.new_uri("http://lv2plug.in/ns/ext/port-groups#mainOutput");
        let atom_port = world.new_uri("http://lv2plug.in/ns/ext/atom#AtomPort");
        let event_port = world.new_uri("http://lv2plug.in/ns/ext/event#EventPort");

        let ports_count = plugin.ports_count();
        let mut port_bindings = vec![PortBinding::Scalar(0); ports_count];
        let mut scalar_values = vec![0.0_f32; ports_count.max(1)];
        let mut audio_inputs: Vec<Arc<AudioIO>> = vec![];
        let mut audio_outputs: Vec<Arc<AudioIO>> = vec![];
        let mut atom_inputs: Vec<AtomBuffer> = vec![];
        let mut atom_outputs: Vec<AtomBuffer> = vec![];
        let mut midi_inputs = 0_usize;
        let mut midi_outputs = 0_usize;
        let mut control_ports = vec![];
        let has_worker_interface = plugin.has_extension_data(&world.new_uri(LV2_WORKER__INTERFACE));
        let atom_sequence_urid = urid_feature.map_uri(LV2_ATOM__SEQUENCE);
        let atom_object_urid = urid_feature.map_uri(LV2_ATOM__OBJECT);
        let atom_long_urid = urid_feature.map_uri(LV2_ATOM__LONG);
        let atom_double_urid = urid_feature.map_uri(LV2_ATOM__DOUBLE);
        let atom_frame_time_urid = urid_feature.map_uri(LV2_ATOM__FRAMETIME);
        let midi_event_urid = urid_feature.map_uri(lv2_raw::LV2_MIDI__MIDIEVENT);
        let time_position_urid = urid_feature.map_uri(lv2_raw::LV2_TIME__POSITION);
        let time_frame_urid = urid_feature.map_uri(lv2_raw::LV2_TIME__FRAME);
        let time_speed_urid = urid_feature.map_uri(lv2_raw::LV2_TIME__SPEED);
        let time_bpm_urid = urid_feature.map_uri(lv2_raw::LV2_TIME__BEATSPERMINUTE);
        let time_bar_urid = urid_feature.map_uri(lv2_raw::LV2_TIME__BAR);
        let time_bar_beat_urid = urid_feature.map_uri(lv2_raw::LV2_TIME__BARBEAT);
        let time_beats_per_bar_urid = urid_feature.map_uri(lv2_raw::LV2_TIME__BEATSPERBAR);
        let time_beat_unit_urid = urid_feature.map_uri(lv2_raw::LV2_TIME__BEATUNIT);
        let declared_audio_inputs = plugin
            .iter_ports()
            .filter(|port| port.is_a(&audio_port) && port.is_a(&input_port))
            .count();
        let declared_audio_outputs = plugin
            .iter_ports()
            .filter(|port| port.is_a(&audio_port) && port.is_a(&output_port))
            .count();
        let main_audio_inputs = count_main_audio_ports(
            &plugin,
            &main_input_group_predicate,
            &port_group_predicate,
            &audio_port,
            &input_port,
        )
        .unwrap_or(declared_audio_inputs);
        let main_audio_outputs = count_main_audio_ports(
            &plugin,
            &main_output_group_predicate,
            &port_group_predicate,
            &audio_port,
            &output_port,
        )
        .unwrap_or(declared_audio_outputs);

        for port in plugin.iter_ports() {
            let index = port.index();
            let is_audio = port.is_a(&audio_port);
            let is_control = port.is_a(&control_port);
            let is_atom = port.is_a(&atom_port) || port.is_a(&event_port);
            let is_input = port.is_a(&input_port);
            let is_output = port.is_a(&output_port);

            if is_audio && is_input {
                let channel = audio_inputs.len();
                audio_inputs.push(Arc::new(AudioIO::new(buffer_size)));
                port_bindings[index] = PortBinding::AudioInput(channel);
            } else if is_audio && is_output {
                let channel = audio_outputs.len();
                audio_outputs.push(Arc::new(AudioIO::new(buffer_size)));
                port_bindings[index] = PortBinding::AudioOutput(channel);
            } else if is_atom && is_input {
                midi_inputs += 1;
                let atom_idx = atom_inputs.len();
                let mut buffer = AtomBuffer::new(LV2_ATOM_BUFFER_BYTES);
                prepare_empty_atom_sequence(
                    buffer.bytes_mut(),
                    atom_sequence_urid,
                    atom_frame_time_urid,
                );
                atom_inputs.push(buffer);
                port_bindings[index] = PortBinding::AtomInput(atom_idx);
            } else if is_atom && is_output {
                midi_outputs += 1;
                let atom_idx = atom_outputs.len();
                let mut buffer = AtomBuffer::new(LV2_ATOM_BUFFER_BYTES);
                prepare_output_atom_sequence(
                    buffer.bytes_mut(),
                    atom_sequence_urid,
                    atom_frame_time_urid,
                );
                atom_outputs.push(buffer);
                port_bindings[index] = PortBinding::AtomOutput(atom_idx);
            } else {
                let range = port.range();
                let min = range
                    .minimum
                    .as_ref()
                    .and_then(lv2_node_to_f32)
                    .unwrap_or(0.0);
                let max = range
                    .maximum
                    .as_ref()
                    .and_then(lv2_node_to_f32)
                    .unwrap_or(1.0);
                let explicit_default = range.default.as_ref().and_then(lv2_node_to_f32);
                let is_toggled = is_control && is_input && port.has_property(&toggled_property);
                let is_discrete = is_control
                    && is_input
                    && (port.has_property(&integer_property)
                        || port.has_property(&enumeration_property));
                let (fallback_default, fallback_reason) =
                    infer_missing_control_default(min, max, is_toggled, is_discrete);
                let default_value = explicit_default.unwrap_or(fallback_default);
                scalar_values[index] = default_value;
                port_bindings[index] = PortBinding::Scalar(index);

                if explicit_default.is_none()
                    && is_control
                    && is_input
                    && std::env::var_os("MAOLAN_TRACE_LV2_DEFAULTS").is_some()
                {
                    let symbol = port
                        .symbol()
                        .and_then(|node| node.as_str().map(str::to_string))
                        .unwrap_or_else(|| format!("port_{index}"));
                    let _ = (uri, index, symbol, min, max, default_value, fallback_reason);
                }

                if is_control && is_input {
                    let mut min = min;
                    let mut max = max;
                    if !matches!(min.partial_cmp(&max), Some(std::cmp::Ordering::Less)) {
                        min = default_value - 1.0;
                        max = default_value + 1.0;
                    }
                    let fallback_name = format!("Port {}", index);
                    let name = port
                        .name()
                        .and_then(|node| node.as_str().map(str::to_string))
                        .or_else(|| {
                            port.symbol()
                                .and_then(|node| node.as_str().map(str::to_string))
                        })
                        .unwrap_or(fallback_name);
                    control_ports.push(ControlPortInfo {
                        index: index as u32,
                        name,
                        min,
                        max,
                    });
                }
            }
        }

        let mut processor = Self {
            uri: uri.to_string(),
            plugin_name: plugin
                .name()
                .as_str()
                .map(str::to_string)
                .unwrap_or_else(|| uri.to_string()),
            sample_rate,
            instance: Some(active_instance),
            _world: world,
            _urid_feature: urid_feature,
            _state_path_feature: state_path_feature,
            _instantiate_features: instantiate_features,
            port_bindings,
            scalar_values: Arc::new(Mutex::new(scalar_values)),
            audio_inputs,
            audio_outputs,
            main_audio_inputs,
            main_audio_outputs,
            atom_inputs,
            atom_outputs,
            atom_sequence_urid,
            atom_object_urid,
            atom_long_urid,
            atom_double_urid,
            atom_frame_time_urid,
            midi_event_urid,
            time_position_urid,
            time_frame_urid,
            time_speed_urid,
            time_bpm_urid,
            time_bar_urid,
            time_bar_beat_urid,
            time_beats_per_bar_urid,
            time_beat_unit_urid,
            midi_inputs,
            midi_outputs,
            has_worker_interface,
            control_ports,
            midnam_note_names: UnsafeMutex::new(HashMap::new()),
        };
        processor.validate_required_runtime_callbacks()?;
        processor.connect_ports();
        if std::env::var_os("MAOLAN_LV2_ENABLE_MIDNAM").is_some() {
            processor.query_midnam();
        }
        Ok(processor)
    }

    pub fn uri(&self) -> &str {
        &self.uri
    }

    pub fn name(&self) -> &str {
        &self.plugin_name
    }

    pub fn audio_inputs(&self) -> &[Arc<AudioIO>] {
        &self.audio_inputs
    }

    pub fn audio_outputs(&self) -> &[Arc<AudioIO>] {
        &self.audio_outputs
    }

    pub fn audio_input_count(&self) -> usize {
        self.audio_inputs.len()
    }

    pub fn audio_output_count(&self) -> usize {
        self.audio_outputs.len()
    }

    pub fn main_audio_input_count(&self) -> usize {
        self.main_audio_inputs
    }

    pub fn main_audio_output_count(&self) -> usize {
        self.main_audio_outputs
    }

    pub fn midi_input_count(&self) -> usize {
        self.midi_inputs
    }

    pub fn midi_output_count(&self) -> usize {
        self.midi_outputs
    }

    pub fn setup_audio_ports(&self) {
        for io in &self.audio_inputs {
            io.setup();
        }
        for io in &self.audio_outputs {
            io.setup();
        }
    }

    pub fn process(&mut self, input_channels: &[Vec<f32>], frames: usize) -> Vec<Vec<f32>> {
        if let Ok(mut values) = self.scalar_values.lock()
            && values.is_empty()
        {
            values.push(0.0);
        }

        for (channel, io) in self.audio_inputs.iter_mut().enumerate() {
            let buffer = io.buffer.lock();
            buffer.fill(0.0);
            if let Some(input) = input_channels.get(channel) {
                let copy_len = input.len().min(frames);
                buffer[..copy_len].copy_from_slice(&input[..copy_len]);
            }
        }
        for io in &self.audio_outputs {
            let buffer = io.buffer.lock();
            buffer.fill(0.0);
        }
        for buffer in &mut self.atom_inputs {
            prepare_empty_atom_sequence(
                buffer.bytes_mut(),
                self.atom_sequence_urid,
                self.atom_frame_time_urid,
            );
        }
        for buffer in &mut self.atom_outputs {
            prepare_output_atom_sequence(
                buffer.bytes_mut(),
                self.atom_sequence_urid,
                self.atom_frame_time_urid,
            );
        }

        self.connect_ports();
        if let Some(instance) = self.instance.as_mut() {
            unsafe {
                instance.run(frames);
            }
        }
        self.audio_outputs
            .iter()
            .map(|io| io.buffer.lock().as_ref().to_vec())
            .collect()
    }

    pub fn process_with_audio_io(
        &mut self,
        frames: usize,
        midi_inputs: &[Vec<MidiEvent>],
        transport: Lv2TransportInfo,
    ) -> Vec<Vec<MidiEvent>> {
        if let Ok(mut values) = self.scalar_values.lock()
            && values.is_empty()
        {
            values.push(0.0);
        }

        for io in &self.audio_outputs {
            let buffer = io.buffer.lock();
            buffer.fill(0.0);
            *io.finished.lock() = false;
        }
        for buffer in &mut self.atom_inputs {
            prepare_empty_atom_sequence(
                buffer.bytes_mut(),
                self.atom_sequence_urid,
                self.atom_frame_time_urid,
            );
        }
        for buffer in &mut self.atom_outputs {
            prepare_output_atom_sequence(
                buffer.bytes_mut(),
                self.atom_sequence_urid,
                self.atom_frame_time_urid,
            );
        }
        for port in 0..self.atom_inputs.len() {
            self.write_transport_event(port, transport);
        }
        for (port, events) in midi_inputs.iter().enumerate() {
            self.write_midi_input_events(port, events);
        }

        self.connect_ports();
        if let Some(instance) = self.instance.as_mut() {
            unsafe {
                instance.run(frames);
            }
        }
        self.run_worker_cycle();

        for io in &self.audio_outputs {
            *io.finished.lock() = true;
        }
        let mut midi_outputs = vec![];
        for port in 0..self.midi_outputs {
            midi_outputs.push(self.read_midi_output_events(port));
        }
        midi_outputs
    }

    fn run_worker_cycle(&mut self) {
        if !self.has_worker_interface {
            return;
        }
        let Some(worker_iface) = self.worker_interface() else {
            return;
        };
        let (work_fn, work_response_fn, end_run_fn) = (
            worker_iface.work,
            worker_iface.work_response,
            worker_iface.end_run,
        );
        let Some(work_fn) = work_fn else {
            return;
        };
        let instance_handle = self.instance_handle();
        if instance_handle.is_null() {
            return;
        }

        let worker_state = &self._instantiate_features._worker_feature.state;
        let mut jobs = std::mem::take(worker_state.jobs.lock());
        for job in jobs.drain(..) {
            if job.len() > (u32::MAX as usize) {
                continue;
            }
            unsafe {
                work_fn(
                    instance_handle,
                    Some(lv2_worker_respond_callback),
                    &**worker_state as *const WorkerScheduleState as *mut c_void,
                    job.len() as u32,
                    job.as_ptr().cast::<c_void>(),
                );
            }
        }
        *worker_state.jobs.lock() = jobs;

        if let Some(work_response_fn) = work_response_fn {
            let mut responses = std::mem::take(worker_state.responses.lock());
            for response in responses.drain(..) {
                if response.len() > (u32::MAX as usize) {
                    continue;
                }
                unsafe {
                    work_response_fn(
                        instance_handle,
                        response.len() as u32,
                        response.as_ptr().cast::<c_void>(),
                    );
                }
            }
            *worker_state.responses.lock() = responses;
        }

        if let Some(end_run_fn) = end_run_fn {
            unsafe {
                end_run_fn(instance_handle);
            }
        }
    }

    fn connect_ports(&mut self) {
        for (port_index, binding) in self.port_bindings.iter().enumerate() {
            match binding {
                PortBinding::AudioInput(channel) => {
                    let ptr = self.audio_inputs[*channel].buffer.lock().as_mut_ptr();
                    if let Some(instance) = self.instance.as_mut() {
                        unsafe {
                            instance.instance_mut().connect_port_mut(port_index, ptr);
                        }
                    }
                }
                PortBinding::AudioOutput(channel) => {
                    let ptr = self.audio_outputs[*channel].buffer.lock().as_mut_ptr();
                    if let Some(instance) = self.instance.as_mut() {
                        unsafe {
                            instance.instance_mut().connect_port_mut(port_index, ptr);
                        }
                    }
                }
                PortBinding::AtomInput(atom_index) => {
                    let ptr = self.atom_inputs[*atom_index].ptr_mut();
                    if let Some(instance) = self.instance.as_mut() {
                        unsafe {
                            instance.instance_mut().connect_port_mut(port_index, ptr);
                        }
                    }
                }
                PortBinding::AtomOutput(atom_index) => {
                    let ptr = self.atom_outputs[*atom_index].ptr_mut();
                    if let Some(instance) = self.instance.as_mut() {
                        unsafe {
                            instance.instance_mut().connect_port_mut(port_index, ptr);
                        }
                    }
                }
                PortBinding::Scalar(index) => {
                    if let Ok(mut values) = self.scalar_values.lock()
                        && *index < values.len()
                    {
                        let ptr = (&mut values[*index]) as *mut f32;
                        if let Some(instance) = self.instance.as_mut() {
                            unsafe {
                                instance.instance_mut().connect_port_mut(port_index, ptr);
                            }
                        }
                    }
                }
            }
        }
    }

    fn validate_required_runtime_callbacks(&self) -> Result<(), String> {
        let Some(instance) = self.instance.as_ref() else {
            return Err(format!("LV2 plugin '{}' has no active instance", self.uri));
        };
        let Some(descriptor) = instance.instance().descriptor() else {
            return Err(format!("LV2 plugin '{}' has no descriptor", self.uri));
        };
        let connect_ptr = (descriptor.connect_port as *const ()) as usize;
        if connect_ptr == 0 {
            return Err(format!(
                "LV2 plugin '{}' has null connect_port callback",
                self.uri
            ));
        }
        let run_ptr = (descriptor.run as *const ()) as usize;
        if run_ptr == 0 {
            return Err(format!("LV2 plugin '{}' has null run callback", self.uri));
        }
        Ok(())
    }

    pub fn snapshot_state(&self) -> Lv2PluginState {
        let mut state = Lv2PluginState {
            port_values: self.control_port_values(),
            properties: vec![],
        };
        let Some(interface) = self.state_interface() else {
            return state;
        };
        let Some(save_fn) = interface.save else {
            return state;
        };

        let mut ctx = StateSaveContext { properties: vec![] };
        let features = self.state_feature_ptrs();
        let status = unsafe {
            save_fn(
                self.instance_handle(),
                Some(lv2_state_store_callback),
                (&mut ctx as *mut StateSaveContext).cast::<c_void>(),
                0,
                features.as_ptr(),
            )
        };
        if status != LV2_STATE_STATUS_SUCCESS {
            return state;
        }

        state.properties = ctx
            .properties
            .into_iter()
            .filter_map(|p| {
                let key_uri = self._urid_feature.unmap_urid(p.key)?;
                let type_uri = self._urid_feature.unmap_urid(p.type_)?;
                Some(Lv2StateProperty {
                    key_uri,
                    type_uri,
                    flags: p.flags,
                    value: p.value,
                })
            })
            .collect();
        state
    }

    pub fn snapshot_port_state(&self) -> Lv2PluginState {
        Lv2PluginState {
            port_values: self.control_port_values(),
            properties: vec![],
        }
    }

    pub fn restore_state(&mut self, state: &Lv2PluginState) -> Result<(), String> {
        self.set_control_port_values(&state.port_values);
        if state.properties.is_empty() {
            return Ok(());
        }
        let Some(interface) = self.state_interface() else {
            return Ok(());
        };
        let Some(restore_fn) = interface.restore else {
            return Ok(());
        };

        let mut properties: Vec<RawStateProperty> = vec![];
        let mut by_key: HashMap<u32, usize> = HashMap::new();
        for prop in &state.properties {
            let key = self._urid_feature.map_uri(prop.key_uri.as_bytes());
            let type_ = self._urid_feature.map_uri(prop.type_uri.as_bytes());
            if key == 0 || type_ == 0 {
                continue;
            }
            let idx = properties.len();
            properties.push(RawStateProperty {
                key,
                type_,
                flags: prop.flags,
                value: prop.value.clone(),
            });
            by_key.insert(key, idx);
        }
        let mut ctx = StateRestoreContext { properties, by_key };
        let features = self.state_feature_ptrs();

        let status = unsafe {
            restore_fn(
                self.instance_handle(),
                Some(lv2_state_retrieve_callback),
                (&mut ctx as *mut StateRestoreContext).cast::<c_void>(),
                0,
                features.as_ptr(),
            )
        };
        if status == LV2_STATE_STATUS_SUCCESS {
            Ok(())
        } else {
            Err(format!(
                "LV2 state restore failed for '{}': status {}",
                self.uri, status
            ))
        }
    }

    fn state_interface(&self) -> Option<&Lv2StateInterface> {
        let instance = self.instance.as_ref()?;
        if !self.has_extension_data_callback(instance.instance()) {
            return None;
        }
        let ptr = unsafe {
            instance
                .instance()
                .extension_data::<Lv2StateInterface>(LV2_STATE_INTERFACE_URI)?
        };
        Some(unsafe { ptr.as_ref() })
    }

    fn worker_interface(&self) -> Option<&Lv2WorkerInterface> {
        let instance = self.instance.as_ref()?;
        if !self.has_extension_data_callback(instance.instance()) {
            return None;
        }
        let ptr = unsafe {
            instance
                .instance()
                .extension_data::<Lv2WorkerInterface>(LV2_WORKER__INTERFACE)?
        };
        Some(unsafe { ptr.as_ref() })
    }

    fn has_extension_data_callback(&self, instance: &lilv::instance::Instance) -> bool {
        let Some(descriptor) = instance.descriptor() else {
            return false;
        };
        (descriptor.extension_data as *const ()) as usize != 0
    }

    fn instance_handle(&self) -> Lv2Handle {
        self.instance
            .as_ref()
            .map(|i| i.instance().handle() as Lv2Handle)
            .unwrap_or(std::ptr::null_mut())
    }

    pub fn instance_access_handle(&self) -> Option<usize> {
        let handle = self.instance_handle();
        if handle.is_null() {
            None
        } else {
            Some(handle as usize)
        }
    }

    fn state_feature_ptrs(&self) -> [*const LV2Feature; 6] {
        let sp = self._state_path_feature.feature_ptrs();
        [
            self._urid_feature.map_feature() as *const LV2Feature,
            self._urid_feature.unmap_feature() as *const LV2Feature,
            sp[0],
            sp[1],
            sp[2],
            std::ptr::null(),
        ]
    }

    fn control_port_values(&self) -> Vec<Lv2StatePortValue> {
        let Ok(values) = self.scalar_values.lock() else {
            return vec![];
        };
        self.control_ports
            .iter()
            .filter_map(|port| {
                values.get(port.index as usize).map(|v| Lv2StatePortValue {
                    index: port.index,
                    value: *v,
                })
            })
            .collect()
    }

    fn set_control_port_values(&mut self, port_values: &[Lv2StatePortValue]) {
        let Ok(mut values) = self.scalar_values.lock() else {
            return;
        };
        for port in port_values {
            if let Some(slot) = values.get_mut(port.index as usize) {
                *slot = port.value;
            }
        }
    }

    pub fn set_state_base_dir(&mut self, base_dir: PathBuf) {
        self._state_path_feature.set_base_dir(base_dir);
    }

    pub fn control_ports_with_values(&self) -> Vec<Lv2ControlPortInfo> {
        let Ok(values) = self.scalar_values.lock() else {
            return Vec::new();
        };
        self.control_ports
            .iter()
            .map(|port| Lv2ControlPortInfo {
                index: port.index,
                name: port.name.clone(),
                min: port.min,
                max: port.max,
                value: values.get(port.index as usize).copied().unwrap_or(0.0),
            })
            .collect()
    }

    pub fn set_control_value(&mut self, index: u32, value: f32) -> Result<(), String> {
        let Some(port) = self.control_ports.iter().find(|port| port.index == index) else {
            return Err(format!("Unknown LV2 control port index: {index}"));
        };
        let clamped = value.clamp(port.min, port.max);
        let Ok(mut values) = self.scalar_values.lock() else {
            return Err("Failed to lock LV2 control values".to_string());
        };
        let Some(slot) = values.get_mut(index as usize) else {
            return Err(format!("LV2 control port index out of range: {index}"));
        };
        *slot = clamped;
        Ok(())
    }

    pub fn midnam_note_names(&self) -> HashMap<u8, String> {
        self.midnam_note_names.lock().clone()
    }

    fn query_midnam(&mut self) {
        let Some(instance) = &self.instance else {
            return;
        };

        if !self.has_extension_data_callback(instance.instance()) {
            return;
        }

        let interface_ptr = unsafe {
            instance
                .instance()
                .extension_data::<Lv2MidnamInterface>(LV2_MIDNAM__INTERFACE)
        };
        let Some(interface_ptr) = interface_ptr else {
            return;
        };
        let interface = unsafe { interface_ptr.as_ref() };

        // Query the midnam XML
        let Some(midnam_fn) = interface.midnam else {
            return;
        };
        let Some(free_fn) = interface.free else {
            return;
        };

        let xml_ptr = unsafe { midnam_fn(instance.instance().handle()) };
        if xml_ptr.is_null() {
            return;
        }

        let xml_cstr = unsafe { CStr::from_ptr(xml_ptr) };
        let Ok(xml_str) = xml_cstr.to_str() else {
            unsafe { free_fn(xml_ptr) };
            return;
        };

        // Parse the midnam XML to extract note names
        let note_names = self.parse_midnam_xml(xml_str);
        *self.midnam_note_names.lock() = note_names;

        unsafe { free_fn(xml_ptr) };
    }

    fn parse_midnam_xml(&self, xml: &str) -> HashMap<u8, String> {
        let mut note_names = HashMap::new();

        // Simple XML parsing for <Note Number="X" Name="Y"/> elements
        for line in xml.lines() {
            let line = line.trim();
            if !line.starts_with("<Note ") {
                continue;
            }

            // Extract Number attribute
            let number = if let Some(start) = line.find("Number=\"") {
                let start = start + 8; // len("Number=\"")
                if let Some(end) = line[start..].find('"') {
                    line[start..start + end].parse::<u8>().ok()
                } else {
                    None
                }
            } else {
                None
            };

            // Extract Name attribute
            let name = if let Some(start) = line.find("Name=\"") {
                let start = start + 6; // len("Name=\"")
                line[start..]
                    .find('"')
                    .map(|end| line[start..start + end].to_string())
            } else {
                None
            };

            if let (Some(num), Some(name_str)) = (number, name) {
                note_names.insert(num, name_str);
            }
        }

        note_names
    }

    fn write_midi_input_events(&mut self, port: usize, events: &[MidiEvent]) {
        let Some(buffer) = self.atom_inputs.get_mut(port) else {
            return;
        };
        let bytes = buffer.bytes_mut();
        if bytes.len() < std::mem::size_of::<LV2AtomSequence>() {
            return;
        }
        let seq = bytes.as_mut_ptr() as *mut LV2AtomSequence;
        let capacity = bytes
            .len()
            .saturating_sub(std::mem::size_of::<lv2_raw::LV2Atom>()) as u32;
        for event in events {
            if event.data.is_empty() {
                continue;
            }
            let mut raw =
                vec![0_u8; std::mem::size_of::<lv2_raw::LV2AtomEvent>() + event.data.len()];
            let raw_event = raw.as_mut_ptr() as *mut lv2_raw::LV2AtomEvent;
            unsafe {
                (*raw_event).time_in_frames = event.frame as i64;
                (*raw_event).body.mytype = self.midi_event_urid;
                (*raw_event).body.size = event.data.len() as u32;
                let data_ptr =
                    (raw_event as *mut u8).add(std::mem::size_of::<lv2_raw::LV2AtomEvent>());
                std::ptr::copy_nonoverlapping(event.data.as_ptr(), data_ptr, event.data.len());
                if lv2_atom_sequence_append_event(seq, capacity, raw_event).is_null() {
                    break;
                }
            }
        }
    }

    fn write_transport_event(&mut self, port: usize, transport: Lv2TransportInfo) {
        let Some(buffer) = self.atom_inputs.get_mut(port) else {
            return;
        };
        let bytes = buffer.bytes_mut();
        if bytes.len() < std::mem::size_of::<LV2AtomSequence>() {
            return;
        }
        let seq = bytes.as_mut_ptr() as *mut LV2AtomSequence;
        let capacity = bytes
            .len()
            .saturating_sub(std::mem::size_of::<lv2_raw::LV2Atom>()) as u32;

        let beats_per_bar = if transport.tsig_num == 0 {
            4.0
        } else {
            transport.tsig_num as f64
        };
        let beat_unit = if transport.tsig_denom == 0 {
            4_i64
        } else {
            transport.tsig_denom as i64
        };
        let bpm = if transport.bpm > 0.0 {
            transport.bpm
        } else {
            120.0
        };
        let speed = if transport.playing { 1.0 } else { 0.0 };
        let sample = transport.transport_sample as i64;
        let seconds = (transport.transport_sample as f64) / self.sample_rate.max(1.0);
        let absolute_beats = seconds * bpm / 60.0;
        let bar = (absolute_beats / beats_per_bar).floor().max(0.0) as i64;
        let bar_beat = absolute_beats - (bar as f64 * beats_per_bar);

        let mut payload =
            Vec::<u8>::with_capacity(std::mem::size_of::<LV2AtomObjectBody>() + (7 * 32));
        let object_body = LV2AtomObjectBody {
            id: 0,
            otype: self.time_position_urid,
        };
        let object_body_bytes = unsafe {
            std::slice::from_raw_parts(
                (&object_body as *const LV2AtomObjectBody).cast::<u8>(),
                std::mem::size_of::<LV2AtomObjectBody>(),
            )
        };
        payload.extend_from_slice(object_body_bytes);

        append_object_long_property(
            &mut payload,
            self.time_frame_urid,
            self.atom_long_urid,
            sample,
        );
        append_object_double_property(
            &mut payload,
            self.time_speed_urid,
            self.atom_double_urid,
            speed,
        );
        append_object_double_property(&mut payload, self.time_bpm_urid, self.atom_double_urid, bpm);
        append_object_long_property(&mut payload, self.time_bar_urid, self.atom_long_urid, bar);
        append_object_double_property(
            &mut payload,
            self.time_bar_beat_urid,
            self.atom_double_urid,
            bar_beat,
        );
        append_object_double_property(
            &mut payload,
            self.time_beats_per_bar_urid,
            self.atom_double_urid,
            beats_per_bar,
        );
        append_object_long_property(
            &mut payload,
            self.time_beat_unit_urid,
            self.atom_long_urid,
            beat_unit,
        );

        if payload.len() > (u32::MAX as usize) {
            return;
        }

        let mut raw = vec![0_u8; std::mem::size_of::<LV2AtomEvent>() + payload.len()];
        let raw_event = raw.as_mut_ptr() as *mut LV2AtomEvent;
        unsafe {
            (*raw_event).time_in_frames = 0;
            (*raw_event).body.mytype = self.atom_object_urid;
            (*raw_event).body.size = payload.len() as u32;
            let data_ptr = (raw_event as *mut u8).add(std::mem::size_of::<LV2AtomEvent>());
            std::ptr::copy_nonoverlapping(payload.as_ptr(), data_ptr, payload.len());
            let _ = lv2_atom_sequence_append_event(seq, capacity, raw_event);
        }
    }

    fn read_midi_output_events(&mut self, port: usize) -> Vec<MidiEvent> {
        let Some(buffer) = self.atom_outputs.get_mut(port) else {
            return vec![];
        };
        let bytes = buffer.bytes_mut();
        if bytes.len() < std::mem::size_of::<LV2AtomSequence>() {
            return vec![];
        }

        let mut result = Vec::new();
        let seq = bytes.as_mut_ptr() as *mut LV2AtomSequence;
        unsafe {
            let body = &(*seq).body as *const LV2AtomSequenceBody;
            let size = (*seq).atom.size;
            let mut it = lv2_atom_sequence_begin(body);
            while !lv2_atom_sequence_is_end(body, size, it) {
                let event = &*it;
                if event.body.mytype == self.midi_event_urid && event.body.size > 0 {
                    let data_ptr =
                        (it as *const u8).add(std::mem::size_of::<lv2_raw::LV2AtomEvent>());
                    let data_len = event.body.size as usize;
                    let data = std::slice::from_raw_parts(data_ptr, data_len).to_vec();
                    result.push(MidiEvent::new(event.time_in_frames.max(0) as u32, data));
                }
                it = lv2_atom_sequence_next(it);
            }
        }
        result
    }
}

impl Drop for Lv2Processor {
    fn drop(&mut self) {
        let Some(instance) = self.instance.take() else {
            return;
        };
        drop(instance);
    }
}

impl Lv2Host {
    pub fn new(sample_rate: f64) -> Self {
        let world = World::new();
        world.load_all();
        Self {
            world,
            sample_rate,
            loaded_plugins: HashMap::new(),
        }
    }

    pub fn list_plugins(&self) -> Vec<Lv2PluginInfo> {
        let input_port = self.world.new_uri("http://lv2plug.in/ns/lv2core#InputPort");
        let output_port = self
            .world
            .new_uri("http://lv2plug.in/ns/lv2core#OutputPort");
        let audio_port = self.world.new_uri("http://lv2plug.in/ns/lv2core#AudioPort");
        let atom_port = self.world.new_uri("http://lv2plug.in/ns/ext/atom#AtomPort");
        let event_port = self
            .world
            .new_uri("http://lv2plug.in/ns/ext/event#EventPort");
        let midi_event = self
            .world
            .new_uri("http://lv2plug.in/ns/ext/midi#MidiEvent");

        let mut plugins = self
            .world
            .plugins()
            .iter()
            .filter(|plugin| plugin.verify())
            .filter_map(|plugin| {
                let uri = plugin.uri().as_uri()?.to_string();
                let name = plugin.name().as_str().unwrap_or(&uri).to_string();
                let class_label = plugin
                    .class()
                    .label()
                    .as_str()
                    .unwrap_or("Unknown")
                    .to_string();
                let bundle_uri = plugin.bundle_uri().as_uri().unwrap_or("").to_string();
                let required_features = plugin_feature_uris(&plugin);
                let (audio_inputs, audio_outputs, midi_inputs, midi_outputs) = plugin_port_counts(
                    &plugin,
                    &input_port,
                    &output_port,
                    &audio_port,
                    &atom_port,
                    &event_port,
                    &midi_event,
                );

                Some(Lv2PluginInfo {
                    uri,
                    name,
                    class_label,
                    bundle_uri,
                    required_features,
                    audio_inputs,
                    audio_outputs,
                    midi_inputs,
                    midi_outputs,
                })
            })
            .collect::<Vec<_>>();

        plugins.sort_by(|left, right| left.name.cmp(&right.name));
        plugins
    }

    pub fn load_plugin(&mut self, uri: &str) -> Result<(), String> {
        if self.loaded_plugins.contains_key(uri) {
            return Err(format!("Plugin is already loaded: {uri}"));
        }

        let plugin = self
            .plugin_by_uri(uri)
            .ok_or_else(|| format!("Plugin not found for URI: {uri}"))?;

        let mut urid_feature = UridMapFeature::new()?;
        let mut state_path_feature = StatePathFeature::new(default_state_base_dir());
        let (instance, instantiate_features) = instantiate_plugin(
            &plugin,
            self.sample_rate,
            uri,
            &mut urid_feature,
            &mut state_path_feature,
        )?;
        let active_instance = unsafe { instance.activate() };
        self.loaded_plugins.insert(
            uri.to_string(),
            LoadedPlugin {
                instance: active_instance,
                _urid_feature: urid_feature,
                _state_path_feature: state_path_feature,
                _instantiate_features: instantiate_features,
            },
        );
        Ok(())
    }

    pub fn unload_plugin(&mut self, uri: &str) -> Result<(), String> {
        let loaded_plugin = self
            .loaded_plugins
            .remove(uri)
            .ok_or_else(|| format!("Plugin is not currently loaded: {uri}"))?;
        let _ = unsafe { loaded_plugin.instance.deactivate() };
        Ok(())
    }

    pub fn unload_all(&mut self) {
        let uris = self.loaded_plugins();
        for uri in uris {
            let _ = self.unload_plugin(&uri);
        }
    }

    pub fn loaded_plugins(&self) -> Vec<String> {
        let mut uris = self.loaded_plugins.keys().cloned().collect::<Vec<_>>();
        uris.sort();
        uris
    }

    pub fn loaded_count(&self) -> usize {
        self.loaded_plugins.len()
    }

    fn plugin_by_uri(&self, uri: &str) -> Option<Plugin> {
        let uri_node = self.world.new_uri(uri);
        self.world.plugins().plugin(&uri_node)
    }
}

impl Drop for Lv2Host {
    fn drop(&mut self) {
        self.unload_all();
    }
}

fn plugin_feature_uris(plugin: &Plugin) -> Vec<String> {
    plugin
        .required_features()
        .iter()
        .filter_map(|feature| {
            feature
                .as_uri()
                .map(str::to_string)
                .or_else(|| feature.as_str().map(str::to_string))
        })
        .collect()
}

fn instantiate_plugin(
    plugin: &Plugin,
    sample_rate: f64,
    uri: &str,
    urid_feature: &mut UridMapFeature,
    state_path_feature: &mut StatePathFeature,
) -> Result<(lilv::instance::Instance, InstantiateFeatureSet), String> {
    let required_features = plugin_feature_uris(plugin);
    let feature_set =
        build_instantiate_features(&required_features, urid_feature, state_path_feature)?;
    let feature_refs: Vec<&LV2Feature> = feature_set.features.iter().collect();
    let instance = unsafe { plugin.instantiate(sample_rate, feature_refs) }.ok_or_else(|| {
        if required_features.is_empty() {
            format!(
                "Failed to instantiate '{uri}'. It likely requires LV2 host features that are not wired yet."
            )
        } else {
            format!(
                "Failed to instantiate '{uri}'. Required features: {}",
                required_features.join(", ")
            )
        }
    })?;
    Ok((instance, feature_set))
}

fn build_instantiate_features(
    required_features: &[String],
    urid_feature: &UridMapFeature,
    state_path_feature: &StatePathFeature,
) -> Result<InstantiateFeatureSet, String> {
    let mut seen = HashSet::<String>::new();
    let mut feature_uris = Vec::<CString>::new();
    let mut features = Vec::<LV2Feature>::new();

    let mut push_feature =
        |uri: &str, data: *mut c_void, allow_duplicate: bool| -> Result<(), String> {
            if !allow_duplicate && !seen.insert(uri.to_string()) {
                return Ok(());
            }
            let c_uri = CString::new(uri)
                .map_err(|e| format!("Invalid LV2 feature URI '{uri}' for instantiate: {e}"))?;
            let feature = LV2Feature {
                uri: c_uri.as_ptr(),
                data,
            };
            feature_uris.push(c_uri);
            features.push(feature);
            Ok(())
        };

    push_feature(LV2_URID__MAP, urid_feature.map_feature().data, false)?;
    push_feature(LV2_URID__UNMAP, urid_feature.unmap_feature().data, false)?;
    let worker_feature = WorkerFeature::new()?;
    push_feature(LV2_WORKER__SCHEDULE, worker_feature.feature.data, false)?;

    let state_features = state_path_feature.feature_ptrs();
    for feature_ptr in state_features {
        if feature_ptr.is_null() {
            continue;
        }
        let feature = unsafe { &*feature_ptr };
        let uri = unsafe { CStr::from_ptr(feature.uri) }
            .to_str()
            .map_err(|e| format!("Invalid LV2 feature URI from state path feature: {e}"))?;
        push_feature(uri, feature.data, false)?;
    }

    let option_values = vec![1_u32, 8192_u32, 1024_u32];
    let int_type = urid_feature.map_uri(LV2_ATOM__INT);
    let min_key = urid_feature.map_uri(LV2_BUF_SIZE__MIN_BLOCK_LENGTH.as_bytes());
    let max_key = urid_feature.map_uri(LV2_BUF_SIZE__MAX_BLOCK_LENGTH.as_bytes());
    let nominal_key = urid_feature.map_uri(LV2_BUF_SIZE__NOMINAL_BLOCK_LENGTH.as_bytes());
    let mut options = vec![
        LV2OptionsOption {
            context: 0,
            subject: 0,
            key: min_key,
            size: std::mem::size_of::<u32>() as u32,
            type_: int_type,
            value: (&option_values[0] as *const u32).cast::<c_void>(),
        },
        LV2OptionsOption {
            context: 0,
            subject: 0,
            key: max_key,
            size: std::mem::size_of::<u32>() as u32,
            type_: int_type,
            value: (&option_values[1] as *const u32).cast::<c_void>(),
        },
        LV2OptionsOption {
            context: 0,
            subject: 0,
            key: nominal_key,
            size: std::mem::size_of::<u32>() as u32,
            type_: int_type,
            value: (&option_values[2] as *const u32).cast::<c_void>(),
        },
        LV2OptionsOption {
            context: 0,
            subject: 0,
            key: 0,
            size: 0,
            type_: 0,
            value: std::ptr::null(),
        },
    ];
    push_feature(
        LV2_OPTIONS__OPTIONS,
        options.as_mut_ptr().cast::<c_void>(),
        false,
    )?;

    let flag_feature_data = Box::new(0_u8);

    for required in required_features {
        let data = match required.as_str() {
            LV2_OPTIONS__OPTIONS => options.as_mut_ptr().cast::<c_void>(),
            LV2_BUF_SIZE__BOUNDED_BLOCK_LENGTH => (&*flag_feature_data as *const u8)
                .cast_mut()
                .cast::<c_void>(),
            LV2_URID__MAP_URI_TYPO_COMPAT => urid_feature.map_feature().data,
            LV2_WORKER__SCHEDULE => worker_feature.feature.data,
            _ => (&*flag_feature_data as *const u8)
                .cast_mut()
                .cast::<c_void>(),
        };
        push_feature(required, data, false)?;
    }

    Ok(InstantiateFeatureSet {
        _feature_uris: feature_uris,
        features,
        _worker_feature: worker_feature,
        _option_values: option_values,
        _options: options,
        _flag_feature_data: flag_feature_data,
    })
}

impl WorkerFeature {
    fn new() -> Result<Self, String> {
        let mut schedule = Box::new(Lv2WorkerSchedule {
            handle: std::ptr::null_mut(),
            schedule_work: Some(lv2_worker_schedule_work_callback),
        });
        let state = Box::new(WorkerScheduleState {
            jobs: UnsafeMutex::new(vec![]),
            responses: UnsafeMutex::new(vec![]),
        });
        schedule.handle = &*state as *const WorkerScheduleState as *mut c_void;
        let uri =
            CString::new(LV2_WORKER__SCHEDULE).map_err(|e| format!("Invalid worker URI: {e}"))?;
        let feature = LV2Feature {
            uri: uri.as_ptr(),
            data: (&mut *schedule as *mut Lv2WorkerSchedule).cast::<c_void>(),
        };
        Ok(Self {
            _uri: uri,
            _schedule: schedule,
            feature,
            state,
        })
    }
}

unsafe extern "C" fn lv2_worker_schedule_work_callback(
    handle: *mut c_void,
    size: u32,
    data: *const c_void,
) -> u32 {
    if handle.is_null() || (size > 0 && data.is_null()) {
        return LV2_WORKER_ERR_UNKNOWN;
    }
    let state = unsafe { &*(handle as *const WorkerScheduleState) };
    let bytes = if size == 0 {
        vec![]
    } else {
        unsafe { std::slice::from_raw_parts(data.cast::<u8>(), size as usize).to_vec() }
    };
    state.jobs.lock().push(bytes);
    LV2_WORKER_SUCCESS
}

unsafe extern "C" fn lv2_worker_respond_callback(
    handle: *mut c_void,
    size: u32,
    data: *const c_void,
) -> u32 {
    if handle.is_null() || (size > 0 && data.is_null()) {
        return LV2_WORKER_ERR_UNKNOWN;
    }
    let state = unsafe { &*(handle as *const WorkerScheduleState) };
    let bytes = if size == 0 {
        vec![]
    } else {
        unsafe { std::slice::from_raw_parts(data.cast::<u8>(), size as usize).to_vec() }
    };
    state.responses.lock().push(bytes);
    LV2_WORKER_SUCCESS
}

fn append_object_long_property(buffer: &mut Vec<u8>, key: LV2Urid, atom_type: LV2Urid, value: i64) {
    let prop = LV2AtomPropertyBody {
        key,
        context: 0,
        value: LV2Atom {
            size: std::mem::size_of::<i64>() as u32,
            mytype: atom_type,
        },
    };
    let prop_size = std::mem::size_of::<LV2AtomPropertyBody>();
    let prop_bytes = unsafe {
        std::slice::from_raw_parts(
            (&prop as *const LV2AtomPropertyBody).cast::<u8>(),
            prop_size,
        )
    };
    buffer.extend_from_slice(prop_bytes);
    let atom = LV2AtomLong {
        atom: LV2Atom {
            size: std::mem::size_of::<i64>() as u32,
            mytype: atom_type,
        },
        body: value,
    };
    let value_bytes = unsafe {
        std::slice::from_raw_parts(
            (&atom.body as *const i64).cast::<u8>(),
            std::mem::size_of::<i64>(),
        )
    };
    buffer.extend_from_slice(value_bytes);
    let written = (prop_size + std::mem::size_of::<i64>()) as u32;
    let padded = lv2_atom_pad_size(written) as usize;
    if padded > (prop_size + std::mem::size_of::<i64>()) {
        buffer.resize(
            buffer.len() + (padded - prop_size - std::mem::size_of::<i64>()),
            0,
        );
    }
}

fn append_object_double_property(
    buffer: &mut Vec<u8>,
    key: LV2Urid,
    atom_type: LV2Urid,
    value: f64,
) {
    let prop = LV2AtomPropertyBody {
        key,
        context: 0,
        value: LV2Atom {
            size: std::mem::size_of::<f64>() as u32,
            mytype: atom_type,
        },
    };
    let prop_size = std::mem::size_of::<LV2AtomPropertyBody>();
    let prop_bytes = unsafe {
        std::slice::from_raw_parts(
            (&prop as *const LV2AtomPropertyBody).cast::<u8>(),
            prop_size,
        )
    };
    buffer.extend_from_slice(prop_bytes);
    let atom = LV2AtomDouble {
        atom: LV2Atom {
            size: std::mem::size_of::<f64>() as u32,
            mytype: atom_type,
        },
        body: value,
    };
    let value_bytes = unsafe {
        std::slice::from_raw_parts(
            (&atom.body as *const f64).cast::<u8>(),
            std::mem::size_of::<f64>(),
        )
    };
    buffer.extend_from_slice(value_bytes);
    let written = (prop_size + std::mem::size_of::<f64>()) as u32;
    let padded = lv2_atom_pad_size(written) as usize;
    if padded > (prop_size + std::mem::size_of::<f64>()) {
        buffer.resize(
            buffer.len() + (padded - prop_size - std::mem::size_of::<f64>()),
            0,
        );
    }
}

impl UridMapFeature {
    fn new() -> Result<Self, String> {
        let mut map = Box::new(LV2UridMap {
            handle: std::ptr::null_mut(),
            map: urid_map_callback,
        });
        let mut unmap = Box::new(LV2UridUnmap {
            handle: std::ptr::null_mut(),
            unmap: urid_unmap_callback,
        });
        let state = Box::new(Mutex::new(UridMapState {
            next_urid: 1,
            by_uri: HashMap::new(),
            by_urid: HashMap::new(),
        }));
        map.handle = (&*state as *const Mutex<UridMapState>) as *mut c_void;
        unmap.handle = (&*state as *const Mutex<UridMapState>) as *mut c_void;

        let map_uri =
            CString::new(LV2_URID__MAP).map_err(|e| format!("Invalid URID feature URI: {e}"))?;
        let map_feature = LV2Feature {
            uri: map_uri.as_ptr(),
            data: (&mut *map as *mut LV2UridMap).cast::<c_void>(),
        };
        let unmap_uri =
            CString::new(LV2_URID__UNMAP).map_err(|e| format!("Invalid URID feature URI: {e}"))?;
        let unmap_feature = LV2Feature {
            uri: unmap_uri.as_ptr(),
            data: (&mut *unmap as *mut LV2UridUnmap).cast::<c_void>(),
        };

        Ok(Self {
            _map_uri: map_uri,
            _unmap_uri: unmap_uri,
            map_feature,
            unmap_feature,
            _map: map,
            _unmap: unmap,
            _state: state,
        })
    }

    fn map_feature(&self) -> &LV2Feature {
        &self.map_feature
    }

    fn unmap_feature(&self) -> &LV2Feature {
        &self.unmap_feature
    }

    fn map_uri(&self, uri: &[u8]) -> LV2Urid {
        let Ok(uri_str) = std::str::from_utf8(uri) else {
            return 0;
        };
        let uri_str = uri_str.trim_end_matches('\0');
        let Ok(mut state) = self._state.lock() else {
            return 0;
        };
        if let Some(existing) = state.by_uri.get(uri_str).copied() {
            return existing;
        }
        let mapped = state.next_urid;
        state.next_urid = state.next_urid.saturating_add(1);
        state.by_uri.insert(uri_str.to_string(), mapped);
        if let Ok(uri_c) = CString::new(uri_str) {
            state.by_urid.insert(mapped, uri_c);
        }
        mapped
    }

    fn unmap_urid(&self, urid: LV2Urid) -> Option<String> {
        let Ok(state) = self._state.lock() else {
            return None;
        };
        state
            .by_urid
            .get(&urid)
            .and_then(|uri| uri.to_str().ok().map(str::to_string))
    }
}

fn default_state_base_dir() -> PathBuf {
    std::env::temp_dir().join("maolan-lv2-state")
}

impl StatePathFeature {
    fn new(base_dir: PathBuf) -> Self {
        let context = Box::new(Mutex::new(StatePathContext {
            base_dir,
            copy_counter: 0,
        }));
        let handle = (&*context as *const Mutex<StatePathContext>) as *mut c_void;

        let map = Box::new(Lv2StateMapPath {
            handle,
            abstract_path: Some(lv2_state_abstract_path_callback),
            absolute_path: Some(lv2_state_absolute_path_callback),
        });
        let make = Box::new(Lv2StateMakePath {
            handle,
            path: Some(lv2_state_make_path_callback),
        });
        let free = Box::new(Lv2StateFreePath {
            handle,
            free_path: Some(lv2_state_free_path_callback),
        });

        let map_uri = CString::new(LV2_STATE_MAP_PATH_URI).expect("valid LV2 state mapPath URI");
        let make_uri = CString::new(LV2_STATE_MAKE_PATH_URI).expect("valid LV2 state makePath URI");
        let free_uri = CString::new(LV2_STATE_FREE_PATH_URI).expect("valid LV2 state freePath URI");

        let map_feature = LV2Feature {
            uri: map_uri.as_ptr(),
            data: (&*map as *const Lv2StateMapPath)
                .cast_mut()
                .cast::<c_void>(),
        };
        let make_feature = LV2Feature {
            uri: make_uri.as_ptr(),
            data: (&*make as *const Lv2StateMakePath)
                .cast_mut()
                .cast::<c_void>(),
        };
        let free_feature = LV2Feature {
            uri: free_uri.as_ptr(),
            data: (&*free as *const Lv2StateFreePath)
                .cast_mut()
                .cast::<c_void>(),
        };

        let instance = Self {
            _map_uri: map_uri,
            _make_uri: make_uri,
            _free_uri: free_uri,
            _map: map,
            _make: make,
            _free: free,
            map_feature,
            make_feature,
            free_feature,
            _context: context,
        };
        instance.ensure_base_dir();
        instance
    }

    fn ensure_base_dir(&self) {
        if let Ok(ctx) = self._context.lock() {
            let _ = std::fs::create_dir_all(&ctx.base_dir);
        }
    }

    fn set_base_dir(&self, base_dir: PathBuf) {
        if let Ok(mut ctx) = self._context.lock() {
            ctx.base_dir = base_dir;
            let _ = std::fs::create_dir_all(&ctx.base_dir);
        }
    }

    fn feature_ptrs(&self) -> [*const LV2Feature; 3] {
        [
            &self.map_feature as *const LV2Feature,
            &self.make_feature as *const LV2Feature,
            &self.free_feature as *const LV2Feature,
        ]
    }
}

extern "C" fn lv2_state_free_path_callback(_handle: *mut c_void, path: *mut c_char) {
    if path.is_null() {
        return;
    }
    unsafe {
        let _ = CString::from_raw(path);
    }
}

fn state_ctx_from_handle(handle: *mut c_void) -> Option<&'static Mutex<StatePathContext>> {
    if handle.is_null() {
        return None;
    }
    Some(unsafe { &*(handle as *const Mutex<StatePathContext>) })
}

fn copy_into_state_assets(ctx: &mut StatePathContext, src: &Path) -> Option<String> {
    let file_name = src.file_name()?.to_str()?.to_string();
    let assets_dir = ctx.base_dir.join("assets");
    let _ = std::fs::create_dir_all(&assets_dir);
    ctx.copy_counter = ctx.copy_counter.saturating_add(1);
    let dst_name = format!("{}-{}", ctx.copy_counter, file_name);
    let dst = assets_dir.join(&dst_name);
    std::fs::copy(src, &dst).ok()?;
    Some(format!("assets/{dst_name}"))
}

extern "C" fn lv2_state_abstract_path_callback(
    handle: *mut c_void,
    absolute_path: *const c_char,
) -> *mut c_char {
    let Some(ctx_lock) = state_ctx_from_handle(handle) else {
        return std::ptr::null_mut();
    };
    if absolute_path.is_null() {
        return std::ptr::null_mut();
    }
    let Some(path_str) = (unsafe { CStr::from_ptr(absolute_path) }).to_str().ok() else {
        return std::ptr::null_mut();
    };
    let path = PathBuf::from(path_str);
    let mut mapped = None;
    if let Ok(mut ctx) = ctx_lock.lock() {
        if let Ok(rel) = path.strip_prefix(&ctx.base_dir) {
            mapped = Some(rel.to_string_lossy().to_string());
        } else if path.exists() {
            mapped = copy_into_state_assets(&mut ctx, &path);
        }
    }
    let out = mapped.unwrap_or_else(|| path_str.to_string());
    CString::new(out)
        .ok()
        .map(CString::into_raw)
        .unwrap_or(std::ptr::null_mut())
}

extern "C" fn lv2_state_absolute_path_callback(
    handle: *mut c_void,
    abstract_path: *const c_char,
) -> *mut c_char {
    let Some(ctx_lock) = state_ctx_from_handle(handle) else {
        return std::ptr::null_mut();
    };
    if abstract_path.is_null() {
        return std::ptr::null_mut();
    }
    let Some(path_str) = (unsafe { CStr::from_ptr(abstract_path) }).to_str().ok() else {
        return std::ptr::null_mut();
    };
    let output = if Path::new(path_str).is_absolute() {
        path_str.to_string()
    } else if let Ok(ctx) = ctx_lock.lock() {
        ctx.base_dir.join(path_str).to_string_lossy().to_string()
    } else {
        path_str.to_string()
    };
    CString::new(output)
        .ok()
        .map(CString::into_raw)
        .unwrap_or(std::ptr::null_mut())
}

extern "C" fn lv2_state_make_path_callback(
    handle: *mut c_void,
    requested: *const c_char,
) -> *mut c_char {
    let Some(ctx_lock) = state_ctx_from_handle(handle) else {
        return std::ptr::null_mut();
    };

    let requested_name = if requested.is_null() {
        "state.bin".to_string()
    } else {
        (unsafe { CStr::from_ptr(requested) })
            .to_str()
            .ok()
            .filter(|s| !s.is_empty())
            .map(|s| s.replace("..", "_"))
            .unwrap_or_else(|| "state.bin".to_string())
    };

    let output = if let Ok(mut ctx) = ctx_lock.lock() {
        ctx.copy_counter = ctx.copy_counter.saturating_add(1);
        let file_name = format!("generated-{}-{}", ctx.copy_counter, requested_name);
        let path = ctx.base_dir.join("generated").join(file_name);
        if let Some(parent) = path.parent() {
            let _ = std::fs::create_dir_all(parent);
        }
        path.to_string_lossy().to_string()
    } else {
        requested_name
    };

    CString::new(output)
        .ok()
        .map(CString::into_raw)
        .unwrap_or(std::ptr::null_mut())
}

extern "C" fn lv2_state_store_callback(
    handle: Lv2StateHandle,
    key: u32,
    value: *const c_void,
    size: usize,
    type_: u32,
    flags: u32,
) -> Lv2StateStatus {
    if handle.is_null() || value.is_null() || size == 0 {
        return LV2_STATE_STATUS_ERR_NO_PROPERTY;
    }
    let ctx = unsafe { &mut *(handle as *mut StateSaveContext) };
    let bytes = unsafe { std::slice::from_raw_parts(value.cast::<u8>(), size) };
    ctx.properties.push(RawStateProperty {
        key,
        type_,
        flags,
        value: bytes.to_vec(),
    });
    LV2_STATE_STATUS_SUCCESS
}

extern "C" fn lv2_state_retrieve_callback(
    handle: Lv2StateHandle,
    key: u32,
    size: *mut usize,
    type_: *mut u32,
    flags: *mut u32,
) -> *const c_void {
    if handle.is_null() {
        return std::ptr::null();
    }
    let ctx = unsafe { &mut *(handle as *mut StateRestoreContext) };
    let Some(idx) = ctx.by_key.get(&key).copied() else {
        return std::ptr::null();
    };
    let Some(prop) = ctx.properties.get(idx) else {
        return std::ptr::null();
    };
    if !size.is_null() {
        unsafe {
            *size = prop.value.len();
        }
    }
    if !type_.is_null() {
        unsafe {
            *type_ = prop.type_;
        }
    }
    if !flags.is_null() {
        unsafe {
            *flags = prop.flags;
        }
    }
    prop.value.as_ptr().cast::<c_void>()
}

fn prepare_empty_atom_sequence(
    buffer: &mut [u8],
    sequence_urid: LV2Urid,
    frame_time_urid: LV2Urid,
) {
    buffer.fill(0);
    if buffer.len() < std::mem::size_of::<LV2AtomSequence>() {
        return;
    }
    let seq = buffer.as_mut_ptr() as *mut LV2AtomSequence;
    unsafe {
        (*seq).atom.mytype = sequence_urid;
        (*seq).atom.size = std::mem::size_of::<LV2AtomSequenceBody>() as u32;
        (*seq).body.unit = frame_time_urid;
        (*seq).body.pad = 0;
    }
}

fn prepare_output_atom_sequence(
    buffer: &mut [u8],
    sequence_urid: LV2Urid,
    frame_time_urid: LV2Urid,
) {
    buffer.fill(0);
    if buffer.len() < std::mem::size_of::<LV2AtomSequence>() {
        return;
    }
    let seq = buffer.as_mut_ptr() as *mut LV2AtomSequence;
    let body_capacity = buffer
        .len()
        .saturating_sub(std::mem::size_of::<lv2_raw::LV2Atom>()) as u32;
    unsafe {
        (*seq).atom.mytype = sequence_urid;
        (*seq).atom.size = body_capacity;
        (*seq).body.unit = frame_time_urid;
        (*seq).body.pad = 0;
    }
}

extern "C" fn urid_map_callback(handle: LV2UridMapHandle, uri: *const c_char) -> LV2Urid {
    if handle.is_null() || uri.is_null() {
        return 0;
    }
    let Some(uri_str) = unsafe { CStr::from_ptr(uri) }.to_str().ok() else {
        return 0;
    };

    let state_mutex = unsafe { &*(handle as *const Mutex<UridMapState>) };
    let Ok(mut state) = state_mutex.lock() else {
        return 0;
    };

    if let Some(existing) = state.by_uri.get(uri_str).copied() {
        return existing;
    }

    let mapped = state.next_urid;
    state.next_urid = state.next_urid.saturating_add(1);
    state.by_uri.insert(uri_str.to_string(), mapped);
    if let Ok(uri_c) = CString::new(uri_str) {
        state.by_urid.insert(mapped, uri_c);
    }
    mapped
}

extern "C" fn urid_unmap_callback(handle: LV2UridMapHandle, urid: LV2Urid) -> *const c_char {
    if handle.is_null() || urid == 0 {
        return std::ptr::null();
    }
    let state_mutex = unsafe { &*(handle as *const Mutex<UridMapState>) };
    let Ok(state) = state_mutex.lock() else {
        return std::ptr::null();
    };
    state
        .by_urid
        .get(&urid)
        .map(|uri| uri.as_ptr())
        .unwrap_or(std::ptr::null())
}

fn plugin_port_counts(
    plugin: &Plugin,
    input_port: &Node,
    output_port: &Node,
    audio_port: &Node,
    atom_port: &Node,
    event_port: &Node,
    midi_event: &Node,
) -> (usize, usize, usize, usize) {
    let mut audio_inputs = 0;
    let mut audio_outputs = 0;
    let mut midi_inputs = 0;
    let mut midi_outputs = 0;

    for port in plugin.iter_ports() {
        let is_input = port.is_a(input_port);
        let is_output = port.is_a(output_port);

        if port.is_a(audio_port) {
            if is_input {
                audio_inputs += 1;
            }
            if is_output {
                audio_outputs += 1;
            }
        }

        let is_event_or_atom = port.is_a(atom_port) || port.is_a(event_port);
        let is_midi = is_event_or_atom && port.supports_event(midi_event);
        if is_midi {
            if is_input {
                midi_inputs += 1;
            }
            if is_output {
                midi_outputs += 1;
            }
        }
    }

    (audio_inputs, audio_outputs, midi_inputs, midi_outputs)
}

fn count_main_audio_ports(
    plugin: &Plugin,
    main_group_predicate: &Node,
    port_group_predicate: &Node,
    audio_port: &Node,
    direction_port: &Node,
) -> Option<usize> {
    let main_groups: Vec<Node> = plugin.value(main_group_predicate).iter().collect();
    if main_groups.is_empty() {
        return None;
    }

    let count = plugin
        .iter_ports()
        .filter(|port| port.is_a(audio_port) && port.is_a(direction_port))
        .filter(|port| {
            port.get(port_group_predicate)
                .is_some_and(|group| main_groups.contains(&group))
        })
        .count();

    Some(count)
}

fn infer_missing_control_default(
    min: f32,
    max: f32,
    is_toggled: bool,
    is_discrete: bool,
) -> (f32, &'static str) {
    if !min.is_finite() || !max.is_finite() {
        return (0.0, "non-finite-range->0");
    }
    if is_toggled {
        let off = if min <= 0.0 && max >= 0.0 { 0.0 } else { min };
        return (off.clamp(min, max), "toggled->off");
    }
    if is_discrete {
        if min < 0.0 && max > 0.0 {
            return (0.0, "discrete-bipolar->0");
        }
        return (min, "discrete->min");
    }
    if min < 0.0 && max > 0.0 {
        return (0.0, "bipolar->0");
    }
    if min <= 1.0 && max >= 1.0 {
        return (1.0, "range-has-unity->1");
    }
    (min + (max - min) * 0.5, "midpoint")
}

fn lv2_node_to_f32(node: &Node) -> Option<f32> {
    if let Some(v) = node.as_float() {
        return Some(v);
    }
    if let Some(v) = node.as_int() {
        return Some(v as f32);
    }
    node.as_bool().map(|v| if v { 1.0 } else { 0.0 })
}
