//! Zero-JavaScript Validation for WASM-First Applications (PROBAR-SPEC-012)
//!
//! Validates that WASM applications contain NO user-generated JavaScript, CSS, or HTML.
//! All web assets must be programmatically generated via probar-js-gen or typed builders.
//!
//! ## Toyota Way Application:
//! - **Poka-Yoke**: Type system prevents user-injected code at compile time
//! - **Jidoka**: Validation stops the line if unauthorized code detected
//! - **Andon**: Clear error messages identify exactly what violated zero-JS policy
//!
//! ## Critical Defect Vectors Addressed:
//! 1. User-created JavaScript files
//! 2. Inline `<script>` tags not from WASM
//! 3. External CSS files not generated by builders
//! 4. HTML files without WASM generation markers
//!
//! ## References:
//! - whisper.apr CLAUDE.md: "ABSOLUTE ZERO JAVASCRIPT"
//! - DO-178C Section 6.3: Configuration management
//! - OWASP: Third-party JavaScript injection prevention

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

/// Forbidden file patterns for zero-JS WASM applications
pub const FORBIDDEN_EXTENSIONS: &[&str] = &[
    ".js",     // JavaScript files (unless manifest verified)
    ".mjs",    // ES modules
    ".cjs",    // CommonJS modules
    ".ts",     // TypeScript source (should be compiled to WASM)
    ".tsx",    // TypeScript JSX
    ".jsx",    // React JSX
    ".coffee", // CoffeeScript
];

/// Forbidden directory names that indicate non-WASM tooling
pub const FORBIDDEN_DIRECTORIES: &[&str] = &[
    "node_modules",
    "npm_packages",
    ".npm",
    ".yarn",
    "bower_components",
    "jspm_packages",
];

/// Forbidden files that indicate JavaScript tooling
pub const FORBIDDEN_FILES: &[&str] = &[
    "package.json",
    "package-lock.json",
    "yarn.lock",
    "pnpm-lock.yaml",
    "bun.lockb",
    "tsconfig.json",
    "jsconfig.json",
    ".babelrc",
    "babel.config.js",
    "webpack.config.js",
    "rollup.config.js",
    "vite.config.js",
    "esbuild.config.js",
    ".eslintrc.js",
    ".prettierrc.js",
];

/// Dangerous patterns in JavaScript that should NEVER appear
pub const DANGEROUS_JS_PATTERNS: &[&str] = &[
    "eval(",
    "new Function(",
    "Function(",
    "document.write(",
    "innerHTML =",
    "outerHTML =",
    "__proto__",
    "prototype.constructor",
    "with (",
    "setTimeout(\"",
    "setInterval(\"",
];

/// Zero-JS validation result
#[derive(Debug, Clone, Default)]
pub struct ZeroJsValidationResult {
    /// Whether validation passed
    pub valid: bool,
    /// Unauthorized JavaScript files found
    pub unauthorized_js_files: Vec<PathBuf>,
    /// Unauthorized CSS files found
    pub unauthorized_css_files: Vec<PathBuf>,
    /// Unauthorized HTML files found
    pub unauthorized_html_files: Vec<PathBuf>,
    /// Forbidden directories found
    pub forbidden_directories: Vec<PathBuf>,
    /// Forbidden tooling files found
    pub forbidden_tooling_files: Vec<PathBuf>,
    /// Inline scripts detected in HTML
    pub inline_scripts_detected: Vec<InlineScriptViolation>,
    /// External script tags without manifest
    pub external_scripts_without_manifest: Vec<String>,
    /// Dangerous patterns found
    pub dangerous_patterns: Vec<DangerousPatternViolation>,
    /// Files that passed manifest verification
    pub verified_js_files: Vec<PathBuf>,
}

impl ZeroJsValidationResult {
    /// Check if validation passed with no violations
    #[must_use]
    pub fn is_valid(&self) -> bool {
        self.valid
            && self.unauthorized_js_files.is_empty()
            && self.unauthorized_css_files.is_empty()
            && self.unauthorized_html_files.is_empty()
            && self.forbidden_directories.is_empty()
            && self.forbidden_tooling_files.is_empty()
            && self.inline_scripts_detected.is_empty()
            && self.external_scripts_without_manifest.is_empty()
            && self.dangerous_patterns.is_empty()
    }

    /// Get total violation count
    #[must_use]
    pub fn violation_count(&self) -> usize {
        self.unauthorized_js_files.len()
            + self.unauthorized_css_files.len()
            + self.unauthorized_html_files.len()
            + self.forbidden_directories.len()
            + self.forbidden_tooling_files.len()
            + self.inline_scripts_detected.len()
            + self.external_scripts_without_manifest.len()
            + self.dangerous_patterns.len()
    }
}

impl fmt::Display for ZeroJsValidationResult {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.is_valid() {
            writeln!(f, "Zero-JS Validation: PASSED")?;
            writeln!(f, "  Verified JS files: {}", self.verified_js_files.len())?;
        } else {
            writeln!(f, "Zero-JS Validation: FAILED")?;
            writeln!(f, "  Total violations: {}", self.violation_count())?;

            if !self.unauthorized_js_files.is_empty() {
                writeln!(f, "\n  Unauthorized JavaScript files:")?;
                for path in &self.unauthorized_js_files {
                    writeln!(f, "    - {}", path.display())?;
                }
            }

            if !self.forbidden_directories.is_empty() {
                writeln!(f, "\n  Forbidden directories:")?;
                for path in &self.forbidden_directories {
                    writeln!(f, "    - {}", path.display())?;
                }
            }

            if !self.inline_scripts_detected.is_empty() {
                writeln!(f, "\n  Inline scripts detected:")?;
                for violation in &self.inline_scripts_detected {
                    writeln!(f, "    - {}", violation)?;
                }
            }

            if !self.dangerous_patterns.is_empty() {
                writeln!(f, "\n  Dangerous patterns:")?;
                for violation in &self.dangerous_patterns {
                    writeln!(f, "    - {}", violation)?;
                }
            }
        }
        Ok(())
    }
}

/// Inline script violation details
#[derive(Debug, Clone)]
pub struct InlineScriptViolation {
    /// File containing the inline script
    pub file: PathBuf,
    /// Line number where script tag was found
    pub line: usize,
    /// Preview of the script content
    pub preview: String,
    /// Whether it's a WASM-generated script (allowed)
    pub is_wasm_generated: bool,
}

