//! Tier-1 streaming decoder — separate from the batch path so the
//! main `decode_t1` flow stays untouched. This module post-processes
//! a fully-decoded `DocumentT1` into an iterator of [`StreamEvent`]s
//! following TIER1.md §"Streaming / incremental decode" yield-point
//! semantics.
//!
//! **Not a true coroutine streamer.** dms-rs is batch-only per
//! TIER1.md line 1466. This module gives consumers the streaming
//! *API* without rewriting the parser. The events fire in the
//! source-order the spec specifies, but parsing happens up front.

use crate::{BreadcrumbSegment, DmsMap, Value, DecodeError};
use crate::tier1::{
    DocumentT1, DecoratorEntry, decode_t1_with_registry,
    DialectRegistry, EmptyRegistry,
};

/// One event emitted by the streaming decoder.
#[derive(Debug, Clone, PartialEq)]
pub enum StreamEvent {
    /// Front matter parsed. Emitted once at the start, before any
    /// body events. `meta` is the front-matter table; empty if no
    /// front matter.
    FrontMatter {
        meta: DmsMap<Value>,
    },
    /// Beginning of a container value (Table / List). Children
    /// follow as separate events. Emitted at the open of the
    /// container — equivalent to "we're about to enter the
    /// container at this path".
    EnterContainer {
        path: Vec<BreadcrumbSegment>,
        kind: ContainerKind,
        decorations: Option<DecoratorEntry>,
    },
    /// End of a container value — DEDENT semantics. Emitted after
    /// all child events. The container's full value is materialized
    /// and available here.
    LeaveContainer {
        path: Vec<BreadcrumbSegment>,
        kind: ContainerKind,
    },
    /// A leaf value (scalar). Emitted as encountered.
    Leaf {
        path: Vec<BreadcrumbSegment>,
        value: Value,
        decorations: Option<DecoratorEntry>,
    },
    /// End of document. Emitted last, with any document-root
    /// floating decorations.
    EndOfDocument {
        root_decorations: Option<DecoratorEntry>,
    },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContainerKind {
    Table,
    List,
}

/// Streaming-decode entry point. Parses the document fully then
/// returns an iterator of events in source order.
///
/// Equivalent to [`decode_t1_streaming_with_registry`] with
/// [`EmptyRegistry`].
pub fn decode_t1_streaming(src: &str) -> Result<StreamingIter, DecodeError> {
    decode_t1_streaming_with_registry(src, &EmptyRegistry)
}

/// Streaming-decode entry point with an explicit dialect registry.
///
/// Parses `src` via [`decode_t1_with_registry`] (batch, full parse),
/// then post-processes the resulting [`DocumentT1`] into an ordered
/// sequence of [`StreamEvent`]s following the TIER1.md §"Streaming /
/// incremental decode" yield-point rules.
pub fn decode_t1_streaming_with_registry(
    src: &str,
    registry: &dyn DialectRegistry,
) -> Result<StreamingIter, DecodeError> {
    let doc = decode_t1_with_registry(src, registry)?;
    Ok(StreamingIter::new(doc))
}

/// Iterator over [`StreamEvent`]s for a decoded [`DocumentT1`].
pub struct StreamingIter {
    events: std::vec::IntoIter<StreamEvent>,
}

impl StreamingIter {
    fn new(doc: DocumentT1) -> Self {
        let events = build_events(doc);
        Self { events: events.into_iter() }
    }
}

impl Iterator for StreamingIter {
    type Item = StreamEvent;
    fn next(&mut self) -> Option<Self::Item> {
        self.events.next()
    }
}

// ── Event builder ─────────────────────────────────────────────────────────────

/// Build a sorted index of `(path_slice, entry_ref)` pairs from `decorators`
/// for `O(log n)` lookup during the event walk. Avoids the per-decode
/// `HashMap` allocation and path-clone-on-insert cost.
fn build_sorted_dec_index(decorators: &[DecoratorEntry]) -> Vec<(&[BreadcrumbSegment], &DecoratorEntry)> {
    if decorators.is_empty() {
        return Vec::new();
    }
    let mut idx: Vec<usize> = (0..decorators.len()).collect();
    idx.sort_by(|&a, &b| decorators[a].path.cmp(&decorators[b].path));
    let mut sorted: Vec<(&[BreadcrumbSegment], &DecoratorEntry)> = Vec::with_capacity(decorators.len());
    for i in idx {
        sorted.push((decorators[i].path.as_slice(), &decorators[i]));
    }
    sorted
}

/// Binary-search `sorted` for an exact path match. Returns a clone of the
/// matching `DecoratorEntry` (events are owned values, so one clone is
/// unavoidable), or `None` when there is no entry at `path`.
#[inline]
fn find_decoration(
    sorted: &[(&[BreadcrumbSegment], &DecoratorEntry)],
    path: &[BreadcrumbSegment],
) -> Option<DecoratorEntry> {
    match sorted.binary_search_by(|(p, _)| (*p).cmp(path)) {
        Ok(i) => Some(sorted[i].1.clone()),
        Err(_) => None,
    }
}

fn build_events(doc: DocumentT1) -> Vec<StreamEvent> {
    let mut events = Vec::new();

    // 1. FrontMatter event — empty map when there was no front matter block.
    let meta = doc.t0.meta.clone().unwrap_or_default();
    events.push(StreamEvent::FrontMatter { meta });

    // 2. Build a sorted index for O(log n) path lookup during walk.
    // Replaces the previous HashMap which paid a Vec clone + siphash per insert.
    let sorted_dec = build_sorted_dec_index(&doc.decorators);

    // 3. Walk body.
    walk_value(&doc.t0.body, &mut Vec::new(), &sorted_dec, &mut events);

    // 4. EndOfDocument with root-level decorations (path == []).
    let root_dec = find_decoration(&sorted_dec, &[]);
    events.push(StreamEvent::EndOfDocument { root_decorations: root_dec });

    events
}

fn walk_value(
    v: &Value,
    path: &mut Vec<BreadcrumbSegment>,
    sorted_dec: &[(&[BreadcrumbSegment], &DecoratorEntry)],
    events: &mut Vec<StreamEvent>,
) {
    match v {
        Value::Table(t) => {
            let dec = find_decoration(sorted_dec, path);
            events.push(StreamEvent::EnterContainer {
                path: path.clone(),
                kind: ContainerKind::Table,
                decorations: dec,
            });
            for (k, child) in t {
                path.push(BreadcrumbSegment::Key(k.clone()));
                walk_value(child, path, sorted_dec, events);
                path.pop();
            }
            events.push(StreamEvent::LeaveContainer {
                path: path.clone(),
                kind: ContainerKind::Table,
            });
        }
        Value::List(l) => {
            let dec = find_decoration(sorted_dec, path);
            events.push(StreamEvent::EnterContainer {
                path: path.clone(),
                kind: ContainerKind::List,
                decorations: dec,
            });
            for (i, child) in l.iter().enumerate() {
                path.push(BreadcrumbSegment::Index(i));
                walk_value(child, path, sorted_dec, events);
                path.pop();
            }
            events.push(StreamEvent::LeaveContainer {
                path: path.clone(),
                kind: ContainerKind::List,
            });
        }
        Value::UnorderedTable(t) => {
            // UnorderedTable: same container semantics as Table; keys
            // iterate in arbitrary HashMap order (matches spec note that
            // unordered tables have no guaranteed iteration order).
            let dec = find_decoration(sorted_dec, path);
            events.push(StreamEvent::EnterContainer {
                path: path.clone(),
                kind: ContainerKind::Table,
                decorations: dec,
            });
            for (k, child) in t {
                path.push(BreadcrumbSegment::Key(k.clone()));
                walk_value(child, path, sorted_dec, events);
                path.pop();
            }
            events.push(StreamEvent::LeaveContainer {
                path: path.clone(),
                kind: ContainerKind::Table,
            });
        }
        // All scalar variants — yield as encountered (TIER1.md line 1464).
        // Covers: Bool, Integer, Float, String, OffsetDateTime,
        // LocalDateTime, LocalDate, LocalTime.
        leaf => {
            let dec = find_decoration(sorted_dec, path);
            events.push(StreamEvent::Leaf {
                path: path.clone(),
                value: leaf.clone(),
                decorations: dec,
            });
        }
    }
}

// ── Tests ─────────────────────────────────────────────────────────────────────

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

