use std::collections::HashMap;

/// 文件概要提取 — 极简程序分析
/// 从声明式定义文件(skeleton.toml)加载各语言的骨架提取规则
pub struct FileSummary {
    /// 扩展名 → 行前缀标记列表
    markers: HashMap<String, Vec<String>>,
    /// 全文显示的扩展名集合
    full_extensions: Vec<String>,
}

/// 文件概要提取结果
pub enum FileSummaryResult {
    /// 全文显示（md等配置的扩展名）
    Full,
    /// 骨架（已拼接为单个字符串，带行号）
    Skeleton(String),
    /// 头尾预览（未知扩展名兜底，带行号）
    Preview(String),
}

#[derive(serde::Deserialize)]
struct TomlRoot {
    languages: HashMap<String, LanguageDef>,
    #[serde(default)]
    full_extensions: Vec<String>,
}

#[derive(serde::Deserialize)]
struct LanguageDef {
    extensions: Vec<String>,
    markers: Vec<String>,
}

/// Number of head lines in preview.
const PREVIEW_HEAD_LINES: usize = 10;
/// Number of tail lines in preview.
const PREVIEW_TAIL_LINES: usize = 5;
/// Line count threshold to switch from head-only to head+tail preview.
const PREVIEW_THRESHOLD: usize = 15;
/// Ellipsis line for preview.
const PREVIEW_ELLIPSIS: &str = "     ...";

impl FileSummary {
    /// 全局单例访问
    pub fn get() -> &'static Self {
        static INSTANCE: std::sync::OnceLock<FileSummary> = std::sync::OnceLock::new();
        INSTANCE.get_or_init(Self::load)
    }

    fn load() -> Self {
        let toml_str = include_str!("skeleton.toml");
        let root: TomlRoot = toml::from_str(toml_str).expect("Failed to parse skeleton.toml");

        let mut markers = HashMap::new();
        for lang in root.languages.values() {
            for ext in &lang.extensions {
                markers.insert(ext.clone(), lang.markers.clone());
            }
        }

        FileSummary {
            markers,
            full_extensions: root.full_extensions,
        }
    }

    /// 从文件路径和内容提取概要
    pub fn extract(&self, path: &str, content: &str) -> FileSummaryResult {
        let ext = path.rsplit('.').next().unwrap_or("").to_lowercase();

        // 全文显示的扩展名
        if self.full_extensions.iter().any(|e| e == &ext) {
            return FileSummaryResult::Full;
        }

        // 尝试骨架提取
        if let Some(skeleton) = self.extract_skeleton(&ext, content) {
            if !skeleton.is_empty() {
                return FileSummaryResult::Skeleton(skeleton);
            }
        }

        // 兜底：头尾预览
        let lines: Vec<&str> = content.lines().collect();
        FileSummaryResult::Preview(format_preview(&lines))
    }

    /// 根据文件扩展名提取骨架行
    /// 返回None表示该扩展名没有配置规则
    fn extract_skeleton(&self, ext: &str, content: &str) -> Option<String> {
        let markers = self.markers.get(ext)?;
        let skeleton: Vec<String> = content
            .lines()
            .enumerate()
            .filter(|(_, line)| {
                let trimmed = line.trim_start();
                markers.iter().any(|m| trimmed.starts_with(m))
            })
            .map(|(i, line)| format!("{:>4}: {}", i + 1, line))
            .collect();
        Some(skeleton.join("\n"))
    }
}

/// Format head+tail preview from lines with line numbers.
fn format_preview(lines: &[&str]) -> String {
    let total = lines.len();
    let actual_head = std::cmp::min(PREVIEW_HEAD_LINES, total);
    let mut preview: Vec<String> = Vec::new();
    for (i, line) in lines[..actual_head].iter().enumerate() {
        preview.push(format!("{:>4}: {}", i + 1, line));
    }
    if total > PREVIEW_THRESHOLD {
        preview.push(PREVIEW_ELLIPSIS.to_string());
        for (i, line) in lines[total - PREVIEW_TAIL_LINES..].iter().enumerate() {
            preview.push(format!("{:>4}: {}", total - PREVIEW_TAIL_LINES + i + 1, line));
        }
    } else if total > PREVIEW_HEAD_LINES {
        for (i, line) in lines[PREVIEW_HEAD_LINES..].iter().enumerate() {
            preview.push(format!("{:>4}: {}", PREVIEW_HEAD_LINES + i + 1, line));
        }
    }
    preview.join("\n")
}