use std::env;
use std::fmt;
use std::rc::Rc;

use ruin_reactivity::{cell_in, effect_in};
use ruin_reactivity_parsing::{
    Anchor, AnchorBias, EditDelta, ReactiveRope, RopeEdit, TextPoint, TextRange,
};
use ruin_runtime::{async_main, fs, stdio};

#[derive(Clone, Debug)]
struct ParseSnapshot {
    text: String,
    root_anchor: Anchor,
    root: AstNode,
    reused_nodes: usize,
    total_nodes: usize,
    diagnostics: Vec<String>,
}

#[derive(Clone, Debug)]
struct AstNode {
    id: u64,
    kind: JsonKind,
    local_range: TextRange,
    children: Vec<AstNode>,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum JsonKind {
    Object,
    Array,
    Pair,
    String,
    Number,
    True,
    False,
    Null,
}

impl fmt::Display for JsonKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let label = match self {
            Self::Object => "object",
            Self::Array => "array",
            Self::Pair => "pair",
            Self::String => "string",
            Self::Number => "number",
            Self::True => "true",
            Self::False => "false",
            Self::Null => "null",
        };
        write!(f, "{label}")
    }
}

#[derive(Clone, Debug)]
struct ParsedNode {
    kind: JsonKind,
    range: TextRange,
    children: Vec<ParsedNode>,
}

#[derive(Default)]
struct JsonParser {
    next_id: u64,
}

struct ReuseContext<'a, 'count> {
    old_text: &'a str,
    new_text: &'a str,
    delta: &'a EditDelta,
    reused: &'count mut usize,
}

struct Cursor<'a> {
    text: &'a str,
    offset: usize,
}

#[derive(Clone)]
enum Command {
    Insert {
        offset: usize,
        text: String,
    },
    Replace {
        start: usize,
        end: usize,
        text: String,
    },
    ReplaceAll {
        text: String,
    },
    Delete {
        start: usize,
        end: usize,
    },
    Print,
    Help,
    Quit,
}

#[async_main]
async fn main() {
    let path = env::args()
        .nth(1)
        .expect("usage: cargo run -p ruin-reactivity-parsing --example json_reactive_parser -- <path/to/file.json>");
    let initial_text = fs::read_to_string(&path)
        .await
        .expect("json file should be readable");

    let reactor = ruin_reactivity::Reactor::new();
    let rope = ReactiveRope::from_text(&reactor, &initial_text);
    let parser = Rc::new(std::cell::RefCell::new(JsonParser::default()));

    let initial_snapshot = parser
        .borrow_mut()
        .parse_initial(&rope)
        .expect("initial JSON should parse");
    let parse_state = cell_in(&reactor, initial_snapshot);
    let last_delta = cell_in(&reactor, None::<EditDelta>);

    {
        let rope = rope.clone();
        let parser = Rc::clone(&parser);
        let parse_state = parse_state.clone();
        let last_delta = last_delta.clone();
        rope.edit_events().subscribe(move |delta| {
            let previous = parse_state.get();
            match parser.borrow_mut().parse_incremental(&rope, &previous, delta) {
                Ok(next) => {
                    last_delta.replace(Some(delta.clone()));
                    parse_state.replace(next);
                }
                Err(error) => {
                    eprintln!("parse error after edit: {error}");
                    eprintln!(
                        "note: current text length is {} bytes; use `replace_all <text>` to replace the full document without manual offsets",
                        rope.len_bytes()
                    );
                }
            }
        });
    };

    effect_in(&reactor, {
        let rope = rope.clone();
        let parse_state = parse_state.clone();
        let last_delta = last_delta.clone();
        move || {
            parse_state.with(|snapshot| {
                print_snapshot(snapshot, &rope, last_delta.with(Clone::clone).as_ref())
            });
        }
    })
    .leak();

    println!("Commands:");
    println!("  insert <byte_offset> <text>");
    println!("  replace <start> <end> <text>");
    println!("  replace_all <text>");
    println!("  delete <start> <end>");
    println!("  print");
    println!("  help");
    println!("  quit");

    let mut input = stdio::stdin().expect("stdin should be available");
    while let Some(line) = input.read_line().await.expect("stdin read should succeed") {
        let trimmed = line.trim_end();
        if trimmed.is_empty() {
            continue;
        }

        match parse_command(trimmed) {
            Ok(Command::Insert { offset, text }) => {
                let _ = rope.edit(RopeEdit::insert(offset, text));
            }
            Ok(Command::Replace { start, end, text }) => {
                let _ = rope.edit(RopeEdit::replace(TextRange::new(start, end), text));
            }
            Ok(Command::ReplaceAll { text }) => {
                let _ = rope.edit(RopeEdit::replace(TextRange::new(0, rope.len_bytes()), text));
            }
            Ok(Command::Delete { start, end }) => {
                let _ = rope.edit(RopeEdit::delete(TextRange::new(start, end)));
            }
            Ok(Command::Print) => {
                let snapshot = parse_state.get();
                print_snapshot(&snapshot, &rope, None);
            }
            Ok(Command::Help) => {
                println!("Commands:");
                println!("  insert <byte_offset> <text>");
                println!("  replace <start> <end> <text>");
                println!("  replace_all <text>");
                println!("  delete <start> <end>");
                println!("  print");
                println!("  help");
                println!("  quit");
            }
            Ok(Command::Quit) => break,
            Err(error) => eprintln!("{error}"),
        }
    }
}

