use std::collections::HashMap;

use crate::model::{Block, Comment, TextAnchor};
use crate::parser::body::{CommentAnchorEvent, CommentAnchorEventKind};

pub(crate) fn apply_comment_anchors(
    comments: &mut [Comment],
    events: &[CommentAnchorEvent],
    blocks: &[Block],
) {
    let blocks_by_id: HashMap<&str, &Block> = blocks.iter().map(|block| (block.id.as_str(), block)).collect();
    let mut ranges_by_comment: HashMap<&str, Vec<TextAnchor>> = HashMap::new();
    let mut starts: HashMap<&str, Vec<&CommentAnchorEvent>> = HashMap::new();

    for event in events {
        match event.kind {
            CommentAnchorEventKind::Start => {
                starts
                    .entry(event.comment_id.as_str())
                    .or_default()
                    .push(event);
            }
            CommentAnchorEventKind::End => {
                if let Some(comment_starts) = starts.get_mut(event.comment_id.as_str())
                    && let Some(start) = comment_starts.pop()
                    && start.block_id == event.block_id
                    && start.char_offset <= event.char_offset
                {
                    ranges_by_comment
                        .entry(event.comment_id.as_str())
                        .or_default()
                        .push(TextAnchor {
                            block_id: event.block_id.clone(),
                            char_start: start.char_offset,
                            char_end: event.char_offset,
                        });
                }
            }
            CommentAnchorEventKind::Reference => {}
        }
    }

    for comment in comments {
        let key = comment.imported_id.as_deref().unwrap_or_else(|| comment.id.trim_start_matches("comment:"));
        if let Some(anchors) = ranges_by_comment.get(key) {
            comment.anchors = anchors.clone();
            comment.anchored_text = anchors
                .first()
                .and_then(|anchor| blocks_by_id.get(anchor.block_id.as_str()))
                .and_then(|block| anchored_text(&block.text, anchors.first().unwrap()));
        }
    }
}

fn anchored_text(text: &str, anchor: &TextAnchor) -> Option<String> {
    let char_count = text.chars().count();
    if anchor.char_start > anchor.char_end || anchor.char_end > char_count {
        return None;
    }

    Some(
        text.chars()
            .skip(anchor.char_start)
            .take(anchor.char_end.saturating_sub(anchor.char_start))
            .collect(),
    )
}

#[cfg(test)]
mod tests {
    use crate::model::{Block, BlockKind, Comment, Story};
    use crate::parser::body::{CommentAnchorEvent, CommentAnchorEventKind};

    use super::apply_comment_anchors;

    #[test]
    fn applies_single_block_anchor_and_anchored_text() {
        let blocks = vec![Block {
            id: "body:block:000001".to_string(),
            story: Story::Body,
            kind: BlockKind::Paragraph,
            text: "Hello world".to_string(),
            style: None,
            text_spans: None,
            footnote_refs: Vec::new(),
            endnote_refs: Vec::new(),
        }];
        let mut comments = vec![Comment {
            id: "comment:1".to_string(),
            imported_id: Some("1".to_string()),
            author: None,
            date: None,
            text: "Note".to_string(),
            anchors: Vec::new(),
            anchored_text: None,
            resolved: None,
            parent_id: None,
            replies: Vec::new(),
        }];
        let events = vec![
            CommentAnchorEvent {
                comment_id: "1".to_string(),
                block_id: "body:block:000001".to_string(),
                char_offset: 6,
                kind: CommentAnchorEventKind::Start,
            },
            CommentAnchorEvent {
                comment_id: "1".to_string(),
                block_id: "body:block:000001".to_string(),
                char_offset: 11,
                kind: CommentAnchorEventKind::End,
            },
        ];

        apply_comment_anchors(&mut comments, &events, &blocks);

        assert_eq!(comments[0].anchors.len(), 1);
        assert_eq!(comments[0].anchors[0].char_start, 6);
        assert_eq!(comments[0].anchors[0].char_end, 11);
        assert_eq!(comments[0].anchored_text.as_deref(), Some("world"));
    }
}
