//! §3.1 step 1's reading of a claim's JSON text (`extensions/jwp-receipts.md`).
//!
//! RFC 8259's grammar, whitespace and string escapes, with the claim's own limits. What
//! it refuses is `malformed`:
//!
//! - anything outside RFC 8259's grammar, including a byte-order mark, a control
//!   character inside a string, and trailing data;
//! - a member name repeated in one object. RFC 8785 is defined over I-JSON (RFC 7493),
//!   which forbids them, and two parsers that resolve a duplicate differently would
//!   canonicalise one receipt two ways;
//! - a `\u` escape that leaves a surrogate unpaired, which denotes no Unicode text
//!   (RFC 8259 §8.2);
//! - arrays and objects nested more than [`MAX_DEPTH`] levels deep, the claim itself
//!   being the first.
//!
//! A number is never refused here, whatever its size: every number is valid JSON, and
//! whether it is one a claim may carry is a type question (`field_type`), answered from
//! its spelling alone. An integer with no sign, fraction or exponent that fits in a
//! `u64` is kept as that integer; any other number — `-0`, `1.0`, `1e400`, an integer of
//! four hundred digits — is outside the claim subset and is read as `-1`, which is all
//! the claim reading needs to know about it. No number goes through a float parser, so
//! no reading depends on how one rounds.
//!
//! This reads claims and nothing else. An OER receipt an `energy` claim names is read
//! under oer-receipts' own document profile, by `crate::oer::load`.

use serde_json::{Map, Value};

/// The deepest nesting of arrays and objects a claim may have, counting the receipt
/// itself as the first level. A claim needs five; a parser needs a bound.
pub(crate) const MAX_DEPTH: usize = 32;

/// Read `s` as one claim text. The error says what was refused and where.
pub(crate) fn read(s: &str) -> Result<Value, String> {
    let mut p = Text { s, b: s.as_bytes(), i: 0 };
    p.ws();
    let v = p.value(1)?;
    p.ws();
    if p.i != p.b.len() {
        return Err(p.err("trailing data"));
    }
    Ok(v)
}

struct Text<'a> {
    s: &'a str,
    b: &'a [u8],
    i: usize,
}