fn parse_command(input: &str) -> Result<Command, String> {
    if input == "quit" {
        return Ok(Command::Quit);
    }
    if input == "print" {
        return Ok(Command::Print);
    }
    if input == "help" {
        return Ok(Command::Help);
    }
    if let Some(rest) = input.strip_prefix("insert ") {
        let (offset, text) = rest
            .split_once(' ')
            .ok_or_else(|| "insert expects: insert <offset> <text>".to_string())?;
        return Ok(Command::Insert {
            offset: offset
                .parse()
                .map_err(|_| "invalid insert offset".to_string())?,
            text: text.to_string(),
        });
    }
    if let Some(rest) = input.strip_prefix("replace ") {
        let mut parts = rest.splitn(3, ' ');
        let start = parts
            .next()
            .ok_or_else(|| "replace expects: replace <start> <end> <text>".to_string())?;
        let end = parts
            .next()
            .ok_or_else(|| "replace expects: replace <start> <end> <text>".to_string())?;
        let text = parts
            .next()
            .ok_or_else(|| "replace expects: replace <start> <end> <text>".to_string())?;
        return Ok(Command::Replace {
            start: start
                .parse()
                .map_err(|_| "invalid replace start".to_string())?,
            end: end.parse().map_err(|_| "invalid replace end".to_string())?,
            text: text.to_string(),
        });
    }
    if let Some(text) = input.strip_prefix("replace_all ") {
        return Ok(Command::ReplaceAll {
            text: text.to_string(),
        });
    }
    if let Some(rest) = input.strip_prefix("delete ") {
        let (start, end) = rest
            .split_once(' ')
            .ok_or_else(|| "delete expects: delete <start> <end>".to_string())?;
        return Ok(Command::Delete {
            start: start
                .parse()
                .map_err(|_| "invalid delete start".to_string())?,
            end: end.parse().map_err(|_| "invalid delete end".to_string())?,
        });
    }
    Err("unknown command; type `help`".to_string())
}

impl JsonParser {
    fn parse_initial(&mut self, rope: &ReactiveRope) -> Result<ParseSnapshot, String> {
        let text = read_rope_text(rope);
        let parsed = parse_json(&text)?;
        let root = self.materialize_fresh(&parsed, 0);
        Ok(ParseSnapshot {
            text,
            root_anchor: Anchor::new(0, AnchorBias::Left),
            total_nodes: count_nodes(&root),
            reused_nodes: 0,
            diagnostics: Vec::new(),
            root,
        })
    }

    fn parse_incremental(
        &mut self,
        rope: &ReactiveRope,
        previous: &ParseSnapshot,
        delta: &EditDelta,
    ) -> Result<ParseSnapshot, String> {
        let text = read_rope_text(rope);
        let parsed = parse_json(&text)?;
        let mut root_anchor = previous.root_anchor;
        root_anchor.map_through(delta);
        let mut reused = 0usize;
        let mut reuse = ReuseContext {
            old_text: &previous.text,
            new_text: &text,
            delta,
            reused: &mut reused,
        };
        let root = self.reuse_node(
            &parsed,
            Some(&previous.root),
            previous.root_anchor.byte,
            root_anchor.byte,
            &mut reuse,
        );
        Ok(ParseSnapshot {
            text,
            root_anchor,
            total_nodes: count_nodes(&root),
            reused_nodes: reused,
            diagnostics: Vec::new(),
            root,
        })
    }

