labscript 0.1.0

Prescription PDF generator with e-signature and QR verification
use sha2::{Digest, Sha256};

use crate::types::Prescription;

/// Compute the verification hash for a prescription.
/// SHA256 over: patient_name|prescriber_name|drug_names_joined|date
pub fn compute_hash(rx: &Prescription) -> String {
    let drug_names: Vec<&str> = rx.medications.iter().map(|m| m.drug.as_str()).collect();
    let payload = format!(
        "{}|{}|{}|{}",
        rx.patient.name,
        rx.prescriber.name,
        drug_names.join(","),
        rx.date,
    );
    let mut hasher = Sha256::new();
    hasher.update(payload.as_bytes());
    format!("{:x}", hasher.finalize())
}

/// Build the QR payload string.
/// Format: labscript:v1:{uuid}:{sha256}:{iso_timestamp}
pub fn build_qr_payload(id: &str, hash: &str, timestamp: &str) -> String {
    format!("labscript:v1:{id}:{hash}:{timestamp}")
}

/// Render a QR code as lines of Unicode block characters.
/// Uses two-row encoding: each character represents two vertical modules.
/// Top half = upper block, bottom half = lower block, both = full block.
pub fn render_qr_text(payload: &str) -> Result<Vec<String>, crate::errors::LabscriptError> {
    use qrcode::QrCode;

    let code = QrCode::new(payload.as_bytes())
        .map_err(|e| crate::errors::LabscriptError::Pdf(format!("QR generation failed: {e}")))?;

    let width = code.width();
    let modules: Vec<Vec<bool>> = (0..width)
        .map(|y| {
            (0..width)
                .map(|x| code[(x, y)] == qrcode::Color::Dark)
                .collect()
        })
        .collect();

    let mut lines = Vec::new();

    // Process two rows at a time using half-block characters
    let mut y = 0;
    while y < width {
        let mut line = String::new();
        for x in 0..width {
            let top = modules[y][x];
            let bottom = if y + 1 < width {
                modules[y + 1][x]
            } else {
                false
            };
            let ch = match (top, bottom) {
                (true, true) => '\u{2588}',   // full block
                (true, false) => '\u{2580}',  // upper half block
                (false, true) => '\u{2584}',  // lower half block
                (false, false) => ' ',
            };
            line.push(ch);
        }
        lines.push(line);
        y += 2;
    }

    Ok(lines)
}