use std::{
    collections::HashMap,
    ops::Range,
    sync::{Mutex, OnceLock},
};

use crate::combine::parser::Node;

// ---------------------------------------------------------------------------
// Symbol — a handle to an interned string
// ---------------------------------------------------------------------------

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Symbol(usize);

impl Node for Symbol {}

impl std::fmt::Display for Symbol {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", lookup(*self))
    }
}

// ---------------------------------------------------------------------------
// Global intern state
// ---------------------------------------------------------------------------

struct GlobalInterner {
    /// String-literal cache (key = decoded content, e.g. "hello\nworld")
    strings: HashMap<String, Symbol>,
    /// Identifier cache (key = raw identifier text)
    idents: HashMap<String, Symbol>,
    arena: StringArena,
}

fn global() -> &'static Mutex<GlobalInterner> {
    static GLOBAL: OnceLock<Mutex<GlobalInterner>> = OnceLock::new();
    GLOBAL.get_or_init(|| {
        Mutex::new(GlobalInterner {
            strings: HashMap::new(),
            idents: HashMap::new(),
            arena: StringArena::new(),
        })
    })
}

// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------

/// Intern a raw string literal (with quotes and escapes) and return a Symbol.
/// Decodes the literal before storing (removes quotes, processes escapes).
pub fn intern_str(raw: &str) -> Result<Symbol, DecodeError> {
    let decoded = decode_string_literal(raw)?;
    let mut g = global().lock().unwrap();
    if let Some(&sym) = g.strings.get(&decoded) {
        return Ok(sym);
    }
    let sym = g.arena.push(&decoded);
    let owned = g.arena.get(sym).to_string();
    g.strings.insert(owned, sym);
    Ok(sym)
}

/// Intern an identifier (plain text, no quotes/escapes).
pub fn intern_ident(s: &str) -> Symbol {
    let mut g = global().lock().unwrap();
    if let Some(&sym) = g.idents.get(s) {
        return sym;
    }
    let sym = g.arena.push(s);
    let owned = g.arena.get(sym).to_string();
    g.idents.insert(owned, sym);
    sym
}

/// Retrieve the string associated with a Symbol.
pub fn lookup(sym: Symbol) -> String {
    global().lock().unwrap().arena.get(sym).to_string()
}

// ---------------------------------------------------------------------------
// Arena — bump-allocated string storage
// ---------------------------------------------------------------------------

#[derive(Default)]
struct StringArena {
    arena: String,
    slices: Vec<Range<usize>>,
}

impl StringArena {
    fn new() -> Self {
        Self {
            arena: String::new(),
            slices: Vec::new(),
        }
    }

    fn push(&mut self, key: &str) -> Symbol {
        let start = self.arena.len();
        self.arena.push_str(key);
        let end = self.arena.len();
        self.slices.push(start..end);
        Symbol(self.slices.len() - 1)
    }

    fn get(&self, symbol: Symbol) -> &str {
        &self.arena[self.slices[symbol.0].clone()]
    }

    #[allow(dead_code)]
    fn reserve(&mut self, additional: usize) {
        self.arena.reserve(additional);
    }
}

// ---------------------------------------------------------------------------
// String literal decoding
// ---------------------------------------------------------------------------

#[derive(Debug)]
pub enum DecodeError {
    MissingDelimiters,
    UnexpectedEnd,
    InvalidHexEscape,
    InvalidEscape(char),
}

fn decode_string_literal(source: &str) -> Result<String, DecodeError> {
    let s = source
        .strip_prefix('"')
        .and_then(|s| s.strip_suffix('"'))
        .ok_or(DecodeError::MissingDelimiters)?;

    let mut result = String::with_capacity(s.len());
    let mut chars = s.chars();

    while let Some(c) = chars.next() {
        if c != '\\' {
            result.push(c);
            continue;
        }

        match chars.next().ok_or(DecodeError::UnexpectedEnd)? {
            'n' => result.push('\n'),
            't' => result.push('\t'),
            'r' => result.push('\r'),
            '0' => result.push('\0'),
            '\\' => result.push('\\'),
            '\'' => result.push('\''),
            '"' => result.push('"'),
            'x' => {
                let hi = chars.next().ok_or(DecodeError::UnexpectedEnd)?;
                let lo = chars.next().ok_or(DecodeError::UnexpectedEnd)?;
                let val = u8::from_str_radix(&format!("{hi}{lo}"), 16)
                    .map_err(|_| DecodeError::InvalidHexEscape)?;
                result.push(val as char);
            }
            c => return Err(DecodeError::InvalidEscape(c)),
        }
    }

    Ok(result)
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

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

    fn decode(s: &str) -> Result<String, DecodeError> {
        decode_string_literal(s)
    }

    #[test]
    fn plain() {
        assert_eq!(decode(r#""hello""#).unwrap(), "hello");
    }
    #[test]
    fn empty() {
        assert_eq!(decode(r#""""#).unwrap(), "");
    }
    #[test]
    fn esc_n() {
        assert_eq!(decode(r#""\n""#).unwrap(), "\n");
    }
    #[test]
    fn esc_t() {
        assert_eq!(decode(r#""\t""#).unwrap(), "\t");
    }
    #[test]
    fn esc_r() {
        assert_eq!(decode(r#""\r""#).unwrap(), "\r");
    }
    #[test]
    fn esc_0() {
        assert_eq!(decode(r#""\0""#).unwrap(), "\0");
    }
    #[test]
    fn esc_backslash() {
        assert_eq!(decode(r#""\\""#).unwrap(), "\\");
    }
    #[test]
    fn esc_quote() {
        assert_eq!(decode(r#""\"""#).unwrap(), "\"");
    }
    #[test]
    fn esc_hex() {
        assert_eq!(decode(r#""\x41""#).unwrap(), "A");
    }
    #[test]
    fn err_no_delimiters() {
        assert!(matches!(
            decode("hello"),
            Err(DecodeError::MissingDelimiters)
        ));
    }
    #[test]
    fn err_invalid_esc() {
        assert!(matches!(
            decode(r#""\q""#),
            Err(DecodeError::InvalidEscape('q'))
        ));
    }
    #[test]
    fn err_unexpected_end() {
        assert!(matches!(
            decode(r#""\x4""#),
            Err(DecodeError::UnexpectedEnd)
        ));
    }

    #[test]
    fn intern_ident_lookup() {
        let s = intern_ident("foo");
        assert_eq!(lookup(s), "foo");
    }

    #[test]
    fn intern_ident_dedup() {
        assert_eq!(intern_ident("foo"), intern_ident("foo"));
    }

    #[test]
    fn intern_str_lookup() {
        let s = intern_str(r#""hello\nworld""#).unwrap();
        assert_eq!(lookup(s), "hello\nworld");
    }

    #[test]
    fn intern_str_dedup() {
        assert_eq!(
            intern_str(r#""hello""#).unwrap(),
            intern_str(r#""hello""#).unwrap()
        );
    }
}