    fn materialize_fresh(&mut self, parsed: &ParsedNode, parent_start: usize) -> AstNode {
        AstNode {
            id: self.alloc_id(),
            kind: parsed.kind,
            local_range: localize(parsed.range, parent_start),
            children: parsed
                .children
                .iter()
                .map(|child| self.materialize_fresh(child, parsed.range.start))
                .collect(),
        }
    }

    fn reuse_node(
        &mut self,
        parsed: &ParsedNode,
        old: Option<&AstNode>,
        old_parent_start: usize,
        new_parent_start: usize,
        reuse: &mut ReuseContext<'_, '_>,
    ) -> AstNode {
        let parsed_local = localize(parsed.range, new_parent_start);
        if let Some(old) = old
            && old.kind == parsed.kind
            && map_range(absolute_range(old, old_parent_start), reuse.delta) == parsed.range
            && reuse.old_text.get(
                absolute_range(old, old_parent_start).start
                    ..absolute_range(old, old_parent_start).end,
            ) == reuse.new_text.get(parsed.range.start..parsed.range.end)
        {
            *reuse.reused += count_nodes(old);
            return AstNode {
                id: old.id,
                kind: old.kind,
                local_range: parsed_local,
                children: old.children.clone(),
            };
        }

        let children = parsed
            .children
            .iter()
            .enumerate()
            .map(|(index, child)| {
                let previous_child = old.and_then(|node| node.children.get(index));
                let next_old_parent_start = old
                    .map(|node| absolute_range(node, old_parent_start).start)
                    .unwrap_or(old_parent_start);
                self.reuse_node(
                    child,
                    previous_child,
                    next_old_parent_start,
                    parsed.range.start,
                    reuse,
                )
            })
            .collect();

        AstNode {
            id: self.alloc_id(),
            kind: parsed.kind,
            local_range: parsed_local,
            children,
        }
    }

    fn alloc_id(&mut self) -> u64 {
        self.next_id = self.next_id.wrapping_add(1);
        self.next_id
    }
}

fn read_rope_text(rope: &ReactiveRope) -> String {
    let mut cursor = rope.cursor(0);
    let mut text = String::new();
    while let Some(chunk) = cursor.next_chunk() {
        text.push_str(chunk.text());
    }
    text
}

fn localize(range: TextRange, parent_start: usize) -> TextRange {
    TextRange::new(
        range.start.saturating_sub(parent_start),
        range.end.saturating_sub(parent_start),
    )
}

fn absolute_range(node: &AstNode, parent_start: usize) -> TextRange {
    TextRange::new(
        parent_start + node.local_range.start,
        parent_start + node.local_range.end,
    )
}

fn map_range(range: TextRange, delta: &EditDelta) -> TextRange {
    if range.end <= delta.range.start {
        return range;
    }
    if range.start >= delta.range.end {
        let shift = delta.net_byte_delta();
        return TextRange::new(
            range.start.saturating_add_signed(shift),
            range.end.saturating_add_signed(shift),
        );
    }
    TextRange::new(usize::MAX, usize::MAX)
}

fn parse_json(text: &str) -> Result<ParsedNode, String> {
    let mut cursor = Cursor { text, offset: 0 };
    cursor.skip_ws();
    let node = cursor.parse_value()?;
    cursor.skip_ws();
    if !cursor.is_eof() {
        return Err(format!(
            "unexpected trailing data at byte {}",
            cursor.offset
        ));
    }
    Ok(node)
}

impl<'a> Cursor<'a> {
    fn is_eof(&self) -> bool {
        self.offset >= self.text.len()
    }

    fn skip_ws(&mut self) {
        while let Some(ch) = self.peek_char() {
            if ch.is_ascii_whitespace() {
                self.offset += ch.len_utf8();
            } else {
                break;
            }
        }
    }

    fn peek_char(&self) -> Option<char> {
        self.text[self.offset..].chars().next()
    }

    fn bump_char(&mut self) -> Option<char> {
        let ch = self.peek_char()?;
        self.offset += ch.len_utf8();
        Some(ch)
    }

    fn expect_char(&mut self, expected: char) -> Result<(), String> {
        match self.bump_char() {
            Some(ch) if ch == expected => Ok(()),
            Some(ch) => Err(format!(
                "expected `{expected}` at byte {}, found `{ch}`",
                self.offset.saturating_sub(ch.len_utf8())
            )),
            None => Err(format!("expected `{expected}` at end of file")),
        }
    }

