use crate::{
    invocation::{SkillContext, SkillInput},
    outcome::SkillOutcome,
    skill::{Skill, SkillId},
};
use forge::budget::estimator::TokenEstimator;
use serde_json::json;

/// Thermal: token heatmap — identify which sections of a file consume the most tokens.
pub struct ThermalSkill;

impl Skill for ThermalSkill {
    fn id(&self) -> SkillId {
        SkillId("thermal")
    }
    fn display_name(&self) -> &str {
        "Thermal"
    }

    fn input_schema(&self) -> serde_json::Value {
        json!({
            "type": "object",
            "properties": {
                "path": { "type": "string", "description": "File to analyse" },
                "content": { "type": "string", "description": "Content to analyse (alternative to path)" },
                "chunk_lines": { "type": "integer", "default": 20, "description": "Lines per heat chunk" },
                "top_k": { "type": "integer", "default": 5, "description": "Return top K hottest chunks" }
            }
        })
    }

    fn output_schema(&self) -> serde_json::Value {
        json!({
            "type": "object",
            "properties": {
                "total_tokens": { "type": "integer" },
                "hotspots": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "start_line": { "type": "integer" },
                            "end_line": { "type": "integer" },
                            "tokens": { "type": "integer" },
                            "pct": { "type": "number" },
                            "preview": { "type": "string" }
                        }
                    }
                }
            }
        })
    }

    fn execute(&self, input: SkillInput, _ctx: &SkillContext) -> anyhow::Result<SkillOutcome> {
        let content = if let Some(c) = input.get_str("content") {
            c.to_string()
        } else if let Some(p) = input.get_str("path") {
            std::fs::read_to_string(p).unwrap_or_default()
        } else {
            return Ok(SkillOutcome::failure("thermal", "path or content required"));
        };

        let chunk_lines = input.get_u64("chunk_lines").unwrap_or(20) as usize;
        let top_k = input.get_u64("top_k").unwrap_or(5) as usize;

        let lines: Vec<&str> = content.lines().collect();
        let total_tokens = TokenEstimator::count(&content);

        let mut chunks: Vec<(usize, usize, usize, String)> = lines
            .chunks(chunk_lines)
            .enumerate()
            .map(|(i, chunk)| {
                let start = i * chunk_lines + 1;
                let end = start + chunk.len() - 1;
                let chunk_str = chunk.join("\n");
                let tokens = TokenEstimator::count_nonblocking(&chunk_str);
                let preview = chunk
                    .first()
                    .copied()
                    .unwrap_or("")
                    .chars()
                    .take(60)
                    .collect();
                (start, end, tokens, preview)
            })
            .collect();

        chunks.sort_by_key(|b| std::cmp::Reverse(b.2));
        chunks.truncate(top_k);

        let hotspots: Vec<serde_json::Value> = chunks
            .into_iter()
            .map(|(start, end, tokens, preview)| {
                let pct = if total_tokens > 0 {
                    tokens as f64 / total_tokens as f64 * 100.0
                } else {
                    0.0
                };
                json!({
                    "start_line": start,
                    "end_line": end,
                    "tokens": tokens,
                    "pct": (pct * 10.0).round() / 10.0,
                    "preview": preview
                })
            })
            .collect();

        Ok(SkillOutcome::success(
            "thermal",
            json!({
                "total_tokens": total_tokens,
                "hotspots": hotspots
            }),
        )
        .with_tokens(total_tokens))
    }
}