impl Text<'_> {
    fn err(&self, what: &str) -> String {
        format!("{what} at byte {}", self.i)
    }

    fn ws(&mut self) {
        while matches!(self.b.get(self.i), Some(b' ' | b'\t' | b'\n' | b'\r')) {
            self.i += 1;
        }
    }

    fn eat(&mut self, c: u8) -> bool {
        let hit = self.b.get(self.i) == Some(&c);
        self.i += usize::from(hit);
        hit
    }

    fn literal(&mut self, word: &str, v: Value) -> Result<Value, String> {
        if self.b[self.i..].starts_with(word.as_bytes()) {
            self.i += word.len();
            Ok(v)
        } else {
            Err(self.err("not a JSON value"))
        }
    }

    /// A value at nesting level `depth` (the level it occupies if it is an array or an
    /// object).
    fn value(&mut self, depth: usize) -> Result<Value, String> {
        match self.b.get(self.i) {
            Some(b'{' | b'[') if depth > MAX_DEPTH => Err(self.err("nested too deeply")),
            Some(b'{') => {
                self.i += 1;
                self.object(depth)
            }
            Some(b'[') => {
                self.i += 1;
                self.array(depth)
            }
            Some(b'"') => self.string().map(Value::String),
            Some(b't') => self.literal("true", Value::Bool(true)),
            Some(b'f') => self.literal("false", Value::Bool(false)),
            Some(b'n') => self.literal("null", Value::Null),
            Some(b'-' | b'0'..=b'9') => self.number(),
            _ => Err(self.err("not a JSON value")),
        }
    }

    fn object(&mut self, depth: usize) -> Result<Value, String> {
        let mut m = Map::new();
        self.ws();
        if self.eat(b'}') {
            return Ok(Value::Object(m));
        }
        loop {
            self.ws();
            if self.b.get(self.i) != Some(&b'"') {
                return Err(self.err("expected a member name"));
            }
            let k = self.string()?;
            self.ws();
            if !self.eat(b':') {
                return Err(self.err("expected ':'"));
            }
            self.ws();
            let v = self.value(depth + 1)?;
            if m.insert(k, v).is_some() {
                return Err(self.err("duplicate member name"));
            }
            self.ws();
            if self.eat(b'}') {
                return Ok(Value::Object(m));
            }
            if !self.eat(b',') {
                return Err(self.err("expected ',' or '}'"));
            }
        }
    }

    fn array(&mut self, depth: usize) -> Result<Value, String> {
        let mut a = Vec::new();
        self.ws();
        if self.eat(b']') {
            return Ok(Value::Array(a));
        }
        loop {
            self.ws();
            a.push(self.value(depth + 1)?);
            self.ws();
            if self.eat(b']') {
                return Ok(Value::Array(a));
            }
            if !self.eat(b',') {
                return Err(self.err("expected ',' or ']'"));
            }
        }
    }

    fn string(&mut self) -> Result<String, String> {
        self.i += 1; // the opening quote
        let mut out = String::new();
        loop {
            // A run of characters written as themselves. It can only end at an ASCII
            // byte, so it never splits a UTF-8 sequence.
            let start = self.i;
            while self.b.get(self.i).is_some_and(|&c| c != b'"' && c != b'\\' && c >= 0x20) {
                self.i += 1;
            }
            out.push_str(&self.s[start..self.i]);
            match self.b.get(self.i) {
                Some(b'"') => {
                    self.i += 1;
                    return Ok(out);
                }
                Some(b'\\') => {
                    self.i += 1;
                    let c = self.escape()?;
                    out.push(c);
                }
                Some(_) => return Err(self.err("control character in a string")),
                None => return Err(self.err("unterminated string")),
            }
        }
    }

    fn escape(&mut self) -> Result<char, String> {
        let c = *self.b.get(self.i).ok_or_else(|| self.err("unterminated escape"))?;
        self.i += 1;
        Ok(match c {
            b'"' => '"',
            b'\\' => '\\',
            b'/' => '/',
            b'b' => '\u{8}',
            b'f' => '\u{c}',
            b'n' => '\n',
            b'r' => '\r',
            b't' => '\t',
            b'u' => {
                let unpaired = "an escape that leaves a surrogate unpaired";
                match self.hex4()? {
                    hi @ 0xd800..=0xdbff => {
                        if !(self.eat(b'\\') && self.eat(b'u')) {
                            return Err(self.err(unpaired));
                        }
                        let lo = self.hex4()?;
                        if !(0xdc00..=0xdfff).contains(&lo) {
                            return Err(self.err(unpaired));
                        }
                        char::from_u32(0x10000 + ((hi - 0xd800) << 10) + (lo - 0xdc00))
                            .expect("a supplementary code point")
                    }
                    0xdc00..=0xdfff => return Err(self.err(unpaired)),
                    u => char::from_u32(u).expect("a scalar value of the BMP"),
                }
            }
            _ => return Err(self.err("not a JSON escape")),
        })
    }

    fn hex4(&mut self) -> Result<u32, String> {
        let h = self.b.get(self.i..self.i + 4).filter(|h| h.iter().all(u8::is_ascii_hexdigit));
        let h = h.ok_or_else(|| self.err("\\u needs four hex digits"))?;
        self.i += 4;
        Ok(h.iter().fold(0, |a, &c| a * 16 + (c as char).to_digit(16).expect("a hex digit")))
    }

    fn digits(&mut self) -> Result<(), String> {
        let start = self.i;
        while self.b.get(self.i).is_some_and(u8::is_ascii_digit) {
            self.i += 1;
        }
        if self.i == start { Err(self.err("expected a digit")) } else { Ok(()) }
    }

    /// RFC 8259 §6's grammar, checked in full; the value is the integer only for a plain
    /// unsigned integer that fits in a `u64`, and `-1` (outside the claim subset) for
    /// every other number.
    fn number(&mut self) -> Result<Value, String> {
        let signed = self.eat(b'-');
        let int_start = self.i;
        match self.b.get(self.i) {
            Some(b'0') => self.i += 1,
            Some(b'1'..=b'9') => self.digits()?,
            _ => return Err(self.err("not a JSON number")),
        }
        let int_end = self.i;
        let mut integer = true;
        if self.eat(b'.') {
            integer = false;
            self.digits()?;
        }
        if matches!(self.b.get(self.i), Some(b'e' | b'E')) {
            integer = false;
            self.i += 1;
            if matches!(self.b.get(self.i), Some(b'+' | b'-')) {
                self.i += 1;
            }
            self.digits()?;
        }
        let unsigned = if integer && !signed { self.s[int_start..int_end].parse::<u64>().ok() } else { None };
        Ok(unsigned.map_or(Value::from(-1i64), Value::from))
    }
}

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

    #[test]
    fn duplicate_members_are_refused_at_any_depth() {
        assert!(read("{\"a\":1,\"a\":1}").is_err());
        assert!(read("{\"a\":{\"b\":1,\"b\":2}}").is_err());
        assert!(read("[{\"b\":1},{\"b\":2}]").is_ok());
        assert!(read("{\"a\":1} x").is_err(), "trailing data is not one value");
    }

    #[test]
    fn a_claim_text_is_read_one_way() {
        // Surrogates: a pair is one character; anything unpaired is not text.
        assert_eq!(read(r#""\ud83d\ude00""#), Ok(Value::String("\u{1F600}".into())));
        assert_eq!(read(r#""\uD83D\uDE00""#), Ok(Value::String("\u{1F600}".into())));
        for t in [r#""\ud800""#, r#""\udc00""#, r#""\ud800\u0041""#, r#""\ud800\ud800""#, r#""\ud800\n""#, r#"{"\udfff":1}"#] {
            assert!(read(t).is_err(), "{t}");
        }
        // Nesting: the receipt is level 1, and 32 levels are read.
        let deep = |n: usize| format!("{}{}", "[".repeat(n), "]".repeat(n));
        assert!(read(&deep(32)).is_ok());
        assert!(read(&deep(33)).is_err());
        assert!(read(&deep(100_000)).is_err(), "refused before any recursion that deep");
        // Numbers are never a parse failure; only an unsigned integer that fits in a u64
        // is kept as one.
        assert_eq!(read("9007199254740991"), Ok(Value::from(9_007_199_254_740_991u64)));
        assert_eq!(read("18446744073709551615"), Ok(Value::from(u64::MAX)));
        let four_hundred_digits = format!("1{}", "0".repeat(400));
        for t in ["-0", "-1", "0.0", "1e0", "1E2", "1e400", "-1e400", "18446744073709551616", four_hundred_digits.as_str()] {
            assert_eq!(read(t), Ok(Value::from(-1i64)), "{t}");
        }
        // Outside RFC 8259.
        for t in ["01", "+1", ".5", "1.", "1e", "1e+", "-", "0x10", "NaN", "Infinity", "-Infinity", "\u{ff11}", "\u{feff}{}",
            "{} x", "", " ", "\"\u{1}\"", "\"\\x41\"", "\"\\u12\"", "{\"a\":1,}", "[1,]", "tru", "nul", "{\"a\" 1}", "{,}", "'a'",
            "\"a", "\"\t\""] {
            assert!(read(t).is_err(), "{t:?}");
        }
        // Everything the canonical subset holds reads as serde_json reads it, member order
        // included.
        for t in [r#"{"b":[1,"x\u00e9\/\n",null,{"c":[]}],"a":"\u001f\"","é":9007199254740991}"#, " [ ] ", "\"\u{2028}\u{7f}\"",
            "{\"k\u{e9}\":\"\u{1f600}\"}", "{}", "0", "\"\""] {
            let want: Value = serde_json::from_str(t).unwrap();
            let got = read(t).unwrap();
            assert_eq!(got, want, "{t}");
            assert_eq!(serde_json::to_string(&got).unwrap(), serde_json::to_string(&want).unwrap(), "{t}: order");
        }
    }
}