    fn parse_value(&mut self) -> Result<ParsedNode, String> {
        self.skip_ws();
        match self.peek_char() {
            Some('{') => self.parse_object(),
            Some('[') => self.parse_array(),
            Some('"') => self.parse_string(),
            Some('t') => self.parse_literal("true", JsonKind::True),
            Some('f') => self.parse_literal("false", JsonKind::False),
            Some('n') => self.parse_literal("null", JsonKind::Null),
            Some('-' | '0'..='9') => self.parse_number(),
            Some(other) => Err(format!("unexpected `{other}` at byte {}", self.offset)),
            None => Err("unexpected end of JSON input".to_string()),
        }
    }

    fn parse_object(&mut self) -> Result<ParsedNode, String> {
        let start = self.offset;
        self.expect_char('{')?;
        self.skip_ws();
        let mut children = Vec::new();
        if self.peek_char() == Some('}') {
            self.bump_char();
            return Ok(ParsedNode {
                kind: JsonKind::Object,
                range: TextRange::new(start, self.offset),
                children,
            });
        }

        loop {
            self.skip_ws();
            let key = self.parse_string()?;
            self.skip_ws();
            self.expect_char(':')?;
            self.skip_ws();
            let value = self.parse_value()?;
            let pair = ParsedNode {
                kind: JsonKind::Pair,
                range: TextRange::new(key.range.start, value.range.end),
                children: vec![key, value],
            };
            children.push(pair);
            self.skip_ws();
            match self.peek_char() {
                Some(',') => {
                    self.bump_char();
                }
                Some('}') => {
                    self.bump_char();
                    break;
                }
                Some(other) => {
                    return Err(format!(
                        "expected `,` or `}}` at byte {}, found `{other}`",
                        self.offset
                    ));
                }
                None => return Err("unterminated object".to_string()),
            }
        }

        Ok(ParsedNode {
            kind: JsonKind::Object,
            range: TextRange::new(start, self.offset),
            children,
        })
    }

    fn parse_array(&mut self) -> Result<ParsedNode, String> {
        let start = self.offset;
        self.expect_char('[')?;
        self.skip_ws();
        let mut children = Vec::new();
        if self.peek_char() == Some(']') {
            self.bump_char();
            return Ok(ParsedNode {
                kind: JsonKind::Array,
                range: TextRange::new(start, self.offset),
                children,
            });
        }

        loop {
            let value = self.parse_value()?;
            children.push(value);
            self.skip_ws();
            match self.peek_char() {
                Some(',') => {
                    self.bump_char();
                    self.skip_ws();
                }
                Some(']') => {
                    self.bump_char();
                    break;
                }
                Some(other) => {
                    return Err(format!(
                        "expected `,` or `]` at byte {}, found `{other}`",
                        self.offset
                    ));
                }
                None => return Err("unterminated array".to_string()),
            }
        }

        Ok(ParsedNode {
            kind: JsonKind::Array,
            range: TextRange::new(start, self.offset),
            children,
        })
    }

    fn parse_string(&mut self) -> Result<ParsedNode, String> {
        let start = self.offset;
        self.expect_char('"')?;
        loop {
            match self.bump_char() {
                Some('"') => {
                    return Ok(ParsedNode {
                        kind: JsonKind::String,
                        range: TextRange::new(start, self.offset),
                        children: Vec::new(),
                    });
                }
                Some('\\') => match self.bump_char() {
                    Some('"') | Some('\\') | Some('/') | Some('b') | Some('f') | Some('n')
                    | Some('r') | Some('t') => {}
                    Some('u') => {
                        for _ in 0..4 {
                            match self.bump_char() {
                                Some(ch) if ch.is_ascii_hexdigit() => {}
                                _ => {
                                    return Err(format!(
                                        "invalid unicode escape at byte {}",
                                        self.offset
                                    ));
                                }
                            }
                        }
                    }
                    _ => return Err(format!("invalid escape sequence at byte {}", self.offset)),
                },
                Some(ch) if ch >= '\u{20}' => {}
                Some(_) => {
                    return Err(format!(
                        "control character in string at byte {}",
                        self.offset
                    ));
                }
                None => return Err("unterminated string".to_string()),
            }
        }
    }

