"""Type stubs for minihost._core native extension module."""

from typing import Any, Callable

import numpy as np
from numpy.typing import NDArray

class Plugin:
    """Audio plugin wrapper for VST3 and AudioUnit plugins."""

    def __init__(
        self,
        path: str,
        sample_rate: float = 48000.0,
        max_block_size: int = 512,
        in_channels: int = 2,
        out_channels: int = 2,
        sidechain_channels: int = 0,
    ) -> None: ...
    @property
    def num_params(self) -> int: ...
    @property
    def num_input_channels(self) -> int: ...
    @property
    def num_output_channels(self) -> int: ...
    @property
    def latency_samples(self) -> int: ...
    @property
    def tail_seconds(self) -> float: ...
    @property
    def sidechain_channels(self) -> int: ...
    @property
    def num_input_buses(self) -> int: ...
    @property
    def num_output_buses(self) -> int: ...
    @property
    def sample_rate(self) -> float: ...
    @sample_rate.setter
    def sample_rate(self, value: float) -> None: ...
    @property
    def num_programs(self) -> int: ...
    @property
    def program(self) -> int: ...
    @program.setter
    def program(self, value: int) -> None: ...
    @property
    def bypass(self) -> bool: ...
    @bypass.setter
    def bypass(self, value: bool) -> None: ...
    @property
    def non_realtime(self) -> bool: ...
    @non_realtime.setter
    def non_realtime(self, value: bool) -> None: ...
    @property
    def supports_double(self) -> bool: ...
    @property
    def accepts_midi(self) -> bool: ...
    @property
    def produces_midi(self) -> bool: ...
    @property
    def is_midi_effect(self) -> bool: ...
    @property
    def supports_mpe(self) -> bool: ...
    @property
    def processing_precision(self) -> int: ...
    @processing_precision.setter
    def processing_precision(self, value: int) -> None: ...
    def get_bus_info(self, is_input: bool, bus_index: int) -> dict[str, Any]: ...
    def check_buses_layout(
        self, input_channels: list[int], output_channels: list[int]
    ) -> bool: ...
    def begin_param_gesture(self, index: int) -> None: ...
    def end_param_gesture(self, index: int) -> None: ...
    def get_program_state(self) -> bytes: ...
    def set_program_state(self, data: bytes) -> None: ...
    def set_change_callback(self, callback: Any) -> None: ...
    def set_param_value_callback(self, callback: Any) -> None: ...
    def set_param_gesture_callback(self, callback: Any) -> None: ...
    def set_track_properties(
        self, name: str | None = None, colour: int | None = None
    ) -> None: ...
    def get_param(self, index: int) -> float: ...
    def set_param(self, index: int, value: float) -> None: ...
    def get_param_info(self, index: int) -> dict[str, Any]: ...
    def param_to_text(self, index: int, value: float) -> str: ...
    def param_from_text(self, index: int, text: str) -> float: ...
    def get_program_name(self, index: int) -> str: ...
    def get_state(self) -> bytes: ...
    def set_state(self, data: bytes) -> None: ...
    def reset(self) -> None: ...
    def set_transport(
        self,
        bpm: float,
        time_sig_num: int = 4,
        time_sig_denom: int = 4,
        position_samples: int = 0,
        position_beats: float = 0.0,
        is_playing: bool = True,
        is_recording: bool = False,
        is_looping: bool = False,
        loop_start: int = 0,
        loop_end: int = 0,
    ) -> None: ...
    def clear_transport(self) -> None: ...
    def process(
        self,
        input: NDArray[np.float32],
        output: NDArray[np.float32],
    ) -> None: ...
    def process_midi(
        self,
        input: NDArray[np.float32],
        output: NDArray[np.float32],
        midi_in: list[tuple[int, int, int, int]],
    ) -> list[tuple[int, int, int, int]]: ...
    def process_auto(
        self,
        input: NDArray[np.float32],
        output: NDArray[np.float32],
        midi_in: list[tuple[int, int, int, int]],
        param_changes: list[tuple[int, int, float]],
    ) -> list[tuple[int, int, int, int]]: ...
    def process_sidechain(
        self,
        main_in: NDArray[np.float32],
        main_out: NDArray[np.float32],
        sidechain_in: NDArray[np.float32],
    ) -> None: ...
    def process_double(
        self,
        input: NDArray[np.float64],
        output: NDArray[np.float64],
    ) -> None: ...

class PluginChain:
    """Chain of audio plugins for sequential processing."""

    def __init__(self, plugins: list[Plugin]) -> None: ...
    @property
    def num_plugins(self) -> int: ...
    @property
    def latency_samples(self) -> int: ...
    @property
    def num_input_channels(self) -> int: ...
    @property
    def num_output_channels(self) -> int: ...
    @property
    def sample_rate(self) -> float: ...
    @property
    def tail_seconds(self) -> float: ...
    def get_plugin(self, index: int) -> Plugin: ...
    def reset(self) -> None: ...
    def set_non_realtime(self, non_realtime: bool) -> None: ...
    def process(
        self,
        input: NDArray[np.float32],
        output: NDArray[np.float32],
    ) -> None: ...
    def process_midi(
        self,
        input: NDArray[np.float32],
        output: NDArray[np.float32],
        midi_in: list[tuple[int, int, int, int]],
    ) -> list[tuple[int, int, int, int]]: ...

