//! Safe, high-level audio/video/image processing for Rust.
//!
//! `avio` is the facade crate for the `ff-*` crate family — a backend-agnostic
//! multimedia toolkit. It re-exports the public APIs of all member crates behind
//! feature flags, so users can depend on a single crate and opt into only the
//! functionality they need.
//!
//! # Feature Flags
//!
//! | Feature    | Crate         | Default | Implies             |
//! |------------|---------------|---------|---------------------|
//! | `probe`    | `ff-probe`    | yes     | —                   |
//! | `decode`   | `ff-decode`   | yes     | —                   |
//! | `encode`   | `ff-encode`   | yes     | —                   |
//! | `filter`   | `ff-filter`   | no      | —                   |
//! | `pipeline` | `ff-pipeline` | no      | `filter`            |
//! | `stream`   | `ff-stream`   | no      | `pipeline`          |
//! | `tokio`    | ff-decode/encode | no   | `decode` + `encode` |
//!
//! # Usage
//!
//! ```toml
//! # Default: probe + decode + encode
//! [dependencies]
//! avio = "0.13"
//!
//! # Add filtering
//! avio = { version = "0.13", features = ["filter"] }
//!
//! # Full stack (implies filter + pipeline)
//! avio = { version = "0.13", features = ["stream"] }
//! ```
//!
//! # Quick Start
//!
//! ## Probe
//!
//! [`open`] is a free function (not a method) that reads metadata without
//! decoding:
//!
//! ```ignore
//! use avio::open;
//!
//! let info = open("video.mp4")?;
//! println!("duration: {:?}", info.duration());
//! ```
//!
//! ## Decode
//!
//! All decoders follow the same builder pattern. Use
//! `.output_format()` / `.output_sample_rate()` to request automatic
//! format conversion inside the decoder:
//!
//! ```ignore
//! use avio::{VideoDecoder, AudioDecoder, PixelFormat, SampleFormat};
//!
//! // Video — request RGB24 output (FFmpeg converts internally)
//! let mut vdec = VideoDecoder::open("video.mp4")
//!     .output_format(PixelFormat::Rgb24)
//!     .build()?;
//! for result in &mut vdec { /* ... */ }
//!
//! // Audio — resample to 16-bit 44.1 kHz
//! let mut adec = AudioDecoder::open("video.mp4")
//!     .output_format(SampleFormat::I16)
//!     .output_sample_rate(44_100)
//!     .build()?;
//! ```
//!
//! ## Encode
//!
//! There are three encode APIs, each suited to a different situation.
//! Choosing the right one prevents unnecessary complexity.
//!
//! ### When to use `Pipeline` (feature: `pipeline`)
//!
//! Use `Pipeline` when your source is an **existing media file** and you want
//! to transcode, filter, or repackage it with minimal boilerplate.
//!
//! - You are transcoding a file to another codec or container.
//! - You want to apply filters (scale, trim, fade, tone-map, …).
//! - You want to concatenate multiple input files.
//! - You need progress reporting without managing the decode loop yourself.
//! - You are generating HLS or DASH output (`stream` feature).
//!
//! ```ignore
//! use avio::{Pipeline, EncoderConfig, VideoCodec, AudioCodec, BitrateMode};
//!
//! Pipeline::builder()
//!     .input("input.mp4")
//!     .output("output.mp4", EncoderConfig::builder()
//!         .video_codec(VideoCodec::H264)
//!         .audio_codec(AudioCodec::Aac)
//!         .bitrate_mode(BitrateMode::Crf(23))
//!         .build())
//!     .build()?
//!     .run()?;
//! ```
//!
//! **Examples:** `transcode`, `trim_and_scale`, `concat_clips`,
//! `extract_thumbnails`, `hls_output`, `abr_ladder`.
//!
//! ### When to use `VideoEncoder` / `AudioEncoder` directly (feature: `encode`)
//!
//! Use the encoder types directly when you need **frame-level control** or
//! your frames come from a source other than a media file.
//!
//! - You are generating frames programmatically (e.g., a game renderer,
//!   a signal generator, test patterns).
//! - You need to inspect or modify individual frames between decode and encode.
//! - You want per-frame metadata, custom PTS/DTS, or non-standard GOP structure.
//! - You need to react to `EncodeError::Cancelled` mid-stream.
//! - You want cancellable progress via `EncodeProgressCallback::should_cancel()`.
//!
//! ```ignore
//! use avio::{VideoDecoder, VideoEncoder, VideoCodec};
//!
//! let mut decoder = VideoDecoder::open("input.mp4").build()?;
//! let mut encoder = VideoEncoder::create("output.mp4")
//!     .video(decoder.width(), decoder.height(), decoder.frame_rate())
//!     .video_codec(VideoCodec::H264)
//!     .build()?;
//!
//! while let Ok(Some(frame)) = decoder.decode_one() {
//!     // Inspect or modify `frame` here before encoding.
//!     encoder.push_video(&frame)?;
//! }
//! encoder.finish()?;
//! ```
//!
//! **Examples:** `encode_video_direct`, `encode_audio_direct`,
//! `encode_with_progress`, `two_pass_encode`, `filter_direct`.
//!
//! ### When to use `AsyncVideoEncoder` / `AsyncAudioEncoder` (feature: `tokio`)
//!
//! Use the async encoders when your application runs on a **Tokio runtime**
//! and you need back-pressure or concurrent decode/encode.
//!
//! - You are writing an async application and cannot block the executor.
//! - Frames arrive from an async source (network, channel, microphone).
//! - You want the decoder and encoder to run concurrently on separate tasks.
//! - You rely on the bounded internal channel (capacity 8) to prevent
//!   unbounded memory growth when the encoder is slower than the producer.
//!
//! ```ignore
//! use avio::{AsyncVideoDecoder, AsyncVideoEncoder, VideoEncoder, VideoCodec};
//! use futures::StreamExt;
//!
//! let mut encoder = AsyncVideoEncoder::from_builder(
//!     VideoEncoder::create("output.mp4")
//!         .video(1920, 1080, 30.0)
//!         .video_codec(VideoCodec::H264),
//! )?;
//!
//! let stream = AsyncVideoDecoder::open("input.mp4").await?.into_stream();
//! tokio::pin!(stream);
//! while let Some(Ok(frame)) = stream.next().await {
//!     encoder.push(frame).await?;
//! }
//! encoder.finish().await?;
//! ```
//!
//! **Examples:** `async_encode_video`, `async_encode_audio`, `async_transcode`.
//!
//! # Real-world Applications
//!
//! ## ascii-term — Terminal ASCII Art Video Player
//!
//! [`ascii-term`](https://github.com/itsakeyfut/ascii-term) is a terminal media player
//! that renders video as colored ASCII art with synchronized audio. It was fully migrated
//! from `ffmpeg-next` / `ffmpeg-sys-next` to `avio`, with no direct `unsafe` `FFmpeg` code
//! remaining in the application.
//!
//! Key patterns used:
//!
//! - [`VideoDecoder`] with `.output_format(PixelFormat::Rgb24)` for per-pixel luminance
//! - [`AudioDecoder`] with [`SampleFormat::F32`] output, converted to interleaved PCM for
//!   [`rodio`](https://crates.io/crates/rodio) playback
//! - Dual-thread A/V sync via `crossbeam-channel`
//!
//! ### Extension trait
//!
//! `VideoCodecEncodeExt` adds encode-specific helpers (`.default_extension()`,
//! `.is_lgpl_compatible()`) to `VideoCodec`. Import the trait to call them:
//!
//! ```ignore
//! use avio::{VideoCodec, VideoCodecEncodeExt};
//!
//! let ext = VideoCodec::H264.default_extension(); // "mp4"
//! ```

