//! A tiny, dependency-free JSON value parser.
//!
//! Chrome extension manifests are small (a few kilobytes at most) and
//! structurally simple, so a recursive-descent parser over a `&str` is
//! sufficient and keeps the crate free of `serde` / `serde_json`. Only the
//! subset of JSON used by `manifest.json` is required — strings, numbers,
//! booleans, null, arrays, objects — but the parser is complete for any
//! well-formed JSON text.

use std::fmt;
use std::string::ToString;

/// A parse error: the byte offset into the source plus a short reason.
///
/// The offset is the index in the original input where parsing gave up, which
/// makes errors easy to point at in a manifest.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseError {
    /// 0-based byte offset into the input where parsing failed.
    pub offset: usize,
    /// A short human-readable reason.
    pub message: String,
}

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "json parse error at offset {}: {}", self.offset, self.message)
    }
}

impl std::error::Error for ParseError {}

impl ParseError {
    fn at(offset: usize, message: &str) -> Self {
        ParseError {
            offset,
            message: message.to_string(),
        }
    }
}

/// A parsed JSON value, modelled closely on the surface of a Chrome
/// `manifest.json`.
///
/// Owned rather than borrowing into the input because decoded strings (with
/// escape processing applied) may differ in length from their source. The
/// representation favours simplicity over performance: manifests are tiny.
#[derive(Debug, Clone, PartialEq)]
pub enum Json {
    Null,
    Bool(bool),
    /// Stored as a string to preserve integer-vs-float fidelity losslessly
    /// (Chrome manifest versions like `1.4.2` are strings anyway, and the
    /// `manifest_version` field is a small integer we parse via `as_i64`).
    Number(String),
    String(String),
    Array(Vec<Json>),
    /// An object whose keys are owned strings. Insertion-ordered, matching
    /// typical JSON parsers.
    Object(Vec<(String, Json)>),
}

impl Json {
    /// Returns `Some(&str)` if this value is a string.
    pub fn as_str(&self) -> Option<&str> {
        match self {
            Json::String(s) => Some(s.as_str()),
            _ => None,
        }
    }

    /// Returns `Some(i64)` if this value is a number that fits losslessly.
    pub fn as_i64(&self) -> Option<i64> {
        match self {
            Json::Number(s) => s.parse::<i64>().ok(),
            _ => None,
        }
    }

    /// Returns `Some(bool)` if this value is a boolean.
    pub fn as_bool(&self) -> Option<bool> {
        match self {
            Json::Bool(b) => Some(*b),
            _ => None,
        }
    }

    /// Returns `Some(&[Json])` if this value is an array.
    pub fn as_array(&self) -> Option<&[Json]> {
        match self {
            Json::Array(a) => Some(a.as_slice()),
            _ => None,
        }
    }

    /// Returns `Some(&[(String, Json)])` if this value is an object.
    pub fn as_object(&self) -> Option<&[(String, Json)]> {
        match self {
            Json::Object(o) => Some(o.as_slice()),
            _ => None,
        }
    }

    /// Look up a key in an object value. Returns `None` for non-objects or
    /// missing keys.
    pub fn get(&self, key: &str) -> Option<&Json> {
        match self {
            Json::Object(entries) => entries
                .iter()
                .find_map(|(k, v)| if k == key { Some(v) } else { None }),
            _ => None,
        }
    }
}

// ---------------------------------------------------------------------------
// Parser
// ---------------------------------------------------------------------------

struct Parser<'a> {
    bytes: &'a [u8],
    pos: usize,
}

impl<'a> Parser<'a> {
    fn new(input: &'a str) -> Self {
        Parser {
            bytes: input.as_bytes(),
            pos: 0,
        }
    }

    fn peek(&self) -> Option<u8> {
        self.bytes.get(self.pos).copied()
    }

    fn bump(&mut self) -> Option<u8> {
        let c = self.peek()?;
        self.pos += 1;
        Some(c)
    }

    fn err(&self, msg: &str) -> ParseError {
        ParseError::at(self.pos, msg)
    }

    /// Skip ASCII whitespace (space, tab, newline, carriage return). JSON
    /// whitespace is defined as exactly these four characters.
    fn skip_ws(&mut self) {
        while let Some(c) = self.peek() {
            match c {
                b' ' | b'\t' | b'\n' | b'\r' => {
                    self.pos += 1;
                }
                _ => break,
            }
        }
    }

