// semantic_client.rs --- Headless consumer of the SemanticFrame wire (T M11.5).

//! The frontend↔instance glue for the semantic projection.
//!
//! `docs/semantic-frontend-protocol.md` deliberately moves rendering
//! correctness (shaping, wrap, hit-testing) into a GPU frontend the
//! instance test harness cannot exercise, and bounds the *testable*
//! surface to "the frontend↔instance glue, not all rendering."
//! `SemanticClient` is exactly that glue, made headless and
//! self-contained: no terminal, no GPU, no pixels.
//!
//! It composes the two replica layers a `semantic_render` session
//! needs:
//!
//! - [`BufferMirror`] — the rope replica (M10.10). The semantic frame
//!   ships *no text*; the client holds the document locally via
//!   `BufferSnapshot` + `CrdtOp`, exactly as the grid TUI does.
//! - A [`SemanticModel`] per family — the *interpretation* layer:
//!   byte-anchored styling / decorations, reconstructed from the
//!   `full` + dirty-segment deltas (M11.4).
//!
//! The client also produces the one frontend→instance message the
//! protocol adds — [`FrontendEvent::Viewport`] — declaring the byte
//! range it has "on screen" so the instance scopes its projection.
//!
//! Read-back accessors (`text`, `effective_style_at`,
//! `decoration_kinds_at`) exist so a test can assert the
//! reconstruction equals the instance's intent — the
//! "reconstruction-equivalence" golden discipline (no snapshot crate;
//! matches the repo's explicit-assertion style).

use std::collections::HashMap;

use crate::buffer::BufferId;
use crate::buffer_mirror::BufferMirror;
use crate::cell::Style;
use crate::overlay::merge_styles;
use crate::protocol::{
    ByteRange, Decoration, DecorationKind, DecorationSegment, FrontendEvent, FrontendId,
    InstanceMessage, StyleSegment, StyleSpan,
};

/// An item the model can restrict to a sub-range. `range` is where it
/// applies; `clipped` is the item narrowed to `bounds` (or `None`
/// when disjoint). The semantic frame's items are byte-anchored, so
/// both families implement this uniformly.
trait Clip: Clone {
    fn range(&self) -> ByteRange;
    fn clipped(&self, bounds: ByteRange) -> Option<Self>;
}

fn intersect(a: ByteRange, b: ByteRange) -> Option<ByteRange> {
    let start = a.start.max(b.start);
    let end = a.end.min(b.end);
    (end > start).then_some(ByteRange { start, end })
}

impl Clip for StyleSpan {
    fn range(&self) -> ByteRange {
        self.range
    }
    fn clipped(&self, bounds: ByteRange) -> Option<Self> {
        intersect(self.range, bounds).map(|range| Self {
            range,
            style: self.style,
        })
    }
}

impl Clip for Decoration {
    fn range(&self) -> ByteRange {
        self.range
    }
    fn clipped(&self, bounds: ByteRange) -> Option<Self> {
        intersect(self.range, bounds).map(|range| Self {
            range,
            kind: self.kind,
        })
    }
}

/// One reconstructed dirty region. The M11.4 contract — *each segment
/// carries every current item intersecting its range* — makes a tile
/// self-contained: rendering any byte in `range` consults only this
/// tile's `items`, never a neighbour's. That is what lets incremental
/// application be a clean per-tile replacement instead of fragile
/// cross-span surgery.
#[derive(Clone, Debug, Eq, PartialEq)]
struct Tile<T> {
    range: ByteRange,
    items: Vec<T>,
}

/// One family's reconstructed view of one buffer: disjoint tiles
/// ordered by start. Bytes covered by no tile have no styling /
/// decoration (default), exactly as the instance intends for regions
/// outside the declared viewport.
struct SemanticModel<T> {
    tiles: Vec<Tile<T>>,
}

// Manual `Default` — the derive would wrongly require `T: Default`
// (a `StyleSpan`/`Decoration` has no meaningful default); an empty
// model is just no tiles regardless of `T`.
impl<T> Default for SemanticModel<T> {
    fn default() -> Self {
        Self { tiles: Vec::new() }
    }
}

impl<T: Clip> SemanticModel<T> {
    /// Apply one frame. `full` discards everything first (resync);
    /// otherwise each segment replaces only its own byte range —
    /// tiles straddling a segment are split, keeping the parts
    /// outside it (clipped), and the segment's items become the new
    /// tile for the region.
    fn apply(&mut self, full: bool, segments: &[(ByteRange, Vec<T>)]) {
        if full {
            self.tiles = segments
                .iter()
                .map(|(range, items)| Tile {
                    range: *range,
                    items: items.clone(),
                })
                .collect();
        } else {
            for (range, items) in segments {
                self.replace_region(*range, items.clone());
            }
        }
        self.tiles.sort_by_key(|t| (t.range.start, t.range.end));
    }