    #[test]
    fn streams_tier0_doc() {
        let src = "x: 1\ny: \"hello\"\n";
        let events: Vec<_> = decode_t1_streaming(src).unwrap().collect();
        // Expect: FrontMatter, EnterContainer(Table, []), Leaf(x=1), Leaf(y="hello"),
        // LeaveContainer, EndOfDocument
        assert!(matches!(events.first(), Some(StreamEvent::FrontMatter { .. })));
        assert!(matches!(events.last(), Some(StreamEvent::EndOfDocument { .. })));
        let leaves: Vec<_> = events
            .iter()
            .filter(|e| matches!(e, StreamEvent::Leaf { .. }))
            .collect();
        assert_eq!(leaves.len(), 2);
    }

    #[test]
    fn streams_nested_table() {
        let src = "a:\n  b: 1\n  c: 2\n";
        let events: Vec<_> = decode_t1_streaming(src).unwrap().collect();
        let enters: Vec<_> = events
            .iter()
            .filter(|e| matches!(e, StreamEvent::EnterContainer { .. }))
            .collect();
        assert_eq!(enters.len(), 2, "outer table + inner table");
        // Verify nesting: enter outer, enter inner, leaves, leave inner, leave outer.
        let mut depth = 0i32;
        let mut max_depth = 0i32;
        for e in &events {
            match e {
                StreamEvent::EnterContainer { .. } => {
                    depth += 1;
                    max_depth = max_depth.max(depth);
                }
                StreamEvent::LeaveContainer { .. } => {
                    depth -= 1;
                }
                _ => {}
            }
        }
        assert_eq!(max_depth, 2);
        assert_eq!(depth, 0, "balanced");
    }

