//! NodeId construction.
//!
//! Format: `<repo_id>:<lang>:<escaped_path>:<escaped_qname>:<kind>`
//!
//! CORE-C1: `path` and `qname` components are backtick-escaped so that
//! colons in Rust qualified names (`Ns::Class::method`) and Windows
//! paths (`C:\Users\...`) don't collide with the outer `:` separator.
//!
//! Escape rules (SCIP-style precedent):
//! - `` ` `` → ` `` `` (double backtick)
//! - `:` → `` `: ``  (backtick-colon)
//!
//! Overloaded callable disambiguation via signature digest is not yet
//! used; once we need it, encode signature into `qname` (e.g. `foo(i32)`)
//! rather than re-introducing a parallel id helper.

use crate::Kind;

/// Escape `:` and `` ` `` in a node_id component so the outer `:`
/// separator is unambiguous.
#[inline]
pub fn node_id_escape(s: &str) -> String {
    let mut out = String::with_capacity(s.len());
    for c in s.chars() {
        match c {
            '`' => out.push_str("``"),
            ':' => out.push_str("`:"),
            _ => out.push(c),
        }
    }
    out
}

/// Reverse of [`node_id_escape`].
#[inline]
pub fn node_id_unescape(s: &str) -> String {
    let mut out = String::with_capacity(s.len());
    let mut chars = s.chars();
    while let Some(c) = chars.next() {
        if c == '`' {
            match chars.next() {
                Some('`') => out.push('`'),
                Some(':') => out.push(':'),
                Some(other) => {
                    // Malformed escape — preserve literally.
                    out.push('`');
                    out.push(other);
                }
                None => out.push('`'),
            }
        } else {
            out.push(c);
        }
    }
    out
}

/// Construct a `NodeId` for a symbol.
///
/// Public cross-crate helper called from every extractor and from
/// `Symbol::new`. Escaped components ensure the outer `:` separator
/// is unambiguous even when `path` or `qname` contain colons.
pub fn node_id(repo_id: &str, lang: &str, path: &str, qname: &str, kind: Kind) -> String {
    let epath = node_id_escape(path);
    let eqname = node_id_escape(qname);
    format!("{repo_id}:{lang}:{epath}:{eqname}:{}", kind.as_str())
}

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

    #[test]
    fn node_id_handles_colons_in_qname() {
        let id = node_id("abc123", "rust", "src/lib.rs", "Ns::Class::method", Kind::Function);
        // Outer separators still work — split on unescaped `:`.
        let parts: Vec<&str> = split_node_id(&id);
        assert_eq!(parts.len(), 5, "id={id}");
        assert_eq!(parts[0], "abc123");
        assert_eq!(parts[1], "rust");
        assert_eq!(node_id_unescape(parts[2]), "src/lib.rs");
        assert_eq!(node_id_unescape(parts[3]), "Ns::Class::method");
        assert_eq!(parts[4], "function");
    }

    #[test]
    fn node_id_handles_backtick_in_path() {
        let id = node_id("abc123", "ruby", "src/`special`.rb", "Foo", Kind::Class);
        let parts = split_node_id(&id);
        assert_eq!(node_id_unescape(parts[2]), "src/`special`.rb");
    }

    #[test]
    fn roundtrip_escape_unescape() {
        for input in &[
            "simple",
            "Ns::Class::method",
            "C:\\Users\\foo",
            "a`b:c`d::e",
            "",
            ":::",
            "```",
        ] {
            assert_eq!(node_id_unescape(&node_id_escape(input)), *input);
        }
    }

    /// Split a node_id on unescaped `:` separators.
    fn split_node_id(id: &str) -> Vec<&str> {
        let mut parts = Vec::new();
        let mut start = 0;
        let bytes = id.as_bytes();
        let mut i = 0;
        while i < bytes.len() {
            if bytes[i] == b'`' {
                i += 2; // skip escaped pair
            } else if bytes[i] == b':' {
                parts.push(&id[start..i]);
                start = i + 1;
                i += 1;
            } else {
                i += 1;
            }
        }
        parts.push(&id[start..]);
        parts
    }
}
