//! Column lineage via polyglot-sql.
//!
//! Wraps [`polyglot_sql::lineage::column_lineage()`] with a 64MB stack
//! thread to handle deep AST recursion safely.  Always uses `values_only`
//! mode — traces only value-producing column references, matching Snowflake
//! ACCESS_USAGE "direct" lineage semantics.

use crate::polyglot::dialect_map::to_polyglot_dialect;
use crate::schema::types::SqlDialect;
use polyglot_sql::lineage::{ColumnLineageResult, LineageOptions};

/// Stack size for lineage threads (64MB).
const LINEAGE_THREAD_STACK_SIZE: usize = 64 * 1024 * 1024;

/// Maximum parenthesis nesting depth before we refuse to analyze.
const MAX_NESTING_DEPTH: usize = 200;

/// Run polyglot-sql column lineage on a SQL query (64MB stack thread).
///
/// Returns [`ColumnLineageResult`] with edges (source→target columns)
/// and any tracing errors.  Each edge includes:
/// - `lens_type`: Unchanged / Alias / Transformation
/// - `lens_code`: full transformation chain
/// - `terminal_kind`: Table / Star / Placeholder / Unknown
///
/// Uses `values_only` mode — skips CASE WHEN conditions, IFF condition
/// args, date-part keywords, and window PARTITION BY/ORDER BY.
pub fn polyglot_column_lineage(
    sql: &str,
    dialect: SqlDialect,
) -> Result<ColumnLineageResult, String> {
    let mut depth: usize = 0;
    let mut max_depth: usize = 0;
    for ch in sql.chars() {
        match ch {
            '(' => {
                depth += 1;
                if depth > max_depth {
                    max_depth = depth;
                }
            }
            ')' => {
                depth = depth.saturating_sub(1);
            }
            _ => {}
        }
    }
    if max_depth > MAX_NESTING_DEPTH {
        return Err(format!(
            "query nesting depth {max_depth} exceeds limit {MAX_NESTING_DEPTH}"
        ));
    }

    let sql_owned = sql.to_string();
    let poly_dialect = to_polyglot_dialect(dialect);
    let options = LineageOptions { values_only: true };

    std::thread::Builder::new()
        .stack_size(LINEAGE_THREAD_STACK_SIZE)
        .spawn(move || {
            polyglot_sql::lineage::column_lineage(&sql_owned, poly_dialect, Some(&options))
        })
        .map_err(|e| format!("lineage thread spawn failed: {e}"))?
        .join()
        .map_err(|_| "lineage thread panicked".to_string())
}

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

    #[test]
    fn basic_lineage() {
        let result = polyglot_column_lineage("SELECT a, b FROM t1", SqlDialect::Generic).unwrap();
        assert!(!result.edges.is_empty() || !result.errors.is_empty());
    }

    #[test]
    fn empty_sql() {
        let result = polyglot_column_lineage("", SqlDialect::Generic).unwrap();
        assert!(result.edges.is_empty());
    }
}