    #[test]
    fn streams_leaf_decorations_attached() {
        let src = concat!(
            "+++\n",
            "_dms_tier: 1\n",
            "_dms_imports:\n",
            "  + dialect: \"x\"\n",
            "    version: \"1.0.0\"\n",
            "    bind:\n",
            "      \"|\": [\"tag\"]\n",
            "+++\n",
            "foo: |tag() 5\n",
        );
        let events: Vec<_> = decode_t1_streaming(src).unwrap().collect();
        let leaf = events
            .iter()
            .find_map(|e| {
                if let StreamEvent::Leaf { path, decorations, .. } = e {
                    if path == &vec![BreadcrumbSegment::Key("foo".into())] {
                        return Some(decorations);
                    }
                }
                None
            })
            .unwrap();
        assert!(leaf.is_some(), "leaf should carry its decoration");
    }

    #[test]
    fn streams_list_root() {
        let src = "+ 1\n+ 2\n+ 3\n";
        let events: Vec<_> = decode_t1_streaming(src).unwrap().collect();
        let enter = events
            .iter()
            .find_map(|e| {
                if let StreamEvent::EnterContainer { kind, .. } = e {
                    Some(*kind)
                } else {
                    None
                }
            })
            .unwrap();
        assert_eq!(enter, ContainerKind::List);
        let leaves: Vec<_> = events
            .iter()
            .filter(|e| matches!(e, StreamEvent::Leaf { .. }))
            .collect();
        assert_eq!(leaves.len(), 3);
    }

    #[test]
    fn front_matter_event_first() {
        let src = "+++\nkey: 1\n+++\nx: 1\n";
        let events: Vec<_> = decode_t1_streaming(src).unwrap().collect();
        match &events[0] {
            StreamEvent::FrontMatter { meta } => {
                assert_eq!(meta["key"], Value::Integer(1));
            }
            _ => panic!("first event must be FrontMatter"),
        }
    }
}