    fn parse_number(&mut self) -> Result<ParsedNode, String> {
        let start = self.offset;
        if self.peek_char() == Some('-') {
            self.bump_char();
        }

        match self.peek_char() {
            Some('0') => {
                self.bump_char();
            }
            Some('1'..='9') => {
                self.bump_char();
                while matches!(self.peek_char(), Some('0'..='9')) {
                    self.bump_char();
                }
            }
            _ => return Err(format!("invalid number at byte {}", self.offset)),
        }

        if self.peek_char() == Some('.') {
            self.bump_char();
            let mut digits = 0usize;
            while matches!(self.peek_char(), Some('0'..='9')) {
                digits += 1;
                self.bump_char();
            }
            if digits == 0 {
                return Err(format!("expected fraction digits at byte {}", self.offset));
            }
        }

        if matches!(self.peek_char(), Some('e' | 'E')) {
            self.bump_char();
            if matches!(self.peek_char(), Some('+' | '-')) {
                self.bump_char();
            }
            let mut digits = 0usize;
            while matches!(self.peek_char(), Some('0'..='9')) {
                digits += 1;
                self.bump_char();
            }
            if digits == 0 {
                return Err(format!("expected exponent digits at byte {}", self.offset));
            }
        }

        Ok(ParsedNode {
            kind: JsonKind::Number,
            range: TextRange::new(start, self.offset),
            children: Vec::new(),
        })
    }

    fn parse_literal(&mut self, literal: &str, kind: JsonKind) -> Result<ParsedNode, String> {
        let start = self.offset;
        if !self.text[self.offset..].starts_with(literal) {
            return Err(format!("expected `{literal}` at byte {}", self.offset));
        }
        self.offset += literal.len();
        Ok(ParsedNode {
            kind,
            range: TextRange::new(start, self.offset),
            children: Vec::new(),
        })
    }
}

fn print_snapshot(snapshot: &ParseSnapshot, rope: &ReactiveRope, delta: Option<&EditDelta>) {
    let metrics = rope.snapshot_metrics();
    println!();
    println!("Document revision {}", metrics.revision);
    println!(
        "Text bytes={}, leaves={}, nodes={}, reused_on_last_edit={}",
        metrics.text.bytes, metrics.leaves, snapshot.total_nodes, snapshot.reused_nodes
    );
    if let Some(delta) = delta {
        println!(
            "Last edit: [{}..{}) -> {:?}",
            delta.range.start, delta.range.end, delta.inserted_text
        );
        let focus = delta.range.start.min(snapshot.text.len().saturating_sub(1));
        let path = path_for_offset(&snapshot.root, snapshot.root_anchor.byte, focus);
        println!("Changed path:");
        for (node, range) in path {
            let start = rope.point_at(range.start);
            let end = rope.point_at(range.end);
            println!(
                "  id={} kind={} span={}",
                node.id,
                node.kind,
                format_span(start, end)
            );
        }
    }
    if !snapshot.diagnostics.is_empty() {
        println!("Diagnostics:");
        for diagnostic in &snapshot.diagnostics {
            println!("  - {diagnostic}");
        }
    }
    println!("Root id={} kind={}", snapshot.root.id, snapshot.root.kind);
    println!("Parse tree:");
    print_tree(&snapshot.root, snapshot.root_anchor.byte, rope, 0);
    println!("Current text:");
    println!("{}", snapshot.text);
}

fn print_tree(node: &AstNode, parent_start: usize, rope: &ReactiveRope, depth: usize) {
    let range = absolute_range(node, parent_start);
    let start = rope.point_at(range.start);
    let end = rope.point_at(range.end);
    let indent = "  ".repeat(depth);
    println!(
        "{indent}- id={} kind={} span={}",
        node.id,
        node.kind,
        format_span(start, end)
    );
    for child in &node.children {
        print_tree(child, range.start, rope, depth + 1);
    }
}

fn path_for_offset(root: &AstNode, root_start: usize, offset: usize) -> Vec<(&AstNode, TextRange)> {
    fn walk<'a>(
        node: &'a AstNode,
        parent_start: usize,
        offset: usize,
        out: &mut Vec<(&'a AstNode, TextRange)>,
    ) -> bool {
        let range = absolute_range(node, parent_start);
        if offset < range.start || offset >= range.end {
            return false;
        }
        out.push((node, range));
        for child in &node.children {
            if walk(child, range.start, offset, out) {
                return true;
            }
        }
        true
    }

    let mut path = Vec::new();
    let _ = walk(root, root_start, offset, &mut path);
    path
}

fn format_span(start: TextPoint, end: TextPoint) -> String {
    format!(
        "{}:{}..{}:{}",
        start.line + 1,
        start.column + 1,
        end.line + 1,
        end.column + 1
    )
}

fn count_nodes(node: &AstNode) -> usize {
    1 + node.children.iter().map(count_nodes).sum::<usize>()
}
