use core::range::Range;

use crate::{
    Result,
    ast::Document,
    format::Emitter,
    tokens::TokenStream,
    traverse::{Visitor, parse_tokens, visit_document},
};

pub fn uglify_str(json: &str) -> Result<String> {
    let mut visitor = UglifyEmitVisitor::default();
    parse_tokens(&mut TokenStream::new(json), json, true, &mut visitor)?;
    Ok(visitor.buf)
}

#[derive(Debug, Default)]
pub(crate) struct UglifyEmitVisitor {
    pub(crate) buf: String,
}

impl Emitter for UglifyEmitVisitor {
    fn push(&mut self, c: char) {
        self.buf.push(c);
    }

    fn push_str(&mut self, s: &str) {
        self.buf.push_str(s);
    }
}

impl<'a> Visitor<'a> for UglifyEmitVisitor {
    fn on_object_open(&mut self) {
        self.emit_object_open();
    }

    fn on_object_key(&mut self, _range: Range<usize>, key: &'a str) {
        self.emit_string(key);
    }

    fn on_object_key_val_delim(&mut self) {
        self.emit_key_val_delim();
    }

    fn on_object_close(&mut self) {
        self.emit_object_close();
    }

    fn on_array_open(&mut self) {
        self.emit_array_open();
    }

    fn on_array_close(&mut self) {
        self.emit_array_close();
    }

    fn on_null(&mut self) {
        self.emit_null();
    }

    fn on_string(&mut self, _range: Range<usize>, s: &'a str) {
        self.emit_string(s);
    }

    fn on_mantissa(&mut self, _range: Range<usize>, mantissa: &'a str) {
        self.emit_mantissa(mantissa);
    }

    fn on_exponent(&mut self, _range: Range<usize>, exponent: &'a str) {
        self.emit_exponent(exponent);
    }

    fn on_boolean(&mut self, b: bool) {
        self.emit_boolean(b);
    }

    fn on_item_delim(&mut self) {
        self.emit_item_delim();
    }
}

pub fn uglify_document<S: AsRef<str>>(doc: &Document<S>) -> String {
    let mut visitor = UglifyEmitVisitor::default();
    visit_document(doc, &mut visitor);
    visitor.buf
}

pub fn uglify_document_into<S: AsRef<str>>(buf: &mut String, doc: &Document<S>) {
    let mut visitor = UglifyEmitVisitor {
        buf: std::mem::take(buf),
    };
    visit_document(doc, &mut visitor);
    *buf = visitor.buf;
}