impl fmt::Display for InlineScriptViolation {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}:{} - {}",
            self.file.display(),
            self.line,
            self.preview
        )
    }
}

/// Dangerous pattern violation
#[derive(Debug, Clone)]
pub struct DangerousPatternViolation {
    /// File containing the pattern
    pub file: PathBuf,
    /// Line number
    pub line: usize,
    /// The dangerous pattern found
    pub pattern: String,
    /// Context around the pattern
    pub context: String,
}

impl fmt::Display for DangerousPatternViolation {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}:{} - '{}' in: {}",
            self.file.display(),
            self.line,
            self.pattern,
            self.context
        )
    }
}

/// Zero-JS validator configuration
#[derive(Debug, Clone)]
pub struct ZeroJsConfig {
    /// Allowed JS files (must have manifest verification)
    pub allowed_js_patterns: Vec<String>,
    /// Allowed CSS files (generated by builders)
    pub allowed_css_patterns: Vec<String>,
    /// Paths to skip during validation
    pub skip_paths: Vec<PathBuf>,
    /// Whether to require manifest for all JS files
    pub require_manifest: bool,
    /// Whether to check for dangerous patterns
    pub check_dangerous_patterns: bool,
    /// WASM generation marker to look for in HTML
    pub wasm_marker: String,
    /// Whether inline scripts are allowed if WASM-generated
    pub allow_wasm_inline_scripts: bool,
}

impl Default for ZeroJsConfig {
    fn default() -> Self {
        Self {
            allowed_js_patterns: vec![],
            allowed_css_patterns: vec![],
            skip_paths: vec![],
            require_manifest: true,
            check_dangerous_patterns: true,
            wasm_marker: "__PROBAR_WASM_GENERATED__".to_string(),
            allow_wasm_inline_scripts: true,
        }
    }
}

impl ZeroJsConfig {
    /// Create strict configuration (no JS allowed at all)
    #[must_use]
    pub fn strict() -> Self {
        Self {
            allowed_js_patterns: vec![],
            allowed_css_patterns: vec![],
            skip_paths: vec![],
            require_manifest: true,
            check_dangerous_patterns: true,
            wasm_marker: "__PROBAR_WASM_GENERATED__".to_string(),
            allow_wasm_inline_scripts: false,
        }
    }

    /// Create permissive configuration (for development)
    #[must_use]
    pub fn development() -> Self {
        Self {
            allowed_js_patterns: vec!["*.worker.js".to_string(), "*.worklet.js".to_string()],
            allowed_css_patterns: vec!["*.css".to_string()],
            skip_paths: vec![],
            require_manifest: false,
            check_dangerous_patterns: false,
            wasm_marker: String::new(),
            allow_wasm_inline_scripts: true,
        }
    }

    /// Add an allowed JS pattern
    #[must_use]
    pub fn with_allowed_js(mut self, pattern: impl Into<String>) -> Self {
        self.allowed_js_patterns.push(pattern.into());
        self
    }

    /// Add a path to skip
    #[must_use]
    pub fn with_skip_path(mut self, path: impl Into<PathBuf>) -> Self {
        self.skip_paths.push(path.into());
        self
    }
}

/// Zero-JavaScript validator for WASM-first applications
#[derive(Debug, Clone)]
pub struct ZeroJsValidator {
    config: ZeroJsConfig,
}

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

impl ZeroJsValidator {
    /// Create a new validator with default configuration
    #[must_use]
    pub fn new() -> Self {
        Self {
            config: ZeroJsConfig::default(),
        }
    }

    /// Create with custom configuration
    #[must_use]
    pub fn with_config(config: ZeroJsConfig) -> Self {
        Self { config }
    }

    /// Validate a directory for zero-JS compliance
    ///
    /// # Errors
    /// Returns IO error if directory cannot be read
    pub fn validate_directory(&self, path: &Path) -> std::io::Result<ZeroJsValidationResult> {
        let mut result = ZeroJsValidationResult {
            valid: true,
            ..Default::default()
        };

        self.scan_directory(path, &mut result)?;

        // Update valid flag based on findings
        result.valid = result.is_valid();

        Ok(result)
    }

    /// Validate HTML content for inline scripts
    #[must_use]
    pub fn validate_html_content(
        &self,
        content: &str,
        file_path: &Path,
    ) -> Vec<InlineScriptViolation> {
        let mut violations = Vec::new();
        let mut in_script = false;
        let mut script_start_line = 0;
        let mut script_content = String::new();

        for (line_num, line) in content.lines().enumerate() {
            let line_num = line_num + 1; // 1-indexed

            // Check for script tag start
            if line.contains("<script") && !line.contains("src=") {
                in_script = true;
                script_start_line = line_num;
                script_content.clear();
            }

            // Accumulate script content
            if in_script {
                script_content.push_str(line);
                script_content.push('\n');
            }

            // Check for script tag end
            if line.contains("</script>") && in_script {
                in_script = false;

                // Check if it's WASM-generated
                let is_wasm_generated = script_content.contains(&self.config.wasm_marker)
                    || script_content.contains("wasm")
                    || script_content.contains("WebAssembly");

                if !is_wasm_generated || !self.config.allow_wasm_inline_scripts {
                    let preview = script_content
                        .chars()
                        .take(100)
                        .collect::<String>()
                        .replace('\n', " ");

                    violations.push(InlineScriptViolation {
                        file: file_path.to_path_buf(),
                        line: script_start_line,
                        preview,
                        is_wasm_generated,
                    });
                }
            }
        }

        violations
    }

    /// Validate JavaScript content for dangerous patterns
    #[must_use]
    pub fn validate_js_content(
        &self,
        content: &str,
        file_path: &Path,
    ) -> Vec<DangerousPatternViolation> {
        if !self.config.check_dangerous_patterns {
            return Vec::new();
        }

        let mut violations = Vec::new();

        for (line_num, line) in content.lines().enumerate() {
            let line_num = line_num + 1; // 1-indexed

            for pattern in DANGEROUS_JS_PATTERNS {
                if line.contains(pattern) {
                    let context = line.trim().chars().take(80).collect::<String>();
                    violations.push(DangerousPatternViolation {
                        file: file_path.to_path_buf(),
                        line: line_num,
                        pattern: (*pattern).to_string(),
                        context,
                    });
                }
            }
        }

        violations
    }