    fn replace_region(&mut self, region: ByteRange, items: Vec<T>) {
        let mut next: Vec<Tile<T>> = Vec::with_capacity(self.tiles.len() + 1);
        for t in std::mem::take(&mut self.tiles) {
            if intersect(t.range, region).is_none() {
                next.push(t);
                continue;
            }
            // Keep the parts of `t` outside `region`, each carrying
            // only the items that survive the narrower range.
            if t.range.start < region.start {
                let left = ByteRange {
                    start: t.range.start,
                    end: region.start,
                };
                next.push(Tile {
                    range: left,
                    items: t.items.iter().filter_map(|i| i.clipped(left)).collect(),
                });
            }
            if t.range.end > region.end {
                let right = ByteRange {
                    start: region.end,
                    end: t.range.end,
                };
                next.push(Tile {
                    range: right,
                    items: t.items.iter().filter_map(|i| i.clipped(right)).collect(),
                });
            }
            // The overlapped middle is dropped — `items` re-supplies it.
        }
        next.push(Tile {
            range: region,
            items,
        });
        self.tiles = next;
    }

    /// Items covering `byte`, in instance order (the order they were
    /// shipped — wider-first for styling, so a fold via
    /// [`merge_styles`] reproduces the grid path's layering).
    fn items_at(&self, byte: u64) -> impl Iterator<Item = &T> {
        self.tiles
            .iter()
            .find(|t| t.range.start <= byte && byte < t.range.end)
            .into_iter()
            .flat_map(move |t| {
                t.items
                    .iter()
                    .filter(move |i| i.range().start <= byte && byte < i.range().end)
            })
    }

    fn tile_ranges(&self) -> Vec<ByteRange> {
        self.tiles.iter().map(|t| t.range).collect()
    }
}

/// A headless `semantic_render` session: rope replica + the styling
/// and decoration interpretation layers, plus the `Viewport` event it
/// emits. Drive it by feeding every [`InstanceMessage`] through
/// [`Self::apply`]; read it back through the accessors.
pub struct SemanticClient {
    frontend_id: FrontendId,
    mirror: BufferMirror,
    styles: HashMap<BufferId, SemanticModel<StyleSpan>>,
    decos: HashMap<BufferId, SemanticModel<Decoration>>,
}

impl SemanticClient {
    /// Construct a client for the session assigned `frontend_id`
    /// (the id the daemon stamped in `Hello`).
    #[must_use]
    pub fn new(frontend_id: FrontendId) -> Self {
        Self {
            frontend_id,
            mirror: BufferMirror::new(frontend_id),
            styles: HashMap::new(),
            decos: HashMap::new(),
        }
    }

    /// The session's assigned frontend id.
    #[must_use]
    pub fn frontend_id(&self) -> FrontendId {
        self.frontend_id
    }

    /// Build the [`FrontendEvent::Viewport`] declaring `visible` for
    /// `buffer_id`. The caller writes it to the daemon; the instance
    /// scopes its projection to this range. `generation` is the CRDT
    /// version the frontend computed the range against (M11.4 records
    /// it for the future viewport-race refinement).
    #[must_use]
    pub fn viewport_event(
        &self,
        buffer_id: BufferId,
        visible: ByteRange,
        generation: u64,
    ) -> FrontendEvent {
        FrontendEvent::Viewport {
            frontend_id: self.frontend_id,
            buffer_id,
            visible,
            generation,
        }
    }

