//! Protobuf wire-format encoder — byte-exact match to the real Antigravity webview.
//!
//! This is a minimal, hand-rolled encoder. We do NOT use prost or any codegen
//! because we need precise control over field ordering and encoding to produce
//! byte-identical output to the captured webview traffic.
//!
//! The LS communicates with Google via gRPC over H2, using proto messages from
//! the `exa.*_pb` package family. Init metadata (field 34 of the init request)
//! carries the `detect_and_use_proxy` enum, model selection, and version info.
//! See `docs/ls-binary-analysis.md` for the full proto schema reverse engineering.

pub mod wire;

use crate::constants::{client_version, CLIENT_NAME};

// ─── Wire primitives ────────────────────────────────────────────────────────

/// Encode a varint (base-128, little-endian, MSB continuation).
pub fn varint(mut val: u64) -> Vec<u8> {
    if val == 0 {
        return vec![0x00];
    }
    let mut out = Vec::with_capacity(10);
    while val > 0x7F {
        out.push(((val & 0x7F) | 0x80) as u8);
        val >>= 7;
    }
    out.push((val & 0x7F) as u8);
    out
}

/// Encode a field tag (field_number << 3 | wire_type).
pub fn tag(field: u32, wire: u8) -> Vec<u8> {
    varint(((field as u64) << 3) | (wire as u64))
}

/// Wire type 2: length-delimited string/bytes field.
pub fn proto_string(field: u32, val: &[u8]) -> Vec<u8> {
    let mut out = tag(field, 2);
    out.extend(varint(val.len() as u64));
    out.extend_from_slice(val);
    out
}

/// Wire type 2: length-delimited sub-message field.
pub fn proto_message(field: u32, inner: &[u8]) -> Vec<u8> {
    let mut out = tag(field, 2);
    out.extend(varint(inner.len() as u64));
    out.extend_from_slice(inner);
    out
}

/// Wire type 0: boolean field (varint 0 or 1).
pub fn bool_field(field: u32, val: bool) -> Vec<u8> {
    let mut out = tag(field, 0);
    out.extend(varint(if val { 1 } else { 0 }));
    out
}

/// Wire type 0: varint field.
pub fn varint_field(field: u32, val: u64) -> Vec<u8> {
    let mut out = tag(field, 0);
    out.extend(varint(val));
    out
}

// ─── Init metadata builder (for standalone LS stdin) ─────────────────────────

/// Build the init metadata protobuf that the LS expects on stdin at startup.
///
/// This replaces the Python snippet in `standalone-ls.sh` with proper Rust encoding.
/// Fields match what the real Antigravity extension sends to the LS.
///
/// Field layout (from binary analysis):
///   1: api_key (string) — unique session key
///   3: ide_name (string) — "antigravity"
///   4: antigravity_version (string) — e.g. "1.107.0"
///   5: ide_version (string) — e.g. "1.16.5"
///   6: locale (string) — "en_US"
///  10: session_id (string) — unique session identifier
///  11: editor_name (string) — "antigravity"
///  34: detect_and_use_proxy (varint enum) — 1 = ENABLED
pub fn build_init_metadata(
    api_key: &str,
    antigravity_version: &str,
    ide_version: &str,
    session_id: &str,
    detect_and_use_proxy: u64,
) -> Vec<u8> {
    let mut buf = Vec::with_capacity(128);

    // Field 1: api_key
    buf.extend(proto_string(1, api_key.as_bytes()));
    // Field 3: ide_name
    buf.extend(proto_string(3, CLIENT_NAME.as_bytes()));
    // Field 4: antigravity version
    buf.extend(proto_string(4, antigravity_version.as_bytes()));
    // Field 5: IDE/client version
    buf.extend(proto_string(5, ide_version.as_bytes()));
    // Field 6: locale
    buf.extend(proto_string(6, b"en_US"));
    // Field 10: session_id
    buf.extend(proto_string(10, session_id.as_bytes()));
    // Field 11: editor_name
    buf.extend(proto_string(11, CLIENT_NAME.as_bytes()));
    // Field 34: detect_and_use_proxy enum (1 = ENABLED)
    buf.extend(varint_field(34, detect_and_use_proxy));

    buf
}

// ─── SendUserCascadeMessageRequest builder ───────────────────────────────────

