pmat 3.15.0

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP, HTTP)
#![cfg_attr(coverage_nightly, coverage(off))]
// DAP (Debug Adapter Protocol) types
// Sprint 71 - TRACE-001: DAP Protocol Server Implementation
//
// Types for Debug Adapter Protocol communication

use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};

/// DAP Request structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DapRequest {
    pub seq: i64,
    #[serde(rename = "type")]
    pub type_field: String,
    pub command: String,
    #[serde(default)]
    pub arguments: serde_json::Value,
}

/// DAP Response structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DapResponse {
    pub seq: i64,
    #[serde(rename = "type")]
    pub type_field: String,
    pub request_seq: i64,
    pub success: bool,
    pub command: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub message: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub body: Option<serde_json::Value>,
}

impl DapResponse {
    /// Create a successful response
    #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
    pub fn success(
        request_seq: i64,
        seq: i64,
        command: String,
        body: Option<serde_json::Value>,
    ) -> Self {
        Self {
            seq,
            type_field: "response".to_string(),
            request_seq,
            success: true,
            command,
            message: None,
            body,
        }
    }

    /// Create an error response
    #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
    pub fn error(request_seq: i64, seq: i64, command: String, message: String) -> Self {
        Self {
            seq,
            type_field: "response".to_string(),
            request_seq,
            success: false,
            command,
            message: Some(message),
            body: None,
        }
    }
}

/// DAP Event structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DapEvent {
    pub seq: i64,
    #[serde(rename = "type")]
    pub type_field: String,
    pub event: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub body: Option<serde_json::Value>,
}

/// DAP Capabilities structure
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct DapCapabilities {
    pub supports_configuration_done_request: bool,
    pub supports_function_breakpoints: bool,
    pub supports_conditional_breakpoints: bool,
    pub supports_hit_conditional_breakpoints: bool,
    pub supports_evaluate_for_hovers: bool,
    pub supports_step_back: bool,
    pub supports_set_variable: bool,
    pub supports_restart_frame: bool,
    pub supports_goto_targets_request: bool,
    pub supports_step_in_targets_request: bool,
    pub supports_completions_request: bool,
    pub supports_modules_request: bool,
    pub supports_restart_request: bool,
    pub supports_exception_options: bool,
    pub supports_value_formatting_options: bool,
    pub supports_exception_info_request: bool,
    pub supports_terminate_debuggee: bool,
    pub supports_delayed_stack_trace_loading: bool,
    pub supports_loaded_sources_request: bool,
    pub supports_log_points: bool,
    pub supports_terminate_threads_request: bool,
    pub supports_set_expression: bool,
    pub supports_terminate_request: bool,
    pub supports_data_breakpoints: bool,
    pub supports_read_memory_request: bool,
    pub supports_write_memory_request: bool,
    pub supports_disassemble_request: bool,
    pub supports_cancel_request: bool,
    pub supports_breakpoint_locations_request: bool,
    pub supports_clipboard_context: bool,
    pub supports_stepping_granularity: bool,
    pub supports_instruction_breakpoints: bool,
    pub supports_exception_filter_options: bool,
}

/// Breakpoint structure
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct Breakpoint {
    pub source: String,
    pub line: i64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub column: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub condition: Option<String>,
}

/// Variable structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Variable {
    pub name: String,
    pub value: String,
    #[serde(rename = "type")]
    pub type_info: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub variables_reference: Option<i64>,
}

/// Stack frame structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StackFrame {
    pub id: i64,
    pub name: String,
    pub source: Option<Source>,
    pub line: i64,
    pub column: i64,
}

/// Source structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Source {
    pub name: Option<String>,
    pub path: Option<String>,
}

/// Scope structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Scope {
    pub name: String,
    pub variables_reference: i64,
    pub expensive: bool,
}

/// Thread structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Thread {
    pub id: i64,
    pub name: String,
}

/// Initialize request arguments
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InitializeRequestArguments {
    pub client_id: Option<String>,
    pub adapter_id: String,
    pub lines_start_at1: Option<bool>,
    pub columns_start_at1: Option<bool>,
    pub path_format: Option<String>,
    pub supports_variable_type: Option<bool>,
    pub supports_variable_paging: Option<bool>,
}

/// Launch request arguments
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LaunchRequestArguments {
    pub program: String,
    pub stop_on_entry: Option<bool>,
    pub cwd: Option<String>,
    #[serde(flatten)]
    pub additional: HashMap<String, serde_json::Value>,
}

/// SetBreakpoints request arguments
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SetBreakpointsArguments {
    pub source: Source,
    pub breakpoints: Option<Vec<SourceBreakpoint>>,
}