// ── Always-available types from ff-format ────────────────────────────────────
//
// ff-format is an unconditional dependency, so these types are always present
// regardless of which features are enabled. Re-exporting them here avoids the
// duplicate-symbol problem that would arise from re-exporting VideoCodec /
// AudioCodec separately from ff-probe *and* ff-encode (both of which pull them
// in from ff-format anyway).
pub use ff_format::subtitle::{SubtitleError, SubtitleEvent, SubtitleTrack};
pub use ff_format::{
    AudioCodec, AudioFrame, AudioStreamInfo, ChannelLayout, ChapterInfo, ChapterInfoBuilder,
    ColorPrimaries, ColorRange, ColorSpace, ColorTransfer, ContainerInfo, Hdr10Metadata,
    MasteringDisplay, MediaInfo, MediaInfoBuilder, NetworkOptions, PixelFormat, Rational,
    SampleFormat, SubtitleCodec, SubtitleStreamInfo, Timestamp, VideoCodec, VideoFrame,
    VideoStreamInfo,
};

// ── probe feature ─────────────────────────────────────────────────────────────
#[cfg(feature = "probe")]
pub use ff_probe::{ProbeError, open};

// ── decode feature ────────────────────────────────────────────────────────────
//
// Frame/codec types are already re-exported from ff-format above, so we omit
// them here to keep a single canonical source.
// Memory pooling: VecPool is the concrete pool implementation; FramePool is the
// trait for accepting custom pool implementations. Use VecPool directly, or
// Arc<dyn FramePool> when you need to pass a pool through an abstraction boundary.
#[cfg(feature = "decode")]
pub use ff_common::VecPool;
#[cfg(feature = "decode")]
pub use ff_decode::{
    AudioDecoder, BlackFrameDetector, DecodeError, FrameExtractor, FrameHistogram, FramePool,
    HardwareAccel, HistogramExtractor, ImageDecoder, KeyframeEnumerator, SceneDetector, SeekMode,
    SilenceDetector, SilenceRange, ThumbnailSelector, VideoDecoder, WaveformAnalyzer,
    WaveformSample,
};