    /// Route one instance message into the replica/interpretation
    /// layers. Unrelated variants (grid `CellDelta`/`Cursor`,
    /// presence, and the not-yet-produced adornment/fold/resource
    /// families) are ignored — a semantic session lays out locally
    /// and never consumes the grid projection.
    pub fn apply(&mut self, msg: &InstanceMessage) {
        match msg {
            InstanceMessage::BufferSnapshot {
                buffer_id,
                crdt_snapshot,
            } => {
                // `AlreadyInitialized` means a duplicate bootstrap for
                // a buffer we already mirror — benign for a consumer.
                let _ = self.mirror.init_from_snapshot(*buffer_id, crdt_snapshot);
            }
            InstanceMessage::CrdtOp { buffer_id, op } => {
                // A pure consumer never edits, so it is never the
                // op's source — no echo to filter (the daemon also
                // excludes the sender). Drop a non-applying op
                // silently, as the test Observer does.
                let _ = self.mirror.apply_remote_op(*buffer_id, &op.bytes);
            }
            InstanceMessage::CursorByte {
                buffer_id,
                byte_pos,
            } => {
                self.mirror
                    .set_cursor_byte_pos(*buffer_id, *byte_pos as usize);
            }
            InstanceMessage::StyleSpans {
                buffer_id,
                full,
                segments,
                ..
            } => {
                let segs: Vec<(ByteRange, Vec<StyleSpan>)> = segments
                    .iter()
                    .map(|s: &StyleSegment| (s.range, s.spans.clone()))
                    .collect();
                self.styles
                    .entry(*buffer_id)
                    .or_default()
                    .apply(*full, &segs);
            }
            InstanceMessage::Decorations {
                buffer_id,
                full,
                segments,
                ..
            } => {
                let segs: Vec<(ByteRange, Vec<Decoration>)> = segments
                    .iter()
                    .map(|s: &DecorationSegment| (s.range, s.decorations.clone()))
                    .collect();
                self.decos
                    .entry(*buffer_id)
                    .or_default()
                    .apply(*full, &segs);
            }
            // Grid projection, presence, and the honest-stub families
            // (InlineAdornments / BlockAdornments / FoldState /
            // ResourceOffer) — a semantic session does not consume
            // these. ModeLine / Signal / Goodbye are session control,
            // handled by the attach loop, not the model.
            _ => {}
        }
    }

    /// The reconstructed document text for `buffer_id` (the rope
    /// replica materialized), or `None` if not yet bootstrapped.
    #[must_use]
    pub fn text(&self, buffer_id: BufferId) -> Option<String> {
        self.mirror.materialize(buffer_id)
    }

    /// Whether the rope replica for `buffer_id` has been bootstrapped.
    #[must_use]
    pub fn is_ready(&self, buffer_id: BufferId) -> bool {
        self.mirror.is_ready(buffer_id)
    }

    /// The cursor byte position the instance last reported.
    #[must_use]
    pub fn cursor_byte_pos(&self, buffer_id: BufferId) -> Option<usize> {
        self.mirror.cursor_byte_pos(buffer_id)
    }

    /// The effective style at `byte`: every reconstructed span
    /// covering it, folded via [`merge_styles`] in instance order.
    /// `Style::default()` when nothing covers it (outside the
    /// declared viewport, or no styling there).
    #[must_use]
    pub fn effective_style_at(&self, buffer_id: BufferId, byte: u64) -> Style {
        self.styles
            .get(&buffer_id)
            .map_or_else(Style::default, |m| {
                m.items_at(byte)
                    .fold(Style::default(), |acc, s| merge_styles(acc, s.style))
            })
    }

    /// The decoration kinds covering `byte`, in instance order
    /// (duplicates preserved — a byte can carry, e.g., both a
    /// selection and a diagnostic).
    #[must_use]
    pub fn decoration_kinds_at(&self, buffer_id: BufferId, byte: u64) -> Vec<DecorationKind> {
        self.decos
            .get(&buffer_id)
            .map_or_else(Vec::new, |m| m.items_at(byte).map(|d| d.kind).collect())
    }

    /// Reconstructed styling tile ranges for `buffer_id` — for
    /// invariant assertions (disjointness, in-viewport bounds).
    #[must_use]
    pub fn style_tile_ranges(&self, buffer_id: BufferId) -> Vec<ByteRange> {
        self.styles
            .get(&buffer_id)
            .map(SemanticModel::tile_ranges)
            .unwrap_or_default()
    }

