use super::{Lens, LensContext, LensId, LensOutput};
use forge::budget::estimator::TokenEstimator;
use forge::signal::compactor;

/// Clarity: strip ANSI codes, remove single-line comments, normalise whitespace, collapse blank lines.
pub struct ClarityLens;

impl Lens for ClarityLens {
    fn id(&self) -> LensId {
        LensId::Clarity
    }

    fn apply(&self, input: &str, _ctx: &LensContext) -> LensOutput {
        let tokens_before = TokenEstimator::count_nonblocking(input);
        let normalised = compactor::normalise(input);
        let stripped = strip_line_comments(&normalised);
        let compacted = compactor::collapse_blanks(&stripped);
        let tokens_after = TokenEstimator::count_nonblocking(&compacted);
        LensOutput {
            content: compacted,
            tokens_before,
            tokens_after,
            applied: vec!["clarity".into()],
        }
    }
}

/// Remove lines that are purely single-line comments (`//` and `#`).
/// Preserves lines that contain code followed by a comment.
/// Handles the most common languages: TypeScript, JavaScript, Rust, Python, Go, Bash, etc.
fn strip_line_comments(input: &str) -> String {
    let mut result = Vec::with_capacity(input.lines().count());
    for line in input.lines() {
        let trimmed = line.trim();
        // Drop lines whose entire content is a comment
        if trimmed.starts_with("//") || trimmed.starts_with('#') {
            continue;
        }
        // Drop JSDoc / block-comment lines (`* ` prefix inside /** */ blocks)
        if trimmed.starts_with("* ")
            || trimmed == "*"
            || trimmed.starts_with("*/")
            || trimmed.starts_with("/**")
            || trimmed.starts_with("/*")
        {
            continue;
        }
        result.push(line);
    }
    let joined = result.join("\n");
    // Restore trailing newline if the original had one
    if input.ends_with('\n') {
        format!("{joined}\n")
    } else {
        joined
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn strips_double_slash_comments() {
        let input = "// comment\nfn foo() {}\n// another\nlet x = 1;\n";
        let result = strip_line_comments(input);
        assert!(!result.contains("// comment"));
        assert!(!result.contains("// another"));
        assert!(result.contains("fn foo()"));
        assert!(result.contains("let x = 1;"));
    }

    #[test]
    fn strips_hash_comments() {
        let input = "# comment\ndef foo():\n    pass\n";
        let result = strip_line_comments(input);
        assert!(!result.contains("# comment"));
        assert!(result.contains("def foo():"));
    }

    #[test]
    fn strips_jsdoc_blocks() {
        let input = "/**\n * Authenticate a user.\n * @param email\n */\nasync login() {}\n";
        let result = strip_line_comments(input);
        assert!(!result.contains("/**"));
        assert!(!result.contains("* Authenticate"));
        assert!(result.contains("async login()"));
    }

    #[test]
    fn preserves_trailing_newline() {
        let input = "// comment\ncode();\n";
        let result = strip_line_comments(input);
        assert!(result.ends_with('\n'));
    }

    #[test]
    fn no_trailing_newline_preserved() {
        let input = "// comment\ncode()";
        let result = strip_line_comments(input);
        assert!(!result.ends_with('\n'));
        assert!(result.contains("code()"));
    }
}