// ── encode feature ────────────────────────────────────────────────────────────
//
// EncodeProgress / EncodeProgressCallback carry encode-specific metrics and are
// distinct from ff-pipeline's Progress / ProgressCallback, so both sets can be
// re-exported from avio without ambiguity.
// VideoCodecEncodeExt provides encode-specific helpers (is_lgpl_compatible,
// default_extension) on the shared VideoCodec type; import it to call them.
#[cfg(feature = "encode")]
pub use ff_encode::{
    AacOptions, AacProfile, AudioAdder, AudioCodecOptions, AudioEncoder, AudioEncoderConfig,
    AudioExtractor, AudioReplacement, Av1Options, Av1Usage, BitrateMode, CRF_MAX, DnxhdOptions,
    DnxhdVariant, EncodeError, EncodeProgress, EncodeProgressCallback, ExportPreset, FlacOptions,
    GifPreview, H264Options, H264Preset, H264Profile, H264Tune, H265Options, H265Profile, H265Tier,
    HardwareEncoder, ImageEncoder, Mp3Options, Mp3Quality, OpusApplication, OpusOptions,
    OutputContainer, Preset, ProResOptions, ProResProfile, SpriteSheet, StreamCopyTrim,
    StreamCopyTrimmer, SvtAv1Options, VideoCodecEncodeExt, VideoCodecOptions, VideoEncoder,
    VideoEncoderConfig, Vp9Options,
};

// ── tokio feature ─────────────────────────────────────────────────────────────
//
// Enabling `tokio` also enables `decode` and `encode` (see Cargo.toml), so the
// underlying crate dependencies are guaranteed to be present. Each async wrapper
// is a thin Send + async shell around its synchronous counterpart, backed by
// spawn_blocking and a bounded tokio::sync::mpsc channel (encoders, cap=8).
#[cfg(feature = "tokio")]
pub use ff_decode::{AsyncAudioDecoder, AsyncImageDecoder, AsyncVideoDecoder};
#[cfg(feature = "tokio")]
pub use ff_encode::{AsyncAudioEncoder, AsyncVideoEncoder};

// ── filter feature ────────────────────────────────────────────────────────────
#[cfg(feature = "filter")]
pub use ff_filter::{
    AnimatedValue, AnimationEntry, AnimationTrack, AudioConcatenator, AudioTrack, BlendMode,
    ClipJoiner, DrawTextOptions, Easing, EqBand, FilterError, FilterGraph, FilterGraphBuilder,
    FilterStep, HwAccel, Keyframe, Lerp, LoudnessMeter, LoudnessResult, MultiTrackAudioMixer,
    MultiTrackComposer, QualityMetrics, Rgb, ScaleAlgorithm, ToneMap, VideoConcatenator,
    VideoLayer, XfadeTransition, YadifMode,
};