    /// Reconstructed decoration tile ranges for `buffer_id`.
    #[must_use]
    pub fn decoration_tile_ranges(&self, buffer_id: BufferId) -> Vec<ByteRange> {
        self.decos
            .get(&buffer_id)
            .map(SemanticModel::tile_ranges)
            .unwrap_or_default()
    }
}

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

    fn br(start: u64, end: u64) -> ByteRange {
        ByteRange { start, end }
    }

    fn styled(fg_bold: bool) -> Style {
        Style {
            bold: fg_bold,
            ..Style::default()
        }
    }

    fn span(start: u64, end: u64, bold: bool) -> StyleSpan {
        StyleSpan {
            range: br(start, end),
            style: styled(bold),
        }
    }

    fn deco(start: u64, end: u64, kind: DecorationKind) -> Decoration {
        Decoration {
            range: br(start, end),
            kind,
        }
    }

    #[test]
    fn full_frame_replaces_the_whole_model() {
        let mut m: SemanticModel<StyleSpan> = SemanticModel::default();
        m.apply(true, &[(br(0, 10), vec![span(2, 5, true)])]);
        assert_eq!(m.tile_ranges(), vec![br(0, 10)]);
        // A second full frame discards the first entirely.
        m.apply(true, &[(br(0, 4), vec![span(0, 4, false)])]);
        assert_eq!(m.tile_ranges(), vec![br(0, 4)]);
        assert_eq!(m.items_at(2).count(), 1);
        assert!(m.items_at(8).next().is_none(), "byte 8 no longer covered");
    }

    #[test]
    fn incremental_segment_splits_a_straddling_tile_and_keeps_the_edges() {
        let mut m: SemanticModel<StyleSpan> = SemanticModel::default();
        // One wide tile spanning [0,30) with a span over [0,30).
        m.apply(true, &[(br(0, 30), vec![span(0, 30, true)])]);
        // A dirty segment repaints the middle [10,20).
        m.apply(false, &[(br(10, 20), vec![span(10, 20, false)])]);
        // Edges [0,10) and [20,30) survive (clipped), middle replaced.
        assert_eq!(
            m.tile_ranges(),
            vec![br(0, 10), br(10, 20), br(20, 30)],
            "straddling tile split into left edge / new middle / right edge"
        );
        // Edge styling preserved (bold); middle replaced (not bold).
        assert!(m.items_at(5).next().unwrap().style.bold);
        assert!(!m.items_at(15).next().unwrap().style.bold);
        assert!(m.items_at(25).next().unwrap().style.bold);
    }

    #[test]
    fn bytes_outside_all_tiles_have_default_style() {
        let c = SemanticClient::new(FrontendId(7));
        let b = BufferId::next();
        assert_eq!(c.effective_style_at(b, 3), Style::default());
        assert!(c.decoration_kinds_at(b, 3).is_empty());
    }

    #[test]
    fn overlapping_spans_fold_in_order_via_merge_styles() {
        let mut m: SemanticModel<StyleSpan> = SemanticModel::default();
        // Wider span (bold) then a nested non-bold span — instance
        // ships wider-first; merge_styles overlays in that order.
        let wide = StyleSpan {
            range: br(0, 10),
            style: Style {
                bold: true,
                ..Style::default()
            },
        };
        let inner = StyleSpan {
            range: br(4, 6),
            style: Style {
                italic: true,
                ..Style::default()
            },
        };
        m.apply(true, &[(br(0, 10), vec![wide, inner])]);
        let folded = m
            .items_at(5)
            .fold(Style::default(), |acc, s| merge_styles(acc, s.style));
        assert!(folded.bold && folded.italic, "both layers apply at byte 5");
        let only_wide = m
            .items_at(1)
            .fold(Style::default(), |acc, s| merge_styles(acc, s.style));
        assert!(only_wide.bold && !only_wide.italic);
    }

    #[test]
    fn decoration_model_tracks_kinds_at_byte() {
        let mut m: SemanticModel<Decoration> = SemanticModel::default();
        m.apply(
            true,
            &[(
                br(0, 20),
                vec![
                    deco(2, 8, DecorationKind::Selection),
                    deco(5, 6, DecorationKind::DiagnosticError),
                ],
            )],
        );
        let at5: Vec<_> = m.items_at(5).map(|d| d.kind).collect();
        assert_eq!(
            at5,
            vec![DecorationKind::Selection, DecorationKind::DiagnosticError]
        );
        assert_eq!(
            m.items_at(3).map(|d| d.kind).collect::<Vec<_>>(),
            vec![DecorationKind::Selection]
        );
        assert!(m.items_at(15).next().is_none());
    }

    #[test]
    fn client_ignores_grid_and_stub_families() {
        let mut c = SemanticClient::new(FrontendId(2));
        let b = BufferId::next();
        // None of these should panic or affect the model.
        c.apply(&InstanceMessage::Cursor(None));
        c.apply(&InstanceMessage::FoldState {
            buffer_id: b,
            folds: vec![br(0, 1)],
        });
        c.apply(&InstanceMessage::ResourceOffer {
            handle: 1,
            mime: "image/png".into(),
            body: crate::protocol::ResourceBody::Inline(vec![1, 2]),
        });
        assert!(c.style_tile_ranges(b).is_empty());
        assert!(c.text(b).is_none());
    }

    #[test]
    fn viewport_event_carries_the_sessions_fid() {
        let c = SemanticClient::new(FrontendId(9));
        let b = BufferId::next();
        match c.viewport_event(b, br(0, 64), 3) {
            FrontendEvent::Viewport {
                frontend_id,
                buffer_id,
                visible,
                generation,
            } => {
                assert_eq!(frontend_id, FrontendId(9));
                assert_eq!(buffer_id, b);
                assert_eq!(visible, br(0, 64));
                assert_eq!(generation, 3);
            }
            other => panic!("expected Viewport, got {other:?}"),
        }
    }
}
