pmat 3.15.0

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP, HTTP)
// DAP Server - Recording and Network Methods
// Split from server.rs for file health compliance
//
// Contains: Sprint 76 CAPTURE-002 recording capture + Sprint 74 DEBUG-002 TCP server
// NOTE: This file is included via include!() - no `use` imports allowed

impl DapServer {
    // Sprint 76 - CAPTURE-002: Recording Capture Methods

    /// Generate a unique recording file path with timestamp
    ///
    /// Format: session-{timestamp}.pmat
    fn generate_recording_path(&self) -> Option<PathBuf> {
        use std::time::{SystemTime, UNIX_EPOCH};

        let recording_dir = self.recording_dir.as_ref()?;

        let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).ok()?.as_secs();

        Some(recording_dir.join(format!("session-{}.pmat", timestamp)))
    }

    /// Initialize recording on session start
    ///
    /// Creates recording directory if needed and sets up ExecutionRecorder
    fn start_recording(&self, program: &str, args: Vec<String>) -> anyhow::Result<()> {
        // Only start recording if recording_dir is configured
        let recording_dir = match &self.recording_dir {
            Some(dir) => dir,
            None => return Ok(()), // No recording configured
        };

        // Create recording directory if it doesn't exist
        if !recording_dir.exists() {
            std::fs::create_dir_all(recording_dir)
                .map_err(|e| anyhow::anyhow!("Failed to create recording directory: {}", e))?;
        }

        // Generate recording file path
        let recording_path = self
            .generate_recording_path()
            .ok_or_else(|| anyhow::anyhow!("Failed to generate recording path"))?;

        // Create recording file
        let file = File::create(&recording_path)
            .map_err(|e| anyhow::anyhow!("Failed to create recording file: {}", e))?;

        // Create ExecutionRecorder with writer
        let dap_server_arc = Arc::new(Mutex::new(DapServer::new()));
        let mut recorder =
            ExecutionRecorder::with_writer(file, program.to_string(), args, dap_server_arc)?;

        // Start recording (enables snapshot capture)
        recorder.start_recording();

        // Store recorder and path
        *self
            .execution_recorder
            .lock()
            .expect("Mutex should not be poisoned") = Some(recorder);
        *self
            .recording_path
            .lock()
            .expect("Mutex should not be poisoned") = Some(recording_path);

        Ok(())
    }

    /// Finalize and save recording
    ///
    /// Called on disconnect or terminate to complete the .pmat file
    fn finalize_recording(&self) -> anyhow::Result<Option<PathBuf>> {
        let mut recorder_guard = self
            .execution_recorder
            .lock()
            .expect("Mutex should not be poisoned");
        let recorder = recorder_guard.take();

        if let Some(recorder) = recorder {
            recorder.finalize()?;
            let path = self
                .recording_path
                .lock()
                .expect("Mutex should not be poisoned")
                .clone();
            return Ok(path);
        }

        Ok(None)
    }

    /// CAPTURE-002: Attempt to capture a snapshot if recording is active
    ///
    /// This is called on debug events (breakpoint hits, step commands) to capture
    /// execution state. Silently fails if recording is not active or capture fails.
    fn capture_snapshot_if_recording(&self) {
        let mut recorder_guard = match self.execution_recorder.lock() {
            Ok(guard) => guard,
            Err(_) => return, // Lock poisoned, skip capture
        };

        if let Some(ref mut recorder) = *recorder_guard {
            // Only attempt capture if recorder is in recording state
            if recorder.is_recording() {
                if let Err(e) = recorder.capture_snapshot() {
                    eprintln!("Warning: Failed to capture snapshot: {}", e);
                    // Continue execution even if snapshot capture fails
                }
            }
        }
    }

    // Sprint 74 - DEBUG-002: DAP Server CLI Handler

    /// Run the DAP server on the specified port
    ///
    /// This starts an async TCP server that listens for DAP protocol connections.
    /// The server runs until the async task is aborted (e.g., via Ctrl+C).
    ///
    /// # Arguments
    /// * `port` - Port number to bind to (e.g., 5678)
    /// * `host` - Host address to bind to (e.g., "127.0.0.1")
    ///
    /// # Returns
    /// * `Ok(())` if server starts and shuts down cleanly
    /// * `Err` if port binding fails (e.g., "address already in use")
    #[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
    pub async fn run(&self, port: u16, host: String) -> anyhow::Result<()> {
        use tokio::net::TcpListener;

        // Bind to TCP port
        let addr = format!("{}:{}", host, port);
        let listener = TcpListener::bind(&addr)
            .await
            .map_err(|e| anyhow::anyhow!("Failed to bind to {}: {}", addr, e))?;

        // Server is now listening - accept connections in a loop
        loop {
            // Accept incoming connection
            let (_stream, _addr) = listener.accept().await?;

            // Minimal implementation: just accept and drop connections
            // Future enhancement: read DAP messages from stream and call handle_request()
        }
    }
}