// ── pipeline feature ──────────────────────────────────────────────────────────
//
// Enabling `pipeline` also enables `filter` (see Cargo.toml).
// Progress / ProgressCallback are re-exported here as the canonical source.
#[cfg(feature = "pipeline")]
pub use ff_pipeline::{
    AudioPipeline, Clip, EncoderConfig, EncoderConfigBuilder, Pipeline, PipelineBuilder,
    PipelineError, Progress, ProgressCallback, ThumbnailPipeline, Timeline, TimelineBuilder,
    VideoPipeline,
};

// ── stream feature ────────────────────────────────────────────────────────────
//
// Enabling `stream` also enables `pipeline` (and transitively `filter`).
#[cfg(feature = "stream")]
pub use ff_stream::{
    AbrLadder, AbrRendition, DashOutput, FanoutOutput, HlsOutput, HlsSegmentFormat, LiveAbrFormat,
    LiveAbrLadder, LiveDashOutput, LiveHlsOutput, Rendition, RtmpOutput, StreamError, StreamOutput,
};

#[cfg(all(feature = "stream", feature = "srt"))]
pub use ff_stream::SrtOutput;

// ── preview feature ───────────────────────────────────────────────────────────
//
// Single-file real-time playback with frame-accurate seek and A/V sync.
// Enable the `preview` feature to access `PreviewPlayer`, `PlaybackClock`,
// and the `RgbaSink` / `RgbaFrame` helpers.
// Enable `preview-proxy` to additionally access `ProxyGenerator`.
#[cfg(feature = "preview")]
pub use ff_preview::{
    DecodeBuffer, DecodeBufferBuilder, FrameResult, FrameSink, PlaybackClock, PreviewError,
    PreviewPlayer, RgbaFrame, RgbaSink, SeekEvent,
};

#[cfg(all(feature = "preview", feature = "tokio"))]
pub use ff_preview::AsyncPreviewPlayer;

#[cfg(feature = "preview-proxy")]
pub use ff_preview::{ProxyGenerator, ProxyJob, ProxyResolution};

#[cfg(test)]
mod tests {
    use super::*;

    // ── ff-format (always-on) ─────────────────────────────────────────────────

    #[test]
    fn format_reexports_should_be_accessible() {
        let _: VideoCodec = VideoCodec::default();
        let _: AudioCodec = AudioCodec::default();
        let _: PixelFormat = PixelFormat::default();
        let _: SampleFormat = SampleFormat::default();
        let _: ChannelLayout = ChannelLayout::default();
        let _: ColorSpace = ColorSpace::default();
        let _: ColorRange = ColorRange::default();
        let _: ColorPrimaries = ColorPrimaries::default();
        let _: Rational = Rational::default();
        let _: Timestamp = Timestamp::default();
        let _: MediaInfo = MediaInfo::default();
        let _: NetworkOptions = NetworkOptions::default();
    }

    // ── probe feature ─────────────────────────────────────────────────────────

    #[cfg(feature = "probe")]
    #[test]
    fn probe_open_should_be_accessible() {
        // open is a function — a non-existent path yields ProbeError
        let result = open("/no/such/file.mp4");
        assert!(matches!(result, Err(ProbeError::FileNotFound { .. })));
    }

    #[cfg(feature = "probe")]
    #[test]
    fn probe_error_should_be_accessible() {
        let err = ProbeError::FileNotFound {
            path: std::path::PathBuf::from("missing.mp4"),
        };
        assert!(err.to_string().contains("missing.mp4"));
    }

    // ── decode feature ────────────────────────────────────────────────────────

    #[cfg(feature = "decode")]
    #[test]
    fn decode_builder_types_should_be_accessible() {
        // Builder entry points are static methods on the decoder types.
        // Calling them with a dummy path exercises name resolution without
        // touching FFmpeg.
        let _ = VideoDecoder::open("/no/such/file.mp4");
        let _ = AudioDecoder::open("/no/such/file.mp4");
        let _ = ImageDecoder::open("/no/such/file.mp4");
    }

    #[cfg(feature = "decode")]
    #[test]
    fn decode_error_should_be_accessible() {
        let _: DecodeError = DecodeError::decoding_failed("test");
    }

    #[cfg(feature = "decode")]
    #[test]
    fn decode_seek_mode_should_be_accessible() {
        let _: SeekMode = SeekMode::Keyframe;
        let _: SeekMode = SeekMode::Exact;
        let _: SeekMode = SeekMode::Backward;
    }

