use crate::frame_c::utils::RunError;
pub use crate::frame_c::visitors::TargetLanguage;
use exitcode;
use std::fs;
use std::io::{self, Read};
use std::path::{Path, PathBuf};

pub struct Exe {}

impl Exe {
    pub fn new() -> Exe {
        Exe {}
    }

    pub fn run_file(
        &self,
        input_path: &Path,
        target_language: Option<TargetLanguage>,
    ) -> Result<String, RunError> {
        match fs::read_to_string(input_path) {
            Ok(content) => {
                // Compile Frame file
                // CLI -l flag takes precedence, fallback to @@target from file, then Python3
                let lang = target_language
                    .or_else(|| detect_at_target(&content))
                    .unwrap_or(TargetLanguage::Python3);
                let target_lang = crate::frame_c::compiler::TargetLanguage::from(lang);
                let compiler = crate::frame_c::compiler::FrameCompiler::new(target_lang);

                match compiler.compile(&content, input_path.to_str().unwrap_or("<unknown>")) {
                    crate::frame_c::compiler::FrameResult::Ok(output) => Ok(output.code),
                    crate::frame_c::compiler::FrameResult::Err(err) => {
                        let mut error_msg = String::from("Frame compilation errors:\n");
                        for error in err.errors() {
                            error_msg.push_str(&format!("  {}\n", error));
                        }
                        Err(RunError::new(exitcode::DATAERR, &error_msg))
                    }
                }
            }
            Err(err) => Err(RunError::new(
                exitcode::NOINPUT,
                &format!("Cannot read file: {}", err),
            )),
        }
    }

    pub fn run_file_debug(
        &self,
        input_path: &Path,
        target_language: Option<TargetLanguage>,
    ) -> Result<String, RunError> {
        match fs::read_to_string(input_path) {
            Ok(content) => {
                // Compile with debug output
                // CLI -l flag takes precedence, fallback to @@target from file, then Python3
                let lang = target_language
                    .or_else(|| detect_at_target(&content))
                    .unwrap_or(TargetLanguage::Python3);
                let target_lang = crate::frame_c::compiler::TargetLanguage::from(lang);
                let compiler = crate::frame_c::compiler::FrameCompiler::new(target_lang);

                match compiler.compile(&content, input_path.to_str().unwrap_or("<unknown>")) {
                    crate::frame_c::compiler::FrameResult::Ok(output) => Ok(output.code),
                    crate::frame_c::compiler::FrameResult::Err(err) => {
                        let mut error_msg = String::from("Frame compilation errors:\n");
                        for error in err.errors() {
                            error_msg.push_str(&format!("  {}\n", error));
                        }
                        Err(RunError::new(exitcode::DATAERR, &error_msg))
                    }
                }
            }
            Err(err) => Err(RunError::new(
                exitcode::NOINPUT,
                &format!("Cannot read file: {}", err),
            )),
        }
    }

    pub fn run_multifile(
        &self,
        _entry_path: &Path,
        _target_language: Option<TargetLanguage>,
        _output_dir: Option<PathBuf>,
    ) -> Result<String, RunError> {
        Err(RunError::new(
            exitcode::USAGE,
            "Multi-file compilation not yet supported",
        ))
    }

    pub fn run_stdin(&self, target_language: Option<TargetLanguage>) -> Result<String, RunError> {
        let mut buffer = String::new();
        let mut stdin = io::stdin();
        match stdin.read_to_string(&mut buffer) {
            Ok(_size) => {
                // Compile from stdin
                // CLI -l flag takes precedence, fallback to @@target from content, then Python3
                let lang = target_language
                    .or_else(|| detect_at_target(&buffer))
                    .unwrap_or(TargetLanguage::Python3);
                let target_lang = crate::frame_c::compiler::TargetLanguage::from(lang);
                let compiler = crate::frame_c::compiler::FrameCompiler::new(target_lang);

                match compiler.compile(&buffer, "<stdin>") {
                    crate::frame_c::compiler::FrameResult::Ok(output) => Ok(output.code),
                    crate::frame_c::compiler::FrameResult::Err(err) => {
                        let mut error_msg = String::from("Frame compilation errors:\n");
                        for error in err.errors() {
                            error_msg.push_str(&format!("  {}\n", error));
                        }
                        Err(RunError::new(exitcode::DATAERR, &error_msg))
                    }
                }
            }
            Err(err) => Err(RunError::new(
                exitcode::NOINPUT,
                &format!("Cannot read stdin: {}", err),
            )),
        }
    }
}

impl Default for Exe {
    fn default() -> Self {
        Exe::new()
    }
}

/// Detect the `@@[target("...")]` / bare `@@target <lang>` pragma in
/// Frame source files. The bracket form (RFC-0013 wave 2) is the
/// canonical form; the bare form is legacy and hard-cut by E804 at
/// validation time, but the lexer still parses it so we recognize both
/// for backend dispatch.
pub fn detect_at_target(content: &str) -> Option<TargetLanguage> {
    for line in content.lines() {
        let trimmed = line.trim();
        // Skip empty lines and comments
        if trimmed.is_empty() || trimmed.starts_with("//") || trimmed.starts_with('#') {
            continue;
        }
        // Bracket form: @@[target("lang")]  or  @@[target(lang)]
        if let Some(rest) = trimmed.strip_prefix("@@[target") {
            let inner = rest
                .trim_start()
                .strip_prefix('(')?
                .rsplit_once(')')?
                .0
                .trim()
                .trim_matches('"')
                .trim_matches('\'');
            return TargetLanguage::try_from(inner).ok();
        }
        // Bare form: @@target lang (legacy; E804 fails at validation
        // but the detection still helps before that stage)
        if let Some(rest) = trimmed.strip_prefix("@@target") {
            let lang_str = rest.trim();
            let lang_token = lang_str.split_whitespace().next()?.trim();
            return TargetLanguage::try_from(lang_token).ok();
        }
        // Stop looking after first non-comment, non-empty line that isn't @@
        if !trimmed.starts_with("@@") {
            break;
        }
    }
    None
}

pub fn detect_header_target_annotation(content: &str) -> Option<TargetLanguage> {
    for line in content.lines() {
        let trimmed = line.trim();
        if trimmed.is_empty() || trimmed.starts_with("//") {
            continue;
        }
        if let Some(language) = parse_target_attribute(trimmed) {
            return Some(language);
        }
        if trimmed.starts_with('#') {
            continue;
        }
        break;
    }
    None
}

fn parse_target_attribute(line: &str) -> Option<TargetLanguage> {
    let inner = line.strip_prefix("#[")?.trim();
    let (body, _rest) = inner.split_once(']')?;
    let body = body.trim();
    let body = body.strip_prefix("target")?;
    let body = body.trim_start_matches(|c: char| c == ':' || c == '=' || c.is_whitespace());
    if body.is_empty() {
        return None;
    }
    let language_token = body.split_whitespace().next()?.trim();
    crate::frame_c::visitors::TargetLanguage::try_from(language_token).ok()
}
