//! Heuristic repo map (plan §11): a cheap, language-aware extraction of
//! top-level signatures so the agent sees structure without reading every line.
//! Tree-sitter can replace this behind a feature later; the interface is stable.

use crate::repo::{Category, Scan};
use crate::util;

/// Build a compact textual repo map: a directory tree followed by per-file
/// top-level symbols. Bounded in size to keep prompts affordable.
pub fn build(scan: &Scan, max_files: usize, max_symbols: usize) -> String {
    let mut out = String::new();
    out.push_str("# Repository map\n\n");
    out.push_str("## Tree\n\n```\n");
    out.push_str(&scan.tree);
    out.push_str("```\n\n");

    out.push_str("## Top-level symbols\n\n");
    let mut shown = 0usize;
    for file in &scan.files {
        if file.category != Category::Code {
            continue;
        }
        if shown >= max_files {
            out.push_str(&format!(
                "\n_(symbol extraction capped at {max_files} files)_\n"
            ));
            break;
        }
        let content = match std::fs::read_to_string(&file.abs) {
            Ok(c) => c,
            Err(_) => continue,
        };
        let symbols = extract_symbols(file.lang, &content, max_symbols);
        if symbols.is_empty() {
            continue;
        }
        out.push_str(&format!("### {} ({})\n", file.rel, file.lang));
        for s in symbols {
            out.push_str("- ");
            out.push_str(&s);
            out.push('\n');
        }
        out.push('\n');
        shown += 1;
    }
    out
}

/// Extract up to `max` declaration-like lines for a language.
pub fn extract_symbols(lang: &str, content: &str, max: usize) -> Vec<String> {
    let mut out = Vec::new();
    for raw in content.lines() {
        if out.len() >= max {
            break;
        }
        let line = raw.trim();
        if line.is_empty() || line.starts_with("//") || line.starts_with('*') {
            continue;
        }
        if let Some(sig) = signature_for(lang, line) {
            let sig = util::truncate_chars(&sig, 120);
            if !out.contains(&sig) {
                out.push(sig);
            }
        }
    }
    out
}

fn signature_for(lang: &str, line: &str) -> Option<String> {
    let l = line;
    match lang {
        "rust" => {
            if starts_any(
                l,
                &[
                    "pub fn ",
                    "fn ",
                    "pub async fn ",
                    "async fn ",
                    "pub(crate) fn ",
                    "pub struct ",
                    "struct ",
                    "pub enum ",
                    "enum ",
                    "pub trait ",
                    "trait ",
                    "impl ",
                    "pub mod ",
                    "mod ",
                    "macro_rules! ",
                    "pub type ",
                    "type ",
                ],
            ) {
                return Some(clean(l));
            }
            None
        }
        "python" => {
            if starts_any(l, &["def ", "class ", "async def "]) {
                return Some(clean(l.trim_end_matches(':')));
            }
            None
        }
        "typescript" | "javascript" => {
            if starts_any(
                l,
                &[
                    "export function ",
                    "export async function ",
                    "function ",
                    "async function ",
                    "export class ",
                    "class ",
                    "export interface ",
                    "interface ",
                    "export type ",
                    "type ",
                    "export enum ",
                    "enum ",
                    "export default function ",
                ],
            ) {
                return Some(clean(l));
            }
            // exported arrow functions: `export const foo = (...) =>`
            if (l.starts_with("export const ") || l.starts_with("const "))
                && (l.contains("=>") || l.contains("= function") || l.contains("= async"))
            {
                return Some(clean(l.trim_end_matches('{')));
            }
            None
        }
        "go" => {
            if starts_any(l, &["func ", "type "]) {
                return Some(clean(l.trim_end_matches('{')));
            }
            None
        }
        _ => generic_signature(l),
    }
}

/// Generic C-family heuristic: a line that opens a block and looks like a
/// declaration (has parens, isn't a control-flow keyword).
fn generic_signature(l: &str) -> Option<String> {
    if !l.ends_with('{') || !l.contains('(') {
        return None;
    }
    let first = l.split(['(', ' ']).next().unwrap_or("");
    const CONTROL: &[&str] = &[
        "if", "for", "while", "switch", "else", "catch", "do", "return", "}", "})",
    ];
    if CONTROL.contains(&first) {
        return None;
    }
    Some(clean(l.trim_end_matches('{')))
}

fn starts_any(l: &str, prefixes: &[&str]) -> bool {
    prefixes.iter().any(|p| l.starts_with(p))
}

fn clean(l: &str) -> String {
    l.trim().trim_end_matches(['{', ' ']).trim().to_string()
}
