cadmpeg-ir 0.6.5

CAD data model, validation, diffing, and codec API for cadmpeg.
// SPDX-License-Identifier: Apache-2.0
//! Borrowed product identities and links during staged source admission.

use std::borrow::Cow;

use super::NativeRecord;
use crate::document::CadIr;
use crate::unknown::UnknownRecord;

#[derive(Clone, Copy)]
pub(crate) enum NativeEntity<'a> {
    Product(&'a NativeRecord),
    Source(&'a UnknownRecord),
}

impl<'a> NativeEntity<'a> {
    pub(crate) fn id(self) -> &'a str {
        match self {
            Self::Product(record) => record.id(),
            Self::Source(record) => record.id().as_str(),
        }
    }

    /// The string members of this entity's links. A product record parses
    /// them from its text, so they are owned; source links are borrowed.
    pub(crate) fn links(self) -> impl Iterator<Item = Cow<'a, str>> {
        let product = match self {
            Self::Product(record) => match record.field("links") {
                Some(serde_json::Value::Array(links)) => links,
                _ => Vec::new(),
            },
            Self::Source(_) => Vec::new(),
        };
        let source = match self {
            Self::Source(record) => record.links(),
            Self::Product(_) => &[],
        };
        product
            .into_iter()
            .filter_map(|value| match value {
                serde_json::Value::String(link) => Some(Cow::Owned(link)),
                _ => None,
            })
            .chain(source.iter().map(|text| Cow::Borrowed(text.as_str())))
    }
}

#[derive(Clone, Copy)]
pub(crate) enum NativeArena<'a> {
    Product(&'a [NativeRecord]),
    Source(&'a [UnknownRecord], &'a [usize]),
}

impl<'a> NativeArena<'a> {
    pub(crate) fn records(self) -> impl Iterator<Item = NativeEntity<'a>> {
        let (products, sources, order): (&[NativeRecord], &[UnknownRecord], &[usize]) = match self {
            Self::Product(records) => (records, &[], &[]),
            Self::Source(records, order) => (&[], records, order),
        };
        products.iter().map(NativeEntity::Product).chain(
            order
                .iter()
                .map(move |index| NativeEntity::Source(&sources[*index])),
        )
    }

    pub(crate) fn len(self) -> usize {
        match self {
            Self::Product(records) => records.len(),
            Self::Source(_, order) => order.len(),
        }
    }
}

#[derive(Clone, Copy)]
pub(crate) struct NativeView<'a> {
    pub(crate) ir: &'a CadIr,
    pub(crate) unknowns: Option<(&'a str, &'a [UnknownRecord], &'a [usize])>,
}

impl<'a> NativeView<'a> {
    pub(crate) fn new(
        ir: &'a CadIr,
        unknowns: Option<(&'a str, &'a [UnknownRecord], &'a [usize])>,
    ) -> Self {
        Self { ir, unknowns }
    }

    /// Visit native arenas in map order, replacing one unknown arena by source facts.
    pub(crate) fn visit<E>(
        self,
        mut visit: impl FnMut(&'a str, &'a str, NativeArena<'a>) -> Result<(), E>,
    ) -> Result<(), E> {
        let mut pending = self.unknowns;
        for (format, namespace) in &self.ir.native.0 {
            if let Some((replacement, records, order)) = pending {
                if replacement < format.as_str() {
                    visit(replacement, "unknowns", NativeArena::Source(records, order))?;
                    pending = None;
                }
            }
            for (arena, records) in namespace.arenas() {
                if let Some((replacement, sources, order)) = pending {
                    if replacement == format && "unknowns" <= arena.as_str() {
                        visit(replacement, "unknowns", NativeArena::Source(sources, order))?;
                        pending = None;
                    }
                }
                if let Some((replacement, _, _)) = self.unknowns {
                    if replacement == format && arena == "unknowns" {
                        continue;
                    }
                }
                visit(format, arena, NativeArena::Product(records))?;
            }
            if let Some((replacement, sources, order)) = pending {
                if replacement == format {
                    visit(replacement, "unknowns", NativeArena::Source(sources, order))?;
                    pending = None;
                }
            }
        }
        if let Some((format, records, order)) = pending {
            visit(format, "unknowns", NativeArena::Source(records, order))?;
        }
        Ok(())
    }

    /// Visit native arenas in the order of [`Self::visit`] with a callback that cannot fail.
    pub(crate) fn for_each(self, mut visit: impl FnMut(&'a str, &'a str, NativeArena<'a>)) {
        let Ok(()) = self.visit::<std::convert::Infallible>(|format, arena, records| {
            visit(format, arena, records);
            Ok(())
        });
    }
}