    /// Check if a JS file has a valid manifest
    #[must_use]
    pub fn has_valid_manifest(&self, js_path: &Path) -> bool {
        let manifest_path = js_path.with_extension("js.manifest.json");
        if !manifest_path.exists() {
            return false;
        }

        // Try to read and parse manifest
        if let Ok(content) = std::fs::read_to_string(&manifest_path) {
            // Check for required manifest fields
            content.contains("manifest_version")
                && content.contains("output_hash")
                && content.contains("generation")
        } else {
            false
        }
    }

    /// Check if a file matches allowed patterns
    fn matches_allowed_pattern(&self, path: &Path, patterns: &[String]) -> bool {
        let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");

        for pattern in patterns {
            if let Some(suffix) = pattern.strip_prefix('*') {
                if file_name.ends_with(suffix) {
                    return true;
                }
            } else if let Some(prefix) = pattern.strip_suffix('*') {
                if file_name.starts_with(prefix) {
                    return true;
                }
            } else if file_name == pattern {
                return true;
            }
        }

        false
    }

    /// Check if path should be skipped
    fn should_skip(&self, path: &Path) -> bool {
        for skip in &self.config.skip_paths {
            if path.starts_with(skip) {
                return true;
            }
        }
        false
    }

    /// Recursively scan directory
    fn scan_directory(
        &self,
        dir: &Path,
        result: &mut ZeroJsValidationResult,
    ) -> std::io::Result<()> {
        if !dir.is_dir() {
            return Ok(());
        }

        // Check if this directory is forbidden
        if let Some(dir_name) = dir.file_name().and_then(|n| n.to_str()) {
            if FORBIDDEN_DIRECTORIES.contains(&dir_name) {
                result.forbidden_directories.push(dir.to_path_buf());
                return Ok(());
            }
        }

        for entry in std::fs::read_dir(dir)? {
            let entry = entry?;
            let path = entry.path();

            if self.should_skip(&path) {
                continue;
            }

            if path.is_dir() {
                self.scan_directory(&path, result)?;
            } else if path.is_file() {
                self.check_file(&path, result)?;
            }
        }

        Ok(())
    }

    /// Check a single file for violations
    fn check_file(&self, path: &Path, result: &mut ZeroJsValidationResult) -> std::io::Result<()> {
        let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
        let extension = path.extension().and_then(|e| e.to_str()).unwrap_or("");

        // Check for forbidden tooling files
        if FORBIDDEN_FILES.contains(&file_name) {
            result.forbidden_tooling_files.push(path.to_path_buf());
            return Ok(());
        }

        // Check extension
        let ext_with_dot = format!(".{extension}");
        let is_forbidden_ext = FORBIDDEN_EXTENSIONS.contains(&ext_with_dot.as_str());

        if is_forbidden_ext {
            // Check if it's allowed by pattern
            if self.matches_allowed_pattern(path, &self.config.allowed_js_patterns) {
                // Allowed pattern - check manifest if required
                if self.config.require_manifest && !self.has_valid_manifest(path) {
                    result.unauthorized_js_files.push(path.to_path_buf());
                } else {
                    result.verified_js_files.push(path.to_path_buf());

                    // Still check for dangerous patterns
                    if let Ok(content) = std::fs::read_to_string(path) {
                        let violations = self.validate_js_content(&content, path);
                        result.dangerous_patterns.extend(violations);
                    }
                }
            } else {
                // Not an allowed pattern - check manifest
                if self.has_valid_manifest(path) {
                    result.verified_js_files.push(path.to_path_buf());
                } else {
                    result.unauthorized_js_files.push(path.to_path_buf());
                }
            }
        } else if extension == "html" || extension == "htm" {
            // Check HTML for inline scripts
            if let Ok(content) = std::fs::read_to_string(path) {
                let violations = self.validate_html_content(&content, path);
                result.inline_scripts_detected.extend(violations);
            }
        } else if extension == "css" {
            // Check CSS files
            if !self.matches_allowed_pattern(path, &self.config.allowed_css_patterns) {
                result.unauthorized_css_files.push(path.to_path_buf());
            }
        }

        Ok(())
    }

    /// Validate a page via CDP for zero-JS compliance
    ///
    /// Checks the loaded page for:
    /// - Inline scripts
    /// - External scripts without manifests
    /// - Dangerous patterns
    ///
    /// # Example
    /// ```ignore
    /// use jugar_probar::ZeroJsValidator;
    ///
    /// let validator = ZeroJsValidator::new();
    /// let result = validator.validate_page_cdp(&page).await?;
    /// assert!(result.is_valid(), "Page violates zero-JS policy: {}", result);
    /// ```
    #[cfg(feature = "browser")]
    pub async fn validate_page_cdp(
        &self,
        page: &chromiumoxide::Page,
    ) -> Result<ZeroJsValidationResult, ZeroJsError> {
        let mut result = ZeroJsValidationResult {
            valid: true,
            ..Default::default()
        };

        // Get all script elements
        let scripts_json: String = page
            .evaluate(
                r#"
                JSON.stringify(Array.from(document.querySelectorAll('script')).map(s => ({
                    src: s.src || '',
                    content: s.textContent || '',
                    type: s.type || ''
                })))
            "#,
            )
            .await
            .map_err(|e| ZeroJsError::CdpError(e.to_string()))?
            .into_value()
            .unwrap_or_else(|_| "[]".to_string());

        let scripts: Vec<serde_json::Value> = serde_json::from_str(&scripts_json)
            .map_err(|e| ZeroJsError::ParseError(e.to_string()))?;

        for script in scripts {
            let src = script["src"].as_str().unwrap_or("");
            let content = script["content"].as_str().unwrap_or("");

            if src.is_empty() {
                // Inline script
                let is_wasm_generated = content.contains(&self.config.wasm_marker)
                    || content.contains("wasm")
                    || content.contains("WebAssembly");

                if !is_wasm_generated || !self.config.allow_wasm_inline_scripts {
                    result.inline_scripts_detected.push(InlineScriptViolation {
                        file: PathBuf::from("(page)"),
                        line: 0,
                        preview: content.chars().take(100).collect(),
                        is_wasm_generated,
                    });
                }

                // Check for dangerous patterns
                let violations =
                    self.validate_js_content(content, &PathBuf::from("(inline script)"));
                result.dangerous_patterns.extend(violations);
            } else {
                // External script - check if allowed
                result
                    .external_scripts_without_manifest
                    .push(src.to_string());
            }
        }

        result.valid = result.is_valid();
        Ok(result)
    }
}