/// Build the `SendUserCascadeMessageRequest` protobuf binary.
///
/// See `build_request_with_image` for the full field layout documentation.
///
/// Field layout (key fields):
///   1: cascade_id (string)
///   2: items (ChatMessage { 1: text })
///   3: metadata { 1: client_name, 3: oauth_token, 4: "en", 7: version, 12: client_name }
///   5: cascade_config (PlannerConfig)
///   6: images (ImageData { 1: base64_data, 2: mime_type }, repeated)
///  11: client_type
#[allow(dead_code)]
pub fn build_request(cascade_id: &str, text: &str, oauth_token: &str, model_enum: u32) -> Vec<u8> {
    build_request_with_image(cascade_id, text, oauth_token, model_enum, None)
}

/// Image data to attach to a cascade message.
pub struct ImageData {
    /// MIME type, e.g. "image/png", "image/jpeg", "image/webp", "image/gif"
    pub mime_type: String,
    /// Raw image bytes (NOT base64 — we base64-encode them into the proto)
    pub data: Vec<u8>,
}

/// Build `SendUserCascadeMessageRequest` with optional image attachment.
///
/// Field layout (from JS protobuf-es registration in workbench bundle):
///   1: cascade_id    (string)
///   2: items         (ChatMessage, repeated) — contains the text
///   3: metadata      (auto-populated by LS)
///   4: experiment_config
///   5: cascade_config (PlannerConfig)
///   6: images        (ImageData, repeated) — TOP-LEVEL, NOT inside ChatMessage
///   8: blocking      (bool)
///   9: additional_steps (repeated)
///  10: artifact_comments (repeated)
///  11: client_type   (enum)
///  12: file_diff_comments (repeated)
///  13: file_comments (repeated)
///  14: media         (repeated)
///  15: agent_script_item
///  16: propagate_error (bool)
///  17: planner_response
///
/// ImageData proto (from codeium_common_pb):
///   1: base64_data   (string) — BASE64 encoded, NOT raw bytes
///   2: mime_type     (string)
///   3: caption       (string)
///   4: uri           (string)
pub fn build_request_with_image(
    cascade_id: &str,
    text: &str,
    oauth_token: &str,
    model_enum: u32,
    image: Option<&ImageData>,
) -> Vec<u8> {
    let mut msg = Vec::with_capacity(256);

    // Field 1: cascade_id
    msg.extend(proto_string(1, cascade_id.as_bytes()));

    // Field 2: items (ChatMessage) — text only, no blob
    let chat_msg = proto_string(1, text.as_bytes());
    msg.extend(proto_message(2, &chat_msg));

    // Field 3: Metadata (Auth + Client ID)
    let mut meta = Vec::new();
    meta.extend(proto_string(1, CLIENT_NAME.as_bytes()));
    meta.extend(proto_string(3, oauth_token.as_bytes()));
    meta.extend(proto_string(4, b"en"));
    meta.extend(proto_string(7, client_version().as_bytes()));
    meta.extend(proto_string(12, CLIENT_NAME.as_bytes()));
    msg.extend(proto_message(3, &meta));

    // Field 5: PlannerConfig (cascade_config)
    let mut inner = Vec::new();

    // field 2: conversational mode { f4: 1, f14: 0 }
    let conv_mode = [varint_field(4, 1), varint_field(14, 0)].concat();
    inner.extend(proto_message(2, &conv_mode));

    // field 13: toolConfig
    //   field 8 (runCommand): field 3 (autoCommandConfig) -> field 6 (policy) = 3 (EAGER)
    //   field 33 (artifactReviewPolicy): field 1 = 2 (TURBO)
    let run_cmd = proto_message(3, &varint_field(6, 3));
    let tool_config = [
        proto_message(8, &run_cmd),
        proto_message(33, &varint_field(1, 2)),
    ]
    .concat();
    inner.extend(proto_message(13, &tool_config));

    // field 15: requested model { f1: model_enum }
    inner.extend(proto_message(15, &varint_field(1, model_enum as u64)));

    // field 21: ephemeral messages config { f1: 1 }
    inner.extend(proto_message(21, &varint_field(1, 1)));

    // field 32: knowledge config { f1: true }
    inner.extend(proto_message(32, &bool_field(1, true)));

    // Field 5 wraps: field 1 (inner config) + field 7 { f1: 1 }
    let f5_payload = [
        proto_message(1, &inner),
        proto_message(7, &varint_field(1, 1)),
    ]
    .concat();
    msg.extend(proto_message(5, &f5_payload));

    // Field 6: images (ImageData, repeated) — TOP-LEVEL field
    // ImageData { 1: base64_data (string), 2: mime_type (string) }
    if let Some(img) = image {
        use base64::Engine;
        let b64 = base64::engine::general_purpose::STANDARD.encode(&img.data);
        let mut image_data = proto_string(1, b64.as_bytes());
        image_data.extend(proto_string(2, img.mime_type.as_bytes()));
        msg.extend(proto_message(6, &image_data));
    }

    // Field 11: client_type (but we use it for conversation history flag)
    msg.extend(bool_field(11, true));

    msg
}

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

    #[test]
    fn test_varint_zero() {
        assert_eq!(varint(0), vec![0x00]);
    }

    #[test]
    fn test_varint_small() {
        assert_eq!(varint(1), vec![0x01]);
        assert_eq!(varint(127), vec![0x7F]);
    }

    #[test]
    fn test_varint_multibyte() {
        assert_eq!(varint(128), vec![0x80, 0x01]);
        assert_eq!(varint(300), vec![0xAC, 0x02]);
    }

    #[test]
    fn test_varint_1026() {
        // model_enum 1026 = 0x402 → varint [0x82, 0x08]
        assert_eq!(varint(1026), vec![0x82, 0x08]);
    }

    #[test]
    fn test_tag() {
        // field 1, wire type 2 (LEN) = (1 << 3) | 2 = 0x0A
        assert_eq!(tag(1, 2), vec![0x0A]);
        // field 3, wire type 0 (VARINT) = (3 << 3) | 0 = 0x18
        assert_eq!(tag(3, 0), vec![0x18]);
        // field 33, wire type 2 = (33 << 3) | 2 = 266 → varint [0x8A, 0x02]
        assert_eq!(tag(33, 2), vec![0x8A, 0x02]);
    }

    #[test]
    fn test_proto_string() {
        let result = proto_string(1, b"hi");
        // tag(1,2) = 0x0A, len=2, 'h'=0x68, 'i'=0x69
        assert_eq!(result, vec![0x0A, 0x02, 0x68, 0x69]);
    }

    #[test]
    fn test_build_request_deterministic() {
        let a = build_request("cid", "hello", "ya29.tok", 1026);
        let b = build_request("cid", "hello", "ya29.tok", 1026);
        assert_eq!(a, b, "build_request must be deterministic");
    }

    #[test]
    fn test_build_request_structure() {
        let msg = build_request("test-cascade-id", "hello", "ya29.test-token", 1026);

        assert_eq!(msg[0], 0x0A, "first byte must be field 1 tag");

        let cascade_bytes = b"test-cascade-id";
        assert!(
            msg.windows(cascade_bytes.len()).any(|w| w == cascade_bytes),
            "cascade_id must appear in output"
        );

        assert!(
            msg.windows(5).any(|w| w == b"hello"),
            "text must appear in output"
        );

        let token_bytes = b"ya29.test-token";
        assert!(
            msg.windows(token_bytes.len()).any(|w| w == token_bytes),
            "oauth token must appear in output"
        );

        // model enum 1026 varint [0x82, 0x08]
        assert!(
            msg.windows(2).any(|w| w == [0x82, 0x08]),
            "model enum 1026 varint must appear in output"
        );

        // field 11 bool true at end: tag(11,0)=0x58, varint(1)=0x01
        let len = msg.len();
        assert_eq!(msg[len - 2], 0x58);
        assert_eq!(msg[len - 1], 0x01);
    }

    /// Cross-verified against Python output: 127/127 bytes identical.
    #[test]
    fn test_byte_exact_match_with_python() {
        let msg = build_request("test-cascade-id", "hello", "ya29.test-token", 1026);
        let hex: String = msg.iter().map(|b| format!("{:02x}", b)).collect();
        let expected = "0a0f746573742d636173636164652d696412070a0568656c6c6f\
            1a370a0b616e7469677261766974791a0f796132392e746573742d746f6b656e\
            2202656e3a06312e31362e35620b616e7469677261766974792a280a22120420\
            0170006a0b42041a0230038a020208027a03088208aa010208018202020801\
            3a0208015801";
        assert_eq!(hex, expected, "must be byte-exact match with Python");
        assert_eq!(msg.len(), 127);
    }
}