/// Source breakpoint
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SourceBreakpoint {
    pub line: i64,
    pub column: Option<i64>,
    pub condition: Option<String>,
    pub hit_condition: Option<String>,
    pub log_message: Option<String>,
}

// Sprint 72 - TRACE-005: Execution Recording Types

/// Source location (simplified from Source for execution recording)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SourceLocation {
    pub file: String,
    pub line: usize,
    pub column: Option<usize>,
}

/// Delta between two snapshots for efficient storage
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SnapshotDelta {
    /// Variables that changed from previous snapshot
    pub changed_vars: HashMap<String, serde_json::Value>,
    /// Variables that were removed
    pub removed_vars: HashSet<String>,
    /// Stack depth change
    pub stack_delta: i32,
}

impl SnapshotDelta {
    /// Compute delta between two snapshots (TRACE-006)
    #[provable_contracts_macros::contract("pmat-core.yaml", equation = "score_range")]
    pub fn compute(previous: &ExecutionSnapshot, current: &ExecutionSnapshot) -> Self {
        let mut changed_vars = HashMap::new();
        let mut removed_vars = HashSet::new();

        // Find changed and new variables
        for (key, value) in &current.variables {
            if let Some(prev_value) = previous.variables.get(key) {
                // Variable exists in both - check if changed
                if prev_value != value {
                    changed_vars.insert(key.clone(), value.clone());
                }
            } else {
                // Variable is new in current snapshot
                changed_vars.insert(key.clone(), value.clone());
            }
        }

        // Find removed variables
        for key in previous.variables.keys() {
            if !current.variables.contains_key(key) {
                removed_vars.insert(key.clone());
            }
        }

        // Calculate stack depth change
        let stack_delta = (current.call_stack.len() as i32) - (previous.call_stack.len() as i32);

        Self {
            changed_vars,
            removed_vars,
            stack_delta,
        }
    }
}

/// Execution snapshot representing program state at a point in time
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionSnapshot {
    /// Unique timestamp (nanoseconds since epoch)
    pub timestamp: u64,
    /// Sequence number (0-indexed)
    pub sequence: usize,
    /// Variable values at this point
    pub variables: HashMap<String, serde_json::Value>,
    /// Call stack frames
    pub call_stack: Vec<StackFrame>,
    /// Source code location
    pub location: SourceLocation,
    /// Delta from previous snapshot (for compression)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub delta: Option<SnapshotDelta>,
}

impl ExecutionSnapshot {
    /// Apply delta to create new snapshot (TRACE-006)
    #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
    pub fn apply_delta(&self, delta: &SnapshotDelta) -> Self {
        let mut new_variables = self.variables.clone();

        // Apply changed variables (includes new variables)
        for (key, value) in &delta.changed_vars {
            new_variables.insert(key.clone(), value.clone());
        }

        // Remove deleted variables
        for key in &delta.removed_vars {
            new_variables.remove(key);
        }

        // Create new snapshot with updated variables
        Self {
            timestamp: self.timestamp + 1000, // Increment by 1 microsecond
            sequence: self.sequence + 1,
            variables: new_variables,
            call_stack: self.call_stack.clone(), // Stack changes tracked but not applied for now
            location: self.location.clone(),
            delta: Some(delta.clone()),
        }
    }
}

#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_dap_response_success_serialization() {
        let response = DapResponse::success(
            1,
            2,
            "initialize".to_string(),
            Some(serde_json::json!({"test": "value"})),
        );

        assert_eq!(response.type_field, "response");
        assert!(response.success);
        assert_eq!(response.command, "initialize");
        assert!(response.message.is_none());
        assert!(response.body.is_some());
    }

    #[test]
    fn test_dap_response_error_serialization() {
        let response = DapResponse::error(
            1,
            2,
            "unknown".to_string(),
            "Command not supported".to_string(),
        );

        assert_eq!(response.type_field, "response");
        assert!(!response.success);
        assert_eq!(response.command, "unknown");
        assert_eq!(response.message, Some("Command not supported".to_string()));
        assert!(response.body.is_none());
    }

    #[test]
    fn test_breakpoint_equality() {
        let bp1 = Breakpoint {
            source: "main.rs".to_string(),
            line: 10,
            column: None,
            condition: None,
        };

        let bp2 = Breakpoint {
            source: "main.rs".to_string(),
            line: 10,
            column: None,
            condition: None,
        };

        assert_eq!(bp1, bp2);
    }

    #[test]
    fn test_dap_capabilities_default() {
        let caps = DapCapabilities::default();
        assert!(!caps.supports_configuration_done_request);
        assert!(!caps.supports_conditional_breakpoints);
    }
}