    #[cfg(feature = "decode")]
    #[test]
    fn decode_hardware_accel_should_be_accessible() {
        let _: HardwareAccel = HardwareAccel::Auto;
        let _: HardwareAccel = HardwareAccel::None;
    }

    #[cfg(feature = "decode")]
    #[test]
    fn decode_vec_pool_should_be_accessible() {
        let pool: std::sync::Arc<VecPool> = VecPool::new(8);
        assert_eq!(pool.capacity(), 8);
        assert_eq!(pool.available(), 0);
    }

    // ── encode feature ────────────────────────────────────────────────────────

    #[cfg(feature = "encode")]
    #[test]
    fn encode_builder_types_should_be_accessible() {
        // VideoEncoder::create / AudioEncoder::create are the public entry
        // points that return their respective builder types.
        let _ = VideoEncoder::create("/tmp/out.mp4");
        let _ = AudioEncoder::create("/tmp/out.mp3");
    }

    #[cfg(feature = "encode")]
    #[test]
    fn encode_bitrate_mode_should_be_accessible() {
        let _: BitrateMode = BitrateMode::Cbr(1_000_000);
        let _: BitrateMode = BitrateMode::Crf(23);
    }

    #[cfg(feature = "encode")]
    #[test]
    fn encode_error_should_be_accessible() {
        let _: EncodeError = EncodeError::Cancelled;
    }

    #[cfg(feature = "encode")]
    #[test]
    fn encode_progress_should_be_accessible() {
        assert!(std::mem::size_of::<EncodeProgress>() > 0);
    }

    #[cfg(feature = "encode")]
    #[test]
    fn encode_progress_callback_should_be_accessible() {
        // EncodeProgressCallback is a trait — verify it is in scope by creating
        // a minimal no-op implementation.
        struct NoOp;
        impl EncodeProgressCallback for NoOp {
            fn on_progress(&mut self, _: &EncodeProgress) {}
        }
        let _ = NoOp;
    }

    // ── tokio feature ─────────────────────────────────────────────────────────

    #[cfg(feature = "tokio")]
    #[test]
    fn tokio_async_decoders_should_be_accessible() {
        // Verify name resolution — constructing the builder/future without
        // opening a file is enough to confirm the types are in scope.
        let _ = AsyncVideoDecoder::open("/no/such/file.mp4");
        let _ = AsyncAudioDecoder::open("/no/such/file.mp4");
        let _ = AsyncImageDecoder::open("/no/such/file.mp4");
    }

    #[cfg(feature = "tokio")]
    #[test]
    fn tokio_async_encoders_should_be_accessible() {
        // from_builder consumes a builder; constructing the builder (which is
        // a sync operation) confirms the types are in scope without touching FFmpeg.
        use ff_encode::{AudioEncoderBuilder, VideoEncoderBuilder};
        fn _accepts_video_builder(_: VideoEncoderBuilder) {}
        fn _accepts_audio_builder(_: AudioEncoderBuilder) {}
        // The types compile — that is the assertion.
        let _ = std::mem::size_of::<AsyncVideoEncoder>();
        let _ = std::mem::size_of::<AsyncAudioEncoder>();
    }

    // ── filter feature ────────────────────────────────────────────────────────

    #[cfg(feature = "filter")]
    #[test]
    fn filter_graph_builder_should_be_accessible() {
        // FilterGraphBuilder::new() is the public entry point.
        let _builder: FilterGraphBuilder = FilterGraphBuilder::new();
    }

    #[cfg(feature = "filter")]
    #[test]
    fn filter_tone_map_should_be_accessible() {
        let _: ToneMap = ToneMap::Hable;
        let _: ToneMap = ToneMap::Reinhard;
        let _: ToneMap = ToneMap::Mobius;
    }

    #[cfg(feature = "filter")]
    #[test]
    fn filter_hw_accel_should_be_accessible() {
        let _: HwAccel = HwAccel::Cuda;
        let _: HwAccel = HwAccel::VideoToolbox;
    }

    #[cfg(feature = "filter")]
    #[test]
    fn filter_error_should_be_accessible() {
        let _: FilterError = FilterError::BuildFailed;
        let _: FilterError = FilterError::ProcessFailed;
    }

    // ── pipeline feature ──────────────────────────────────────────────────────

