//! Core abstractions shared by PixelFlow crates.

pub mod core;
pub mod error;
pub mod filter;
pub mod format;
pub mod frame;
pub mod graph;
pub mod log;
pub mod metadata;
pub mod plugin_abi;
pub mod plugin_host;
pub mod render;
pub mod scheduler;
pub mod source;
pub mod y4m;

pub use core::{Core, CoreConfig};
pub use error::{ErrorCategory, ErrorCode, PixelFlowError, Result};
pub use filter::{
    FilterDescriptor, FilterOptionValue, FilterOptions, FilterPlan, FilterPlanRequest,
    FilterPlanner, FilterRegistry,
};
pub use format::{
    ChromaSiting, ChromaSubsampling, ColorMatrix, ColorPrimaries, ColorRange, ColorTransfer,
    FormatDescriptor, FormatFamily, PlaneDescriptor, PlaneRole, SampleType, format_with_bit_depth,
    resolve_format_alias,
};
pub use frame::{
    AllocatorConfig, Frame, FrameBuilder, Plane, PlaneMut, PlaneRows, RawPlane, RawPlaneMut, Sample,
};
pub use graph::{
    Clip, ClipFormat, ClipMedia, ClipResolution, FilterChangeSet, FilterCompatibility, FrameCount,
    FrameRate, Graph, GraphBuilder, GraphNode, NodeId, NodeKind, ValidatedGraph, ValidationPlan,
    is_y4m_compatible_format,
};
pub use log::{LogLevel, LogRecord, LogSink, Logger, NoopLogSink};
pub use metadata::{Metadata, MetadataKind, MetadataSchema, MetadataValue, Rational};
pub use plugin_abi::{
    PIXELFLOW_ABI_VERSION, PIXELFLOW_PLUGIN_ENTRY_SYMBOL, PixelflowErrorCategory,
    PixelflowFilterDescriptorV1, PixelflowHostApiV1, PixelflowMetadataKind, PixelflowPluginApiV1,
    PixelflowPluginEntryV1, PixelflowRegistrar, PixelflowStatus, PixelflowStringView,
};
pub use plugin_host::{
    LoadedPlugin, is_dynamic_library, load_plugins_from_directories, platform_plugin_directories,
};
pub use render::{
    FrameExecutor, FrameRequest, OrderedRender, RenderEngine, RenderExecutorMap, RenderOptions,
    RenderRange,
};
pub use scheduler::{
    ConcurrencyClass, DependencyPattern, DynamicDependencyBounds, FilterTiming, SourceCapabilities,
    TimingReport, WorkerPoolConfig,
};
pub use source::{SourceOptionValue, SourceRequest};
pub use y4m::{Y4mWriter, y4m_chroma_tag};

/// Returns the `pixelflow-core` crate version.
#[must_use]
pub const fn version() -> &'static str {
    env!("CARGO_PKG_VERSION")
}

#[cfg(test)]
mod tests {
    #![expect(clippy::indexing_slicing, reason = "allow in tests")]

    use std::sync::{Arc, Mutex};

    use super::{
        AllocatorConfig, ErrorCategory, ErrorCode, LogLevel, LogRecord, LogSink, Logger,
        MetadataSchema, PixelFlowError, resolve_format_alias,
    };

    #[test]
    fn version_matches_package_version() {
        assert_eq!(super::version(), env!("CARGO_PKG_VERSION"));
    }

    #[test]
    fn structured_error_preserves_category_code_and_message() {
        let error = PixelFlowError::new(
            ErrorCategory::Graph,
            ErrorCode::new("graph.cycle"),
            "graph contains a cycle",
        );

        assert_eq!(error.category(), ErrorCategory::Graph);
        assert_eq!(error.code().as_str(), "graph.cycle");
        assert_eq!(error.message(), "graph contains a cycle");
        assert_eq!(
            error.to_string(),
            "graph error graph.cycle: graph contains a cycle"
        );
    }

    #[test]
    fn logger_forwards_records_to_sink() {
        #[derive(Default)]
        struct RecordingSink {
            records: Mutex<Vec<LogRecord>>,
        }

        impl LogSink for RecordingSink {
            fn log(&self, record: &LogRecord) {
                self.records
                    .lock()
                    .expect("record lock poisoned")
                    .push(record.clone());
            }
        }

        let sink = Arc::new(RecordingSink::default());
        let logger = Logger::new(sink.clone());

        logger.log(LogLevel::Warn, "pixelflow_core::tests", "cache disabled");

        let records = sink.records.lock().expect("record lock poisoned");
        assert_eq!(records.len(), 1);
        assert_eq!(records[0].level(), LogLevel::Warn);
        assert_eq!(records[0].target(), "pixelflow_core::tests");
        assert_eq!(records[0].message(), "cache disabled");
    }

    #[test]
    fn default_logger_discards_records() {
        let logger = Logger::default();

        logger.log(LogLevel::Info, "pixelflow_core::tests", "startup");
    }

    #[test]
    fn public_modules_are_reachable() {
        let format = resolve_format_alias("yuv420p10").expect("format alias should resolve");
        let schema = MetadataSchema::core();
        let allocator = AllocatorConfig::default();
        let media = super::ClipMedia::fixed(
            format.clone(),
            1920,
            1080,
            24,
            super::Rational {
                numerator: 24_000,
                denominator: 1_001,
            },
        );

        assert_eq!(format.name(), "yuv420p10");
        assert!(schema.contains_key("core:matrix"));
        assert!(allocator.actual_alignment() >= 64);
        assert!(super::is_y4m_compatible_format(&format));
        assert!(matches!(media.frame_count(), super::FrameCount::Finite(24)));
        assert_eq!(super::ColorMatrix::Bt709.as_str(), "bt709");
        assert_eq!(super::ColorTransfer::Pq.as_str(), "pq");
        assert_eq!(super::ColorPrimaries::DisplayP3.as_str(), "display_p3");
        assert_eq!(super::ChromaSiting::Center.offsets(), (0.5, 0.5));
    }
}
