#![feature(try_trait_v2)]

use std::path::{Path, PathBuf};

use common::{
    build::{BuildError, Builder},
    logging::LogReceiver,
};

use crate::{model::CompilerError, specialised::build_specialised};

mod backend;
mod frontend;
mod model;
mod specialised;

pub struct Compiler {
    src_path: PathBuf,
    result: Option<Result<String, BuildError>>,
    logger: LogReceiver,
}

impl Compiler {
    fn build(&mut self, is_lib: bool) -> Result<String, Box<dyn std::error::Error>> {
        let input =
            std::fs::read_to_string(&self.src_path).expect("Failed to read input file");

        let input_ext = self
            .src_path
            .extension()
            .and_then(|s| s.to_str())
            .unwrap_or("");

        // check if we're using a specialised compiler
        if let Some(output) = build_specialised(input_ext, &input) {
            return output.map_err(|err| format!("Compilation failed: {err:?}").into());
        }

        // Parse the input using the frontend, providing the file extension and data.
        let ast =
            match frontend::compiler_frontend(input_ext, &input, &self.logger.logger()) {
                Ok(ast) => ast,
                Err(err) => return Err(format!("Compilation failed: {err:?}").into()),
            };

        // println!("Parsed AST: {:#?}", ast);

        // Generate the output using the backend with the parsed result.
        let result = match backend::compiler_backend("dsa", &ast, is_lib) {
            Ok(result) => result,
            Err(err) => return Err(format!("Compilation failed: {err:?}").into()),
        };

        Ok(result)
    }
}

impl Builder for Compiler {
    type Output = String;
    type Args = bool;

    fn new(src_path: impl Into<PathBuf>) -> Self {
        Self {
            src_path: src_path.into(),
            result: None,
            logger: LogReceiver::new(true),
        }
    }

    fn start(&mut self, args: bool) {
        match self.build(args) {
            Ok(x) => self.result = Some(Ok(x)),
            Err(err) => self.result = Some(Err(err.into())),
        }
    }

    fn poll(&mut self) -> Option<Result<Self::Output, BuildError>> {
        self.result.take()
    }

    fn output(&mut self) -> Result<Self::Output, BuildError> {
        self.result.clone().ok_or(BuildError::Generic(String::from(
            "Compiler was never started",
        )))?
    }

    fn logs(&self) -> Vec<String> {
        todo!()
    }
}

pub fn error(msg: impl Into<String>) -> CompilerError {
    CompilerError::Generic(msg.into())
}
