maslc 1.0.1

Maduka Authorization Specification Language (MASL) toolchain and runtime
use std::fs;
use std::path::{Path, PathBuf};
use std::process::exit;

use masl_compiler::{CompileError, compile};

use crate::diagnostics::print_diagnostics;

/// Compiles a single `.mdk` file to a `.mdkc` binary.
pub fn compile_file(file: &Path, output: Option<&Path>) -> Result<(), ()> {
    let source = match fs::read_to_string(file) {
        Ok(s) => s,
        Err(e) => {
            eprintln!("Error reading file {}: {}", file.display(), e);
            return Err(());
        }
    };

    match compile(&source) {
        Ok(bytes) => {
            let out_path = resolve_output_path(file, output);
            if let Err(e) = fs::write(&out_path, bytes) {
                eprintln!("Error writing output {}: {}", out_path.display(), e);
                return Err(());
            }
            println!(
                "Successfully compiled {} to {}",
                file.display(),
                out_path.display()
            );
            Ok(())
        }
        Err(e) => {
            let diags = extract_diagnostics(e);
            print_diagnostics(&diags, &source, file.to_str().unwrap_or(""));
            Err(())
        }
    }
}

fn resolve_output_path(file: &Path, output: Option<&Path>) -> PathBuf {
    if let Some(out) = output {
        if out.is_dir() {
            let mut p = out.to_path_buf();
            p.push(file.file_name().unwrap_or_default());
            p.set_extension("mdkc");
            p
        } else {
            out.to_path_buf()
        }
    } else {
        let mut p = file.to_path_buf();
        p.set_extension("mdkc");
        p
    }
}

fn extract_diagnostics(e: CompileError) -> Vec<masl_diagnostics::diagnostic::Diagnostic> {
    match e {
        CompileError::ParseFailed(d) => d,
        CompileError::AnalysisFailed(d) => d,
        CompileError::ValidationFailed(d) => d,
        CompileError::LoweringFailed(d) => d,
        CompileError::BytecodeFailed(d) => d,
    }
}

/// Runs the `build` command over a file or directory.
pub fn run(path: PathBuf, output: Option<PathBuf>) {
    if path.is_dir() {
        if let Some(ref out) = output {
            let _ = fs::create_dir_all(out);
        }
        let mut success = true;
        let read_dir = match fs::read_dir(&path) {
            Ok(rd) => rd,
            Err(e) => {
                eprintln!("Error reading directory {}: {}", path.display(), e);
                exit(1);
            }
        };
        for entry in read_dir {
            let file_path = entry.unwrap().path();
            if file_path.is_file()
                && file_path.extension().is_some_and(|ext| ext == "mdk")
                && compile_file(&file_path, output.as_deref()).is_err()
            {
                success = false;
            }
        }
        if !success {
            exit(1);
        }
    } else if compile_file(&path, output.as_deref()).is_err() {
        exit(1);
    }
}