    fn parse_value(&mut self) -> Result<Json, ParseError> {
        self.skip_ws();
        let c = self
            .peek()
            .ok_or_else(|| self.err("unexpected end of input"))?;
        let value = match c {
            b'{' => self.parse_object()?,
            b'[' => self.parse_array()?,
            b'"' => Json::String(self.parse_string()?),
            b't' | b'f' => self.parse_bool()?,
            b'n' => self.parse_null()?,
            b'-' | b'0'..=b'9' => self.parse_number()?,
            other => return Err(self.err(&format!("unexpected character {:#x}", other))),
        };
        Ok(value)
    }

    fn parse_object(&mut self) -> Result<Json, ParseError> {
        self.bump(); // '{'
        let mut entries: Vec<(String, Json)> = Vec::new();
        self.skip_ws();
        if self.peek() == Some(b'}') {
            self.bump();
            return Ok(Json::Object(entries));
        }
        loop {
            self.skip_ws();
            if self.peek() != Some(b'"') {
                return Err(self.err("expected string key in object"));
            }
            let key = self.parse_string()?;
            self.skip_ws();
            if self.peek() != Some(b':') {
                return Err(self.err("expected ':' after object key"));
            }
            self.bump(); // ':'
            let val = self.parse_value()?;
            entries.push((key, val));
            self.skip_ws();
            match self.peek() {
                Some(b',') => {
                    self.bump();
                    continue;
                }
                Some(b'}') => {
                    self.bump();
                    break;
                }
                _ => return Err(self.err("expected ',' or '}' in object")),
            }
        }
        Ok(Json::Object(entries))
    }

    fn parse_array(&mut self) -> Result<Json, ParseError> {
        self.bump(); // '['
        let mut items: Vec<Json> = Vec::new();
        self.skip_ws();
        if self.peek() == Some(b']') {
            self.bump();
            return Ok(Json::Array(items));
        }
        loop {
            let val = self.parse_value()?;
            items.push(val);
            self.skip_ws();
            match self.peek() {
                Some(b',') => {
                    self.bump();
                    continue;
                }
                Some(b']') => {
                    self.bump();
                    break;
                }
                _ => return Err(self.err("expected ',' or ']' in array")),
            }
        }
        Ok(Json::Array(items))
    }

    fn parse_string(&mut self) -> Result<String, ParseError> {
        // Opening quote.
        if self.bump() != Some(b'"') {
            return Err(self.err("expected opening '\"'"));
        }
        let mut out = String::new();
        loop {
            let c = self
                .bump()
                .ok_or_else(|| self.err("unterminated string"))?;
            match c {
                b'"' => break,
                b'\\' => {
                    let esc = self
                        .bump()
                        .ok_or_else(|| self.err("trailing escape"))?;
                    match esc {
                        b'"' => out.push('"'),
                        b'\\' => out.push('\\'),
                        b'/' => out.push('/'),
                        b'b' => out.push('\u{0008}'),
                        b'f' => out.push('\u{000C}'),
                        b'n' => out.push('\n'),
                        b'r' => out.push('\r'),
                        b't' => out.push('\t'),
                        b'u' => {
                            let cp = self.parse_unicode_escape()?;
                            out.push(cp);
                        }
                        other => {
                            return Err(self.err(&format!("invalid escape \\{}", other as char)))
                        }
                    }
                }
                // Control characters (U+0000..U+001F) must be escaped per RFC
                // 8259; reject them so a manifest can't smuggle raw control
                // bytes.
                0x00..=0x1F => return Err(self.err("unescaped control character in string")),
                // A single byte with the high bit set is part of a UTF-8
                // sequence; consume the rest of the code point.
                0x80..=0xFF => {
                    let cp = self.continue_utf8(c)?;
                    out.push(cp);
                }
                // Plain ASCII.
                _ => out.push(c as char),
            }
        }
        Ok(out)
    }

    /// Decode a `\uXXXX` escape, including a surrogate pair when followed by
    /// a second `\uXXXX`. Returns the resulting `char`.
    fn parse_unicode_escape(&mut self) -> Result<char, ParseError> {
        let hi = self.parse_hex4()?;
        // High surrogate -> expect a low surrogate following.
        if (0xD800..=0xDBFF).contains(&hi) {
            if self.bump() != Some(b'\\') || self.bump() != Some(b'u') {
                return Err(self.err("expected '\\u' low surrogate after high surrogate"));
            }
            let lo = self.parse_hex4()?;
            if !(0xDC00..=0xDFFF).contains(&lo) {
                return Err(self.err("invalid low surrogate"));
            }
            let codepoint = 0x10000 + (((hi - 0xD800) << 10) | (lo - 0xDC00));
            char::from_u32(codepoint).ok_or_else(|| self.err("invalid surrogate pair codepoint"))
        } else if (0xDC00..=0xDFFF).contains(&hi) {
            Err(self.err("unexpected low surrogate without preceding high surrogate"))
        } else {
            char::from_u32(hi).ok_or_else(|| self.err("invalid codepoint"))
        }
    }