    #[cfg(feature = "pipeline")]
    #[test]
    fn pipeline_builder_should_be_accessible() {
        // Pipeline::builder() is the public entry point; verify name resolution.
        let _builder: PipelineBuilder = Pipeline::builder();
    }

    #[cfg(feature = "pipeline")]
    #[test]
    fn pipeline_error_should_be_accessible() {
        let _: PipelineError = PipelineError::NoInput;
        let _: PipelineError = PipelineError::NoOutput;
        let _: PipelineError = PipelineError::Cancelled;
    }

    #[cfg(feature = "pipeline")]
    #[test]
    fn pipeline_progress_should_be_accessible() {
        let p = Progress {
            frames_processed: 10,
            total_frames: Some(100),
            elapsed: std::time::Duration::from_secs(1),
        };
        assert_eq!(p.percent(), Some(10.0));
    }

    #[cfg(feature = "pipeline")]
    #[test]
    fn pipeline_progress_callback_should_be_accessible() {
        // ProgressCallback is Box<dyn Fn(&Progress) -> bool + Send>.
        let _cb: ProgressCallback = Box::new(|_: &Progress| true);
    }

    #[cfg(feature = "pipeline")]
    #[test]
    fn pipeline_thumbnail_pipeline_should_be_accessible() {
        // ThumbnailPipeline::new constructs without opening a file.
        let _t: ThumbnailPipeline = ThumbnailPipeline::new("/no/such/file.mp4");
    }

    #[cfg(feature = "pipeline")]
    #[test]
    fn pipeline_audio_pipeline_should_be_accessible() {
        let _: AudioPipeline = AudioPipeline::new();
    }

    #[cfg(all(feature = "pipeline", feature = "encode"))]
    #[test]
    fn pipeline_encoder_config_should_be_accessible() {
        let _config = EncoderConfig::builder()
            .video_codec(VideoCodec::H264)
            .audio_codec(AudioCodec::Aac)
            .bitrate_mode(BitrateMode::Cbr(4_000_000))
            .build();
    }

    // ── stream feature ────────────────────────────────────────────────────────

    #[cfg(feature = "stream")]
    #[test]
    fn stream_hls_output_should_be_accessible() {
        // HlsOutput::new() is the public entry point; verify name resolution.
        let _hls: HlsOutput = HlsOutput::new("/tmp/hls");
    }

    #[cfg(feature = "stream")]
    #[test]
    fn stream_dash_output_should_be_accessible() {
        // DashOutput::new() is the public entry point; verify name resolution.
        let _dash: DashOutput = DashOutput::new("/tmp/dash");
    }

    #[cfg(feature = "stream")]
    #[test]
    fn stream_abr_ladder_should_be_accessible() {
        // AbrLadder::new() is the public entry point; verify name resolution.
        let _ladder: AbrLadder = AbrLadder::new("/no/such/file.mp4");
    }

    #[cfg(feature = "stream")]
    #[test]
    fn stream_rendition_should_be_accessible() {
        let _r: Rendition = Rendition {
            width: 1280,
            height: 720,
            bitrate: 3_000_000,
        };
    }

    #[cfg(feature = "stream")]
    #[test]
    fn stream_error_should_be_accessible() {
        let _err: StreamError = StreamError::InvalidConfig {
            reason: "test".into(),
        };
    }

    // ── preview feature ───────────────────────────────────────────────────────

    #[cfg(feature = "preview")]
    #[test]
    fn preview_rgba_types_should_be_accessible() {
        // RgbaSink and RgbaFrame are the concrete sink/frame types added in v0.13.0.
        let _: Option<RgbaSink> = None;
        let _: Option<RgbaFrame> = None;
    }

    #[cfg(all(feature = "preview", feature = "tokio"))]
    #[test]
    fn preview_async_player_should_be_accessible() {
        // Confirm AsyncPreviewPlayer is in scope under the combined feature gate.
        let _ = std::mem::size_of::<AsyncPreviewPlayer>();
    }

    #[cfg(feature = "preview-proxy")]
    #[test]
    fn preview_proxy_types_should_be_accessible() {
        // ProxyResolution variants cover Quarter / Half / Eighth.
        let _: ProxyResolution = ProxyResolution::Half;
        let _: ProxyResolution = ProxyResolution::Quarter;
        let _: ProxyResolution = ProxyResolution::Eighth;
    }
}