/// Error type for zero-JS validation
#[derive(Debug, Clone)]
pub enum ZeroJsError {
    /// CDP operation failed
    CdpError(String),
    /// JSON parsing failed
    ParseError(String),
    /// IO error
    IoError(String),
}

impl fmt::Display for ZeroJsError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::CdpError(msg) => write!(f, "CDP error: {msg}"),
            Self::ParseError(msg) => write!(f, "Parse error: {msg}"),
            Self::IoError(msg) => write!(f, "IO error: {msg}"),
        }
    }
}

impl std::error::Error for ZeroJsError {}

impl From<std::io::Error> for ZeroJsError {
    fn from(err: std::io::Error) -> Self {
        Self::IoError(err.to_string())
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
    use super::*;
    use tempfile::TempDir;

    // =========================================================================
    // H0-ZJ-01: Forbidden JS files detected
    // =========================================================================

    #[test]
    fn h0_zj_01_detect_js_file() {
        let temp = TempDir::new().unwrap();
        let js_path = temp.path().join("user_code.js");
        std::fs::write(&js_path, "console.log('hello')").unwrap();

        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(temp.path()).unwrap();

        assert!(!result.is_valid());
        assert!(!result.unauthorized_js_files.is_empty());
    }

    #[test]
    fn h0_zj_02_detect_ts_file() {
        let temp = TempDir::new().unwrap();
        let ts_path = temp.path().join("code.ts");
        std::fs::write(&ts_path, "const x: number = 1;").unwrap();

        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(temp.path()).unwrap();

        assert!(!result.is_valid());
        assert!(!result.unauthorized_js_files.is_empty());
    }

    #[test]
    fn h0_zj_03_detect_mjs_file() {
        let temp = TempDir::new().unwrap();
        let mjs_path = temp.path().join("module.mjs");
        std::fs::write(&mjs_path, "export const x = 1;").unwrap();

        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(temp.path()).unwrap();

        assert!(!result.is_valid());
    }

    // =========================================================================
    // H0-ZJ-04: Forbidden directories detected
    // =========================================================================

    #[test]
    fn h0_zj_04_detect_node_modules() {
        let temp = TempDir::new().unwrap();
        let nm_path = temp.path().join("node_modules");
        std::fs::create_dir(&nm_path).unwrap();
        std::fs::write(nm_path.join("package.json"), "{}").unwrap();

        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(temp.path()).unwrap();

        assert!(!result.is_valid());
        assert!(!result.forbidden_directories.is_empty());
    }

    #[test]
    fn h0_zj_05_detect_npm_directory() {
        let temp = TempDir::new().unwrap();
        let npm_path = temp.path().join(".npm");
        std::fs::create_dir(&npm_path).unwrap();

        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(temp.path()).unwrap();

        assert!(!result.is_valid());
    }

    // =========================================================================
    // H0-ZJ-06: Forbidden tooling files detected
    // =========================================================================

    #[test]
    fn h0_zj_06_detect_package_json() {
        let temp = TempDir::new().unwrap();
        let pkg_path = temp.path().join("package.json");
        std::fs::write(&pkg_path, r#"{"name": "test"}"#).unwrap();

        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(temp.path()).unwrap();

        assert!(!result.is_valid());
        assert!(!result.forbidden_tooling_files.is_empty());
    }

    #[test]
    fn h0_zj_07_detect_tsconfig() {
        let temp = TempDir::new().unwrap();
        let cfg_path = temp.path().join("tsconfig.json");
        std::fs::write(&cfg_path, "{}").unwrap();

        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(temp.path()).unwrap();

        assert!(!result.is_valid());
    }

    #[test]
    fn h0_zj_08_detect_webpack_config() {
        let temp = TempDir::new().unwrap();
        let cfg_path = temp.path().join("webpack.config.js");
        std::fs::write(&cfg_path, "module.exports = {}").unwrap();

        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(temp.path()).unwrap();

        assert!(!result.is_valid());
        // Both tooling file and JS file
        assert!(
            !result.forbidden_tooling_files.is_empty() || !result.unauthorized_js_files.is_empty()
        );
    }

    // =========================================================================
    // H0-ZJ-09: Inline scripts detected
    // =========================================================================

    #[test]
    fn h0_zj_09_detect_inline_script() {
        let html = r#"
<!DOCTYPE html>
<html>
<head>
    <script>alert('hello')</script>
</head>
<body></body>
</html>
"#;
        let validator = ZeroJsValidator::with_config(ZeroJsConfig {
            allow_wasm_inline_scripts: false,
            ..Default::default()
        });
        let violations = validator.validate_html_content(html, Path::new("test.html"));

        assert!(!violations.is_empty());
        assert!(violations[0].preview.contains("alert"));
    }

    #[test]
    fn h0_zj_10_allow_wasm_inline_script() {
        let html = r#"
<!DOCTYPE html>
<html>
<head>
    <script>
        // __PROBAR_WASM_GENERATED__
        WebAssembly.instantiate(module);
    </script>
</head>
<body></body>
</html>
"#;
        let validator = ZeroJsValidator::new();
        let violations = validator.validate_html_content(html, Path::new("test.html"));

        // WASM-generated scripts are allowed by default
        assert!(violations.is_empty());
    }

    // =========================================================================
    // H0-ZJ-11: Dangerous patterns detected
    // =========================================================================

    #[test]
    fn h0_zj_11_detect_eval() {
        let js = "const result = eval('1 + 1');";
        let validator = ZeroJsValidator::new();
        let violations = validator.validate_js_content(js, Path::new("code.js"));

        assert!(!violations.is_empty());
        assert!(violations[0].pattern.contains("eval("));
    }

    #[test]
    fn h0_zj_12_detect_function_constructor() {
        let js = "const fn = new Function('return 1');";
        let validator = ZeroJsValidator::new();
        let violations = validator.validate_js_content(js, Path::new("code.js"));

        assert!(!violations.is_empty());
        assert!(violations[0].pattern.contains("Function("));
    }

    #[test]
    fn h0_zj_13_detect_innerhtml() {
        let js = "element.innerHTML = userInput;";
        let validator = ZeroJsValidator::new();
        let violations = validator.validate_js_content(js, Path::new("code.js"));

        assert!(!violations.is_empty());
        assert!(violations[0].pattern.contains("innerHTML"));
    }

    #[test]
    fn h0_zj_14_detect_document_write() {
        let js = "document.write('<script>alert(1)</script>');";
        let validator = ZeroJsValidator::new();
        let violations = validator.validate_js_content(js, Path::new("code.js"));

        assert!(!violations.is_empty());
    }

    #[test]
    fn h0_zj_15_detect_proto_access() {
        let js = "obj.__proto__.polluted = true;";
        let validator = ZeroJsValidator::new();
        let violations = validator.validate_js_content(js, Path::new("code.js"));

        assert!(!violations.is_empty());
    }

    // =========================================================================
    // H0-ZJ-16: Manifest verification
    // =========================================================================

    #[test]
    fn h0_zj_16_js_with_manifest_allowed() {
        let temp = TempDir::new().unwrap();
        let js_path = temp.path().join("worker.js");
        let manifest_path = temp.path().join("worker.js.manifest.json");

        std::fs::write(&js_path, "self.onmessage = function() {}").unwrap();
        std::fs::write(
            &manifest_path,
            r#"{"manifest_version": 1, "output_hash": "abc123", "generation": {}}"#,
        )
        .unwrap();

        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(temp.path()).unwrap();

        assert!(result.is_valid());
        assert!(!result.verified_js_files.is_empty());
    }

    #[test]
    fn h0_zj_17_js_without_manifest_rejected() {
        let temp = TempDir::new().unwrap();
        let js_path = temp.path().join("worker.js");
        std::fs::write(&js_path, "self.onmessage = function() {}").unwrap();

        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(temp.path()).unwrap();

        assert!(!result.is_valid());
        assert!(!result.unauthorized_js_files.is_empty());
    }

    // =========================================================================
    // H0-ZJ-18: Configuration options
    // =========================================================================

    #[test]
    fn h0_zj_18_strict_mode() {
        let config = ZeroJsConfig::strict();
        assert!(config.require_manifest);
        assert!(config.check_dangerous_patterns);
        assert!(!config.allow_wasm_inline_scripts);
    }

    #[test]
    fn h0_zj_19_development_mode() {
        let config = ZeroJsConfig::development();
        assert!(!config.require_manifest);
        assert!(!config.check_dangerous_patterns);
    }

    #[test]
    fn h0_zj_20_allowed_pattern() {
        let temp = TempDir::new().unwrap();
        let js_path = temp.path().join("audio.worker.js");
        std::fs::write(&js_path, "self.onmessage = function() {}").unwrap();

        let config = ZeroJsConfig::default()
            .with_allowed_js("*.worker.js")
            .with_allowed_js("*.worklet.js");
        let validator = ZeroJsValidator::with_config(ZeroJsConfig {
            require_manifest: false,
            ..config
        });
        let result = validator.validate_directory(temp.path()).unwrap();

        // Worker.js is allowed by pattern
        assert!(result.is_valid());
    }

    #[test]
    fn h0_zj_21_skip_path() {
        let temp = TempDir::new().unwrap();
        let vendor_path = temp.path().join("vendor");
        std::fs::create_dir(&vendor_path).unwrap();
        std::fs::write(vendor_path.join("lib.js"), "var x = 1;").unwrap();

        let config = ZeroJsConfig::default().with_skip_path(vendor_path);
        let validator = ZeroJsValidator::with_config(config);
        let result = validator.validate_directory(temp.path()).unwrap();

        // vendor directory is skipped
        assert!(result.is_valid());
    }

    // =========================================================================
    // H0-ZJ-22: Clean directory passes
    // =========================================================================

    #[test]
    fn h0_zj_22_clean_wasm_directory() {
        let temp = TempDir::new().unwrap();

        // Create valid WASM-only structure
        std::fs::write(temp.path().join("app.wasm"), b"fake wasm").unwrap();
        std::fs::write(
            temp.path().join("index.html"),
            "<!DOCTYPE html><html><head></head><body></body></html>",
        )
        .unwrap();

        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(temp.path()).unwrap();

        assert!(result.is_valid());
        assert_eq!(result.violation_count(), 0);
    }

    // =========================================================================
    // H0-ZJ-23: Result display
    // =========================================================================

    #[test]
    fn h0_zj_23_result_display_passed() {
        let result = ZeroJsValidationResult {
            valid: true,
            verified_js_files: vec![PathBuf::from("worker.js")],
            ..Default::default()
        };

        let display = format!("{result}");
        assert!(display.contains("PASSED"));
        assert!(display.contains("Verified JS files: 1"));
    }

    #[test]
    fn h0_zj_24_result_display_failed() {
        let result = ZeroJsValidationResult {
            valid: false,
            unauthorized_js_files: vec![PathBuf::from("bad.js")],
            ..Default::default()
        };

        let display = format!("{result}");
        assert!(display.contains("FAILED"));
        assert!(display.contains("bad.js"));
    }

    // =========================================================================
    // H0-ZJ-25: Violation displays
    // =========================================================================

    #[test]
    fn h0_zj_25_inline_script_violation_display() {
        let violation = InlineScriptViolation {
            file: PathBuf::from("index.html"),
            line: 10,
            preview: "alert('hello')".to_string(),
            is_wasm_generated: false,
        };

        let display = format!("{violation}");
        assert!(display.contains("index.html"));
        assert!(display.contains("10"));
        assert!(display.contains("alert"));
    }

    #[test]
    fn h0_zj_26_dangerous_pattern_display() {
        let violation = DangerousPatternViolation {
            file: PathBuf::from("code.js"),
            line: 5,
            pattern: "eval(".to_string(),
            context: "eval(userInput)".to_string(),
        };

        let display = format!("{violation}");
        assert!(display.contains("code.js"));
        assert!(display.contains("eval("));
    }

    // =========================================================================
    // H0-ZJ-27: Error types
    // =========================================================================

    #[test]
    fn h0_zj_27_error_display() {
        let cdp_err = ZeroJsError::CdpError("connection failed".to_string());
        assert!(cdp_err.to_string().contains("CDP"));

        let parse_err = ZeroJsError::ParseError("invalid json".to_string());
        assert!(parse_err.to_string().contains("Parse"));

        let io_err = ZeroJsError::IoError("file not found".to_string());
        assert!(io_err.to_string().contains("IO"));
    }

    #[test]
    fn h0_zj_28_error_from_io() {
        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "not found");
        let zero_js_err: ZeroJsError = io_err.into();
        assert!(matches!(zero_js_err, ZeroJsError::IoError(_)));
    }

    // =========================================================================
    // H0-ZJ-29: Edge cases
    // =========================================================================

    #[test]
    fn h0_zj_29_empty_directory() {
        let temp = TempDir::new().unwrap();
        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(temp.path()).unwrap();

        assert!(result.is_valid());
    }

    #[test]
    fn h0_zj_30_non_existent_directory() {
        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(Path::new("/nonexistent/path/12345"));

        // Should return Ok with valid result (empty directory behavior)
        // or Err for IO error
        assert!(result.is_ok() || result.is_err());
    }

    #[test]
    fn h0_zj_31_nested_directories() {
        let temp = TempDir::new().unwrap();
        let nested = temp.path().join("a").join("b").join("c");
        std::fs::create_dir_all(&nested).unwrap();
        std::fs::write(nested.join("deep.js"), "var x = 1;").unwrap();

        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(temp.path()).unwrap();

        assert!(!result.is_valid());
        assert!(!result.unauthorized_js_files.is_empty());
    }

    #[test]
    fn h0_zj_32_manifest_invalid_json() {
        let temp = TempDir::new().unwrap();
        let js_path = temp.path().join("code.js");
        let manifest_path = temp.path().join("code.js.manifest.json");

        std::fs::write(&js_path, "var x = 1;").unwrap();
        std::fs::write(&manifest_path, "not valid json").unwrap();

        let validator = ZeroJsValidator::new();
        assert!(!validator.has_valid_manifest(&js_path));
    }

    #[test]
    fn h0_zj_33_manifest_missing_fields() {
        let temp = TempDir::new().unwrap();
        let js_path = temp.path().join("code.js");
        let manifest_path = temp.path().join("code.js.manifest.json");

        std::fs::write(&js_path, "var x = 1;").unwrap();
        std::fs::write(&manifest_path, r#"{"only": "partial"}"#).unwrap();

        let validator = ZeroJsValidator::new();
        assert!(!validator.has_valid_manifest(&js_path));
    }

    // =========================================================================
    // Additional coverage tests
    // =========================================================================

    #[test]
    fn test_default_validator() {
        let validator = ZeroJsValidator::default();
        assert!(validator.config.require_manifest);
    }

    #[test]
    fn test_violation_count() {
        let result = ZeroJsValidationResult {
            unauthorized_js_files: vec![PathBuf::from("a.js"), PathBuf::from("b.js")],
            forbidden_directories: vec![PathBuf::from("node_modules")],
            inline_scripts_detected: vec![InlineScriptViolation {
                file: PathBuf::from("x.html"),
                line: 1,
                preview: String::new(),
                is_wasm_generated: false,
            }],
            ..Default::default()
        };

        assert_eq!(result.violation_count(), 4);
    }

    #[test]
    fn test_with_timeout_string_pattern() {
        let js = r#"setTimeout("alert(1)", 1000);"#;
        let validator = ZeroJsValidator::new();
        let violations = validator.validate_js_content(js, Path::new("code.js"));
        assert!(!violations.is_empty());
    }

    #[test]
    fn test_setinterval_string_pattern() {
        let js = r#"setInterval("tick()", 100);"#;
        let validator = ZeroJsValidator::new();
        let violations = validator.validate_js_content(js, Path::new("code.js"));
        assert!(!violations.is_empty());
    }

    #[test]
    fn test_prototype_constructor_pattern() {
        let js = "obj.prototype.constructor = Evil;";
        let validator = ZeroJsValidator::new();
        let violations = validator.validate_js_content(js, Path::new("code.js"));
        assert!(!violations.is_empty());
    }

    #[test]
    fn test_outerhtml_pattern() {
        let js = "el.outerHTML = userInput;";
        let validator = ZeroJsValidator::new();
        let violations = validator.validate_js_content(js, Path::new("code.js"));
        assert!(!violations.is_empty());
    }

    #[test]
    fn test_with_statement_pattern() {
        let js = "with (obj) { x = 1; }";
        let validator = ZeroJsValidator::new();
        let violations = validator.validate_js_content(js, Path::new("code.js"));
        assert!(!violations.is_empty());
    }

    #[test]
    fn test_clean_js_no_dangerous() {
        let js = "const x = 1; console.log(x);";
        let validator = ZeroJsValidator::new();
        let violations = validator.validate_js_content(js, Path::new("code.js"));
        assert!(violations.is_empty());
    }

    #[test]
    fn test_check_dangerous_patterns_disabled() {
        let js = "eval('code');";
        let config = ZeroJsConfig {
            check_dangerous_patterns: false,
            ..Default::default()
        };
        let validator = ZeroJsValidator::with_config(config);
        let violations = validator.validate_js_content(js, Path::new("code.js"));
        assert!(violations.is_empty());
    }

    #[test]
    fn test_jsx_file_detected() {
        let temp = TempDir::new().unwrap();
        std::fs::write(
            temp.path().join("component.jsx"),
            "const App = () => <div/>",
        )
        .unwrap();

        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(temp.path()).unwrap();
        assert!(!result.is_valid());
    }

    #[test]
    fn test_tsx_file_detected() {
        let temp = TempDir::new().unwrap();
        std::fs::write(
            temp.path().join("component.tsx"),
            "const App: React.FC = () => <div/>",
        )
        .unwrap();

        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(temp.path()).unwrap();
        assert!(!result.is_valid());
    }

    #[test]
    fn test_coffee_file_detected() {
        let temp = TempDir::new().unwrap();
        std::fs::write(temp.path().join("app.coffee"), "x = 1").unwrap();

        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(temp.path()).unwrap();
        assert!(!result.is_valid());
    }

    #[test]
    fn test_cjs_file_detected() {
        let temp = TempDir::new().unwrap();
        std::fs::write(temp.path().join("module.cjs"), "module.exports = {}").unwrap();

        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(temp.path()).unwrap();
        assert!(!result.is_valid());
    }

    #[test]
    fn test_yarn_directory_detected() {
        let temp = TempDir::new().unwrap();
        std::fs::create_dir(temp.path().join(".yarn")).unwrap();

        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(temp.path()).unwrap();
        assert!(!result.is_valid());
    }

    #[test]
    fn test_pattern_matching_prefix() {
        let validator = ZeroJsValidator::new();
        // Test prefix matching (pattern ending with *)
        assert!(
            validator.matches_allowed_pattern(Path::new("bundle.abc"), &["bundle.*".to_string()])
        );
    }

    #[test]
    fn test_pattern_matching_exact() {
        let validator = ZeroJsValidator::new();
        assert!(validator.matches_allowed_pattern(Path::new("exact.js"), &["exact.js".to_string()]));
    }

    #[test]
    fn test_pattern_matching_no_match() {
        let validator = ZeroJsValidator::new();
        assert!(
            !validator.matches_allowed_pattern(Path::new("other.js"), &["exact.js".to_string()])
        );
    }

    #[test]
    fn test_result_display_with_all_violations() {
        let result = ZeroJsValidationResult {
            valid: false,
            unauthorized_js_files: vec![PathBuf::from("bad.js")],
            unauthorized_css_files: vec![],
            unauthorized_html_files: vec![],
            forbidden_directories: vec![PathBuf::from("node_modules")],
            forbidden_tooling_files: vec![],
            inline_scripts_detected: vec![InlineScriptViolation {
                file: PathBuf::from("index.html"),
                line: 5,
                preview: "alert('hi')".into(),
                is_wasm_generated: false,
            }],
            external_scripts_without_manifest: vec![],
            dangerous_patterns: vec![DangerousPatternViolation {
                file: PathBuf::from("code.js"),
                line: 10,
                pattern: "eval(".into(),
                context: "eval(input)".into(),
            }],
            verified_js_files: vec![],
        };

        let display = format!("{result}");
        assert!(display.contains("FAILED"));
        assert!(display.contains("bad.js"));
        assert!(display.contains("node_modules"));
        assert!(display.contains("alert"));
        assert!(display.contains("eval"));
    }

    #[test]
    fn test_config_with_allowed_css() {
        let temp = TempDir::new().unwrap();
        std::fs::write(temp.path().join("styles.css"), "body { color: red; }").unwrap();

        let config = ZeroJsConfig {
            allowed_css_patterns: vec!["*.css".to_string()],
            ..Default::default()
        };
        let validator = ZeroJsValidator::with_config(config);
        let result = validator.validate_directory(temp.path()).unwrap();

        assert!(result.unauthorized_css_files.is_empty());
    }

    #[test]
    fn test_css_file_not_allowed() {
        let temp = TempDir::new().unwrap();
        std::fs::write(temp.path().join("styles.css"), "body { color: red; }").unwrap();

        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(temp.path()).unwrap();

        assert!(!result.unauthorized_css_files.is_empty());
    }

    #[test]
    fn test_html_with_external_script() {
        let html = r#"
<!DOCTYPE html>
<html>
<head>
    <script src="external.js"></script>
</head>
<body></body>
</html>
"#;
        let validator = ZeroJsValidator::new();
        let violations = validator.validate_html_content(html, Path::new("test.html"));

        // External scripts are not inline scripts, so no violations here
        assert!(violations.is_empty());
    }

    #[test]
    fn test_html_with_wasm_marker() {
        let html = r#"
<!DOCTYPE html>
<html>
<head>
    <script>
        // __PROBAR_WASM_GENERATED__
        init();
    </script>
</head>
<body></body>
</html>
"#;
        let validator = ZeroJsValidator::new();
        let violations = validator.validate_html_content(html, Path::new("test.html"));

        // WASM-generated inline scripts are allowed
        assert!(violations.is_empty());
    }

    #[test]
    fn test_inline_script_violation_is_wasm_generated() {
        let violation = InlineScriptViolation {
            file: PathBuf::from("test.html"),
            line: 5,
            preview: "WebAssembly.instantiate".into(),
            is_wasm_generated: true,
        };
        assert!(violation.is_wasm_generated);
    }

    #[test]
    fn test_forbidden_directories_all() {
        // Test all forbidden directory names
        for dir_name in FORBIDDEN_DIRECTORIES {
            let temp = TempDir::new().unwrap();
            let forbidden = temp.path().join(dir_name);
            std::fs::create_dir(&forbidden).unwrap();

            let validator = ZeroJsValidator::new();
            let result = validator.validate_directory(temp.path()).unwrap();

            assert!(
                !result.is_valid(),
                "Directory '{}' should be forbidden",
                dir_name
            );
        }
    }

    #[test]
    fn test_forbidden_tooling_files_all() {
        // Test a selection of forbidden tooling files
        let tooling_files = &[
            "package-lock.json",
            "yarn.lock",
            "pnpm-lock.yaml",
            "bun.lockb",
            "jsconfig.json",
            ".babelrc",
            "rollup.config.js",
            "vite.config.js",
            "esbuild.config.js",
            ".eslintrc.js",
            ".prettierrc.js",
        ];

        for file_name in tooling_files {
            let temp = TempDir::new().unwrap();
            std::fs::write(temp.path().join(file_name), "{}").unwrap();

            let validator = ZeroJsValidator::new();
            let result = validator.validate_directory(temp.path()).unwrap();

            assert!(
                !result.is_valid(),
                "File '{}' should be forbidden",
                file_name
            );
        }
    }

    #[test]
    fn test_dangerous_patterns_all() {
        // Test all dangerous patterns
        let code_samples = &[
            "eval('code');",
            "new Function('return 1');",
            "Function('code')();",
            "document.write('html');",
            "el.innerHTML = x;",
            "el.outerHTML = y;",
            "obj.__proto__.bad = true;",
            "Foo.prototype.constructor = Bar;",
            "with (obj) {}",
            r#"setTimeout("code", 1);"#,
            r#"setInterval("tick", 1);"#,
        ];

        let validator = ZeroJsValidator::new();

        for code in code_samples {
            let violations = validator.validate_js_content(code, Path::new("test.js"));
            assert!(
                !violations.is_empty(),
                "Pattern should be detected in: {}",
                code
            );
        }
    }

    #[test]
    fn test_zero_js_error_std_error() {
        let err = ZeroJsError::CdpError("test".into());
        let _: &dyn std::error::Error = &err;
    }

    #[test]
    fn test_validation_result_is_valid_comprehensive() {
        let mut result = ZeroJsValidationResult::default();
        result.valid = true;
        assert!(result.is_valid());

        result.unauthorized_js_files.push(PathBuf::from("a.js"));
        assert!(!result.is_valid());

        result.unauthorized_js_files.clear();
        result.unauthorized_css_files.push(PathBuf::from("a.css"));
        assert!(!result.is_valid());

        result.unauthorized_css_files.clear();
        result.unauthorized_html_files.push(PathBuf::from("a.html"));
        assert!(!result.is_valid());

        result.unauthorized_html_files.clear();
        result
            .forbidden_directories
            .push(PathBuf::from("node_modules"));
        assert!(!result.is_valid());

        result.forbidden_directories.clear();
        result
            .forbidden_tooling_files
            .push(PathBuf::from("package.json"));
        assert!(!result.is_valid());

        result.forbidden_tooling_files.clear();
        result.inline_scripts_detected.push(InlineScriptViolation {
            file: PathBuf::from("x.html"),
            line: 1,
            preview: String::new(),
            is_wasm_generated: false,
        });
        assert!(!result.is_valid());

        result.inline_scripts_detected.clear();
        result
            .external_scripts_without_manifest
            .push("ext.js".into());
        assert!(!result.is_valid());

        result.external_scripts_without_manifest.clear();
        result.dangerous_patterns.push(DangerousPatternViolation {
            file: PathBuf::from("code.js"),
            line: 1,
            pattern: "eval(".into(),
            context: String::new(),
        });
        assert!(!result.is_valid());
    }

    #[test]
    fn test_file_without_extension() {
        let temp = TempDir::new().unwrap();
        std::fs::write(temp.path().join("Makefile"), "all:\n\techo hello").unwrap();

        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(temp.path()).unwrap();

        // File without JS extension should be fine
        assert!(result.unauthorized_js_files.is_empty());
    }

    #[test]
    fn test_file_with_unknown_extension() {
        let temp = TempDir::new().unwrap();
        std::fs::write(temp.path().join("data.xyz"), "some data").unwrap();

        let validator = ZeroJsValidator::new();
        let result = validator.validate_directory(temp.path()).unwrap();

        assert!(result.is_valid());
    }

    #[test]
    fn test_htm_extension() {
        let temp = TempDir::new().unwrap();
        let html = r#"
<!DOCTYPE html>
<html>
<head>
    <script>alert('test')</script>
</head>
<body></body>
</html>
"#;
        std::fs::write(temp.path().join("page.htm"), html).unwrap();

        let validator = ZeroJsValidator::with_config(ZeroJsConfig {
            allow_wasm_inline_scripts: false,
            ..Default::default()
        });
        let result = validator.validate_directory(temp.path()).unwrap();

        assert!(!result.inline_scripts_detected.is_empty());
    }

    #[test]
    fn test_allowed_js_with_manifest_requirement() {
        let temp = TempDir::new().unwrap();
        let js_path = temp.path().join("app.worker.js");
        let manifest_path = temp.path().join("app.worker.js.manifest.json");

        std::fs::write(&js_path, "self.onmessage = function() {}").unwrap();
        std::fs::write(
            &manifest_path,
            r#"{"manifest_version": 1, "output_hash": "abc", "generation": {}}"#,
        )
        .unwrap();

        let config = ZeroJsConfig::default().with_allowed_js("*.worker.js");
        let validator = ZeroJsValidator::with_config(config);
        let result = validator.validate_directory(temp.path()).unwrap();

        assert!(result.is_valid());
        assert!(!result.verified_js_files.is_empty());
    }

    #[test]
    fn test_allowed_js_without_manifest_fails() {
        let temp = TempDir::new().unwrap();
        let js_path = temp.path().join("app.worker.js");
        std::fs::write(&js_path, "self.onmessage = function() {}").unwrap();

        let config = ZeroJsConfig::default().with_allowed_js("*.worker.js");
        let validator = ZeroJsValidator::with_config(config);
        let result = validator.validate_directory(temp.path()).unwrap();

        // Without manifest, even allowed patterns fail
        assert!(!result.is_valid());
    }

    #[test]
    fn test_verified_js_with_dangerous_patterns() {
        let temp = TempDir::new().unwrap();
        let js_path = temp.path().join("worker.js");
        let manifest_path = temp.path().join("worker.js.manifest.json");

        // JS with dangerous pattern
        std::fs::write(&js_path, "const x = eval('1')").unwrap();
        std::fs::write(
            &manifest_path,
            r#"{"manifest_version": 1, "output_hash": "abc", "generation": {}}"#,
        )
        .unwrap();

        let config = ZeroJsConfig::default().with_allowed_js("*.js");
        let validator = ZeroJsValidator::with_config(ZeroJsConfig {
            require_manifest: false,
            ..config
        });
        let result = validator.validate_directory(temp.path()).unwrap();

        // File is verified but has dangerous patterns
        assert!(!result.verified_js_files.is_empty());
        assert!(!result.dangerous_patterns.is_empty());
    }

    #[test]
    fn test_skip_path_nested() {
        let temp = TempDir::new().unwrap();
        let vendor = temp.path().join("vendor").join("external");
        std::fs::create_dir_all(&vendor).unwrap();
        std::fs::write(vendor.join("lib.js"), "var x = 1;").unwrap();

        let config = ZeroJsConfig::default().with_skip_path(temp.path().join("vendor"));
        let validator = ZeroJsValidator::with_config(config);
        let result = validator.validate_directory(temp.path()).unwrap();

        assert!(result.is_valid());
    }

    #[test]
    fn test_multiple_inline_scripts() {
        let html = r#"
<!DOCTYPE html>
<html>
<head>
    <script>first();</script>
    <script>second();</script>
    <script>third();</script>
</head>
<body></body>
</html>
"#;
        let validator = ZeroJsValidator::with_config(ZeroJsConfig {
            allow_wasm_inline_scripts: false,
            ..Default::default()
        });
        let violations = validator.validate_html_content(html, Path::new("test.html"));

        assert_eq!(violations.len(), 3);
    }

    #[test]
    fn test_html_script_not_closed() {
        let html = r#"
<!DOCTYPE html>
<html>
<head>
    <script>unclosed
"#;
        let validator = ZeroJsValidator::with_config(ZeroJsConfig {
            allow_wasm_inline_scripts: false,
            ..Default::default()
        });
        let violations = validator.validate_html_content(html, Path::new("test.html"));

        // Script tag not closed - no violation recorded since no </script>
        assert!(violations.is_empty());
    }
}