class AudioDevice:
    """Real-time audio device for plugin playback."""

    def __init__(
        self,
        plugin: Plugin | PluginChain,
        sample_rate: float = 0.0,
        buffer_frames: int = 0,
        output_channels: int = 0,
        midi_input_port: int = -1,
        midi_output_port: int = -1,
        capture: bool = False,
    ) -> None: ...
    @property
    def is_playing(self) -> bool: ...
    @property
    def sample_rate(self) -> float: ...
    @property
    def buffer_frames(self) -> int: ...
    @property
    def channels(self) -> int: ...
    @property
    def midi_input_port(self) -> int: ...
    @property
    def midi_output_port(self) -> int: ...
    @property
    def is_midi_input_virtual(self) -> bool: ...
    @property
    def is_midi_output_virtual(self) -> bool: ...
    def start(self) -> None: ...
    def stop(self) -> None: ...
    def connect_midi_input(self, port_index: int) -> None: ...
    def connect_midi_output(self, port_index: int) -> None: ...
    def disconnect_midi_input(self) -> None: ...
    def disconnect_midi_output(self) -> None: ...
    def create_virtual_midi_input(self, port_name: str) -> None: ...
    def create_virtual_midi_output(self, port_name: str) -> None: ...
    def send_midi(self, status: int, data1: int, data2: int) -> None: ...
    def enable_input(self, capacity_frames: int = 0) -> None: ...
    def disable_input(self) -> None: ...
    def write_input(self, data: NDArray[np.float32]) -> int: ...
    @property
    def input_available(self) -> int: ...
    def __enter__(self) -> "AudioDevice": ...
    def __exit__(self, *args: object) -> None: ...

class MidiFile:
    """MIDI file reader/writer."""

    def __init__(self) -> None: ...
    @property
    def num_tracks(self) -> int: ...
    @property
    def ticks_per_quarter(self) -> int: ...
    @ticks_per_quarter.setter
    def ticks_per_quarter(self, value: int) -> None: ...
    @property
    def duration_seconds(self) -> float: ...
    def load(self, path: str) -> bool: ...
    def save(self, path: str) -> bool: ...
    def add_track(self) -> int: ...
    def add_tempo(self, track: int, tick: int, bpm: float) -> None: ...
    def add_note_on(
        self, track: int, tick: int, channel: int, pitch: int, velocity: int
    ) -> None: ...
    def add_note_off(
        self, track: int, tick: int, channel: int, pitch: int, velocity: int = 0
    ) -> None: ...
    def add_control_change(
        self, track: int, tick: int, channel: int, controller: int, value: int
    ) -> None: ...
    def add_program_change(
        self, track: int, tick: int, channel: int, program: int
    ) -> None: ...
    def add_pitch_bend(
        self, track: int, tick: int, channel: int, value: int
    ) -> None: ...
    def get_events(self, track: int) -> list[dict[str, Any]]: ...
    def make_absolute_ticks(self) -> None: ...
    def make_delta_ticks(self) -> None: ...
    def join_tracks(self) -> None: ...
    def split_tracks(self) -> None: ...

class MidiIn:
    """Standalone MIDI input for monitoring MIDI messages."""

    @staticmethod
    def open(port_index: int, callback: Callable[[bytes], None]) -> "MidiIn": ...
    @staticmethod
    def open_virtual(name: str, callback: Callable[[bytes], None]) -> "MidiIn": ...
    def close(self) -> None: ...
    def __enter__(self) -> "MidiIn": ...
    def __exit__(self, *args: object) -> None: ...

def probe(path: str) -> dict[str, Any]:
    """Get plugin metadata without full instantiation."""
    ...

def scan_directory(directory_path: str) -> list[dict[str, Any]]:
    """Scan a directory for plugins (VST3, AudioUnit)."""
    ...

def midi_get_input_ports() -> list[dict[str, Any]]:
    """Get list of available MIDI input ports."""
    ...

def midi_get_output_ports() -> list[dict[str, Any]]:
    """Get list of available MIDI output ports."""
    ...

MH_CHANGE_LATENCY: int
MH_CHANGE_PARAM_INFO: int
MH_CHANGE_PROGRAM: int
MH_CHANGE_NON_PARAM_STATE: int
MH_PRECISION_SINGLE: int
MH_PRECISION_DOUBLE: int

def audio_read(path: str) -> tuple[NDArray[np.float32], int]:
    """Read an audio file. Returns (data, sample_rate) where data has shape (channels, frames)."""
    ...

def audio_write(
    path: str,
    data: NDArray[np.float32],
    sample_rate: int,
    bit_depth: int = 24,
) -> None:
    """Write audio data to a WAV file. Data shape: (channels, frames)."""
    ...

def audio_resample(
    data: NDArray[np.float32],
    sample_rate_in: int,
    sample_rate_out: int,
) -> NDArray[np.float32]:
    """Resample audio data. Input/output shape: (channels, frames)."""
    ...

def audio_get_file_info(path: str) -> dict[str, Any]:
    """Get audio file metadata without decoding."""
    ...