    fn parse_hex4(&mut self) -> Result<u32, ParseError> {
        let mut acc: u32 = 0;
        for _ in 0..4 {
            let c = self
                .bump()
                .ok_or_else(|| self.err("truncated \\u escape"))?;
            let d = match c {
                b'0'..=b'9' => (c - b'0') as u32,
                b'a'..=b'f' => (c - b'a' + 10) as u32,
                b'A'..=b'F' => (c - b'A' + 10) as u32,
                _ => return Err(self.err("invalid hex digit in \\u escape")),
            };
            acc = acc * 16 + d;
        }
        Ok(acc)
    }

    /// Given the first byte of a multi-byte UTF-8 sequence, read enough
    /// continuation bytes to reconstruct the `char`.
    fn continue_utf8(&mut self, first: u8) -> Result<char, ParseError> {
        let (len, mut codepoint) = if first & 0xE0 == 0xC0 {
            (2, (first & 0x1F) as u32)
        } else if first & 0xF0 == 0xE0 {
            (3, (first & 0x0F) as u32)
        } else if first & 0xF8 == 0xF0 {
            (4, (first & 0x07) as u32)
        } else {
            return Err(self.err("invalid UTF-8 lead byte"));
        };
        for _ in 1..len {
            let c = self
                .bump()
                .ok_or_else(|| self.err("truncated UTF-8 sequence"))?;
            if c & 0xC0 != 0x80 {
                return Err(self.err("invalid UTF-8 continuation byte"));
            }
            codepoint = (codepoint << 6) | ((c & 0x3F) as u32);
        }
        char::from_u32(codepoint).ok_or_else(|| self.err("invalid UTF-8 codepoint"))
    }

    fn parse_bool(&mut self) -> Result<Json, ParseError> {
        if self.bytes[self.pos..].starts_with(b"true") {
            self.pos += 4;
            Ok(Json::Bool(true))
        } else if self.bytes[self.pos..].starts_with(b"false") {
            self.pos += 5;
            Ok(Json::Bool(false))
        } else {
            Err(self.err("expected 'true' or 'false'"))
        }
    }

    fn parse_null(&mut self) -> Result<Json, ParseError> {
        if self.bytes[self.pos..].starts_with(b"null") {
            self.pos += 4;
            Ok(Json::Null)
        } else {
            Err(self.err("expected 'null'"))
        }
    }

    fn parse_number(&mut self) -> Result<Json, ParseError> {
        let start = self.pos;
        if self.peek() == Some(b'-') {
            self.bump();
        }
        // Integer part.
        match self.peek() {
            Some(b'0') => {
                self.bump();
            }
            Some(c) if c.is_ascii_digit() => {
                while self.peek().map_or(false, |c| c.is_ascii_digit()) {
                    self.bump();
                }
            }
            _ => return Err(self.err("expected digit in number")),
        }
        // Fractional part.
        if self.peek() == Some(b'.') {
            self.bump();
            if !self.peek().map_or(false, |c| c.is_ascii_digit()) {
                return Err(self.err("expected digit after decimal point"));
            }
            while self.peek().map_or(false, |c| c.is_ascii_digit()) {
                self.bump();
            }
        }
        // Exponent.
        if matches!(self.peek(), Some(b'e') | Some(b'E')) {
            self.bump();
            if matches!(self.peek(), Some(b'+') | Some(b'-')) {
                self.bump();
            }
            if !self.peek().map_or(false, |c| c.is_ascii_digit()) {
                return Err(self.err("expected digit in exponent"));
            }
            while self.peek().map_or(false, |c| c.is_ascii_digit()) {
                self.bump();
            }
        }
        let text = core::str::from_utf8(&self.bytes[start..self.pos])
            .map_err(|_| self.err("non-utf8 number literal"))?;
        Ok(Json::Number(text.to_string()))
    }
}

/// Parse a JSON document into a [`Json`] value.
pub fn parse(input: &str) -> Result<Json, ParseError> {
    let mut p = Parser::new(input);
    let value = p.parse_value()?;
    p.skip_ws();
    if p.pos != p.bytes.len() {
        return Err(ParseError::at(p.pos, "trailing characters after JSON value"));
    }
    Ok(value)
}
