//! Python extractor.
//!
//! Walks tree-sitter-python output and emits Symbols (function, class,
//! method, decorator-applied) plus best-effort Calls/Imports/Extends edges.

mod imports;
mod calls;
mod decls;
mod walk;

use std::path::Path;

use ckg_core::{Kind, Result, Symbol, node_id};
use tree_sitter::Node;

use crate::ParseOutput;

// Re-export `text` so external callers (`crate::framework::fastapi`) can
// reach it via `crate::lang::python::text` — same path as before the split.
pub(crate) use crate::util::text;

fn module_name(path: &Path) -> String {
    path.with_extension("")
        .components()
        .filter_map(|c| c.as_os_str().to_str().map(str::to_owned))
        .collect::<Vec<_>>()
        .join(".")
}

pub(crate) struct Ctx<'a> {
    pub(crate) repo_id: &'a str,
    pub(crate) relpath: String,
    pub(crate) module: String,
    pub(crate) out: ParseOutput,
    pub(crate) name_to_id: std::collections::HashMap<String, String>,
    /// Import alias → fully-qualified path. `from foo import bar as b` → `b` → `foo.bar`.
    pub(crate) aliases: std::collections::HashMap<String, String>,
    /// `(src_id, raw_target, is_awaited)` — when `is_awaited=true`, the
    /// resolver emits an `Awaits` edge alongside `Calls`.
    pub(crate) raw_calls: Vec<(String, String, bool)>,
}

impl Ctx<'_> {
    pub(crate) fn push(
        &mut self,
        n: Node<'_>,
        src: &str,
        kind: Kind,
        qname: String,
        name: String,
        doc: String,
    ) -> String {
        let pos = n.start_position();
        let id = node_id(self.repo_id, "python", &self.relpath, &qname, kind);
        self.name_to_id.insert(name.clone(), id.clone());
        self.out.symbols.push(Symbol {
            id: id.clone(),
            qname,
            name,
            kind,
            file: self.relpath.clone(),
            line: pos.row as u32 + 1,
            col: pos.column as u32,
            is_public: true, // Python has no `pub` — treat all module-scope as public; private = `_` prefix.
            doc,
            hash: blake3::hash(crate::util::text(n, src).as_bytes()).to_hex()[..16].to_string(),
        });
        id
    }
}

// Re-exports consumed by `tests.rs` via `use super::*`.
// `Edge` and `EdgeKind` are needed for test assertions; `strip_generics` is
// called as `super::strip_generics(...)` explicitly in the tests.
#[cfg(test)]
pub(crate) use ckg_core::{Edge, EdgeKind};
#[cfg(test)]
pub(crate) use imports::strip_generics;

// FastAPI / Flask route detection lives in `crate::framework::fastapi`.

pub fn extract(repo_id: &str, path: &Path, source: &str) -> Result<ParseOutput> {
    let lang: tree_sitter::Language = tree_sitter_python::LANGUAGE.into();
    let mut parser = crate::util::get_pooled_parser("python", &lang);
    let tree = crate::util::guarded_parse(&mut parser, source, "python");
    // H11: complete the full tree walk BEFORE returning the parser to the pool.
    // tree-sitter's Tree is independent of Parser today, but tying their
    // lifetimes would silently corrupt walks. Reorder eliminates the invariant
    // dependence entirely. Pool contention cost is negligible (parse >> walk).
    let tree = tree?;
    let mut ctx = Ctx {
        repo_id,
        relpath: crate::util::path_to_relpath(path)?,
        module: module_name(path),
        out: ParseOutput::default(),
        name_to_id: std::collections::HashMap::new(),
        aliases: std::collections::HashMap::new(),
        raw_calls: Vec::new(),
    };
    decls::extract_module_docstring(&mut ctx, tree.root_node(), source);
    let dunder_all = decls::collect_dunder_all(tree.root_node(), source);
    walk::walk(&mut ctx, tree.root_node(), source, "");
    decls::emit_lambda_symbols(&mut ctx, tree.root_node(), source);
    calls::resolve_calls(&mut ctx);
    decls::apply_dunder_all(&mut ctx.out.symbols, &dunder_all, &ctx.module);
    if path.file_name().and_then(|n| n.to_str()) == Some("__init__.py") {
        decls::emit_package_reexports(&mut ctx);
    }
    crate::util::return_pooled_parser("python", parser);
    Ok(ctx.out)
}

#[cfg(test)]
mod tests;
