use crate::{
    invocation::{SkillContext, SkillInput},
    outcome::SkillOutcome,
    skill::{Skill, SkillId},
};
use forge::budget::estimator::TokenEstimator;
use serde_json::json;
use weave::lenses::{clarity::ClarityLens, narrow::NarrowLens, Lens, LensContext};

/// Parallax: read multiple files in one call, apply per-file lens, return merged result.
pub struct ParallaxSkill;

impl Skill for ParallaxSkill {
    fn id(&self) -> SkillId {
        SkillId("parallax")
    }
    fn display_name(&self) -> &str {
        "Parallax"
    }

    fn input_schema(&self) -> serde_json::Value {
        json!({
            "type": "object",
            "properties": {
                "paths": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "File paths to read"
                },
                "budget_tokens": { "type": "integer", "default": 4000 },
                "lens": {
                    "type": "string",
                    "enum": ["auto", "full", "aggressive"],
                    "default": "auto"
                }
            },
            "required": ["paths"]
        })
    }

    fn output_schema(&self) -> serde_json::Value {
        json!({
            "type": "object",
            "properties": {
                "files": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "path": { "type": "string" },
                            "content": { "type": "string" },
                            "tokens": { "type": "integer" },
                            "error": { "type": "string" }
                        }
                    }
                },
                "total_tokens": { "type": "integer" },
                "tokens_before": { "type": "integer" }
            }
        })
    }

    fn execute(&self, input: SkillInput, _ctx: &SkillContext) -> anyhow::Result<SkillOutcome> {
        let paths = input.get_str_array("paths").unwrap_or_default();
        let budget = input.get_u64("budget_tokens").unwrap_or(4000) as usize;
        let lens_name = input.get_str("lens").unwrap_or("auto");

        let per_file_budget = if paths.is_empty() {
            budget
        } else {
            budget / paths.len().max(1)
        };

        let mut files = Vec::new();
        let mut total_before = 0usize;
        let mut total_after = 0usize;

        for path in &paths {
            match std::fs::read_to_string(path) {
                Ok(content) => {
                    let tb = TokenEstimator::count(&content);
                    total_before += tb;
                    let compressed = apply_lens(&content, lens_name, per_file_budget);
                    let ta = TokenEstimator::count(&compressed);
                    total_after += ta;
                    files.push(json!({
                        "path": path,
                        "content": compressed,
                        "tokens": ta
                    }));
                }
                Err(e) => {
                    files.push(json!({
                        "path": path,
                        "error": e.to_string()
                    }));
                }
            }
        }

        Ok(SkillOutcome::success(
            "parallax",
            json!({
                "files": files,
                "total_tokens": total_after,
                "tokens_before": total_before
            }),
        )
        .with_tokens(total_after))
    }
}

fn apply_lens(content: &str, lens: &str, budget: usize) -> String {
    let ctx = LensContext::new(budget);
    match lens {
        "aggressive" => {
            let c = ClarityLens.apply(content, &ctx);
            NarrowLens.apply(&c.content, &ctx).content
        }
        "full" => content.to_string(),
        _ => ClarityLens.apply(content, &ctx).content,
    }
}

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

    #[test]
    fn full_lens_returns_content_unchanged() {
        let input = "fn foo() {\n    let x = 1;\n    x\n}\n";
        assert_eq!(apply_lens(input, "full", 4000), input);
    }

    #[test]
    fn auto_lens_returns_non_empty() {
        let input = "fn foo() {\n    println!(\"hello\");\n}\n";
        let result = apply_lens(input, "auto", 4000);
        assert!(!result.is_empty());
    }

    #[test]
    fn aggressive_lens_returns_something() {
        let input = "fn foo() {}\nfn bar() {}\nfn baz() {}\n";
        let result = apply_lens(input, "aggressive", 100);
        assert!(!result.is_empty());
    }

    #[test]
    fn unknown_lens_behaves_like_clarity() {
        let input = "fn bar() {}";
        let auto_out = apply_lens(input, "auto", 4000);
        let unknown_out = apply_lens(input, "mystery_lens", 4000);
        assert_eq!(auto_out, unknown_out);
    }

    #[test]
    fn full_lens_longer_than_aggressive_for_large_input() {
        let input = "x ".repeat(500);
        let full = apply_lens(&input, "full", 100);
        let aggressive = apply_lens(&input, "aggressive", 100);
        assert!(full.len() >= aggressive.len());
    }
}
