//! Structural comparison of SQL queries using polyglot-sql's diff module.
//!
//! Note: polyglot-sql's diff algorithm uses deep recursion that can overflow
//! the default 8MB thread stack. Comparison runs on a thread with a larger stack.

use super::dialect_map::to_polyglot_dialect;
use super::types::{CompareResult, DiffEntry};
use crate::schema::types::SqlDialect;
use polyglot_sql::diff;

/// Stack size for diff threads (32MB).
const DIFF_THREAD_STACK_SIZE: usize = 32 * 1024 * 1024;

/// Compare two SQL queries structurally and return a diff.
///
/// Parses both queries into ASTs, then uses the ChangeDistiller algorithm
/// to produce a list of structural differences.
pub fn compare_queries(
    left_sql: &str,
    right_sql: &str,
    dialect: SqlDialect,
) -> Result<CompareResult, String> {
    let left = left_sql.to_string();
    let right = right_sql.to_string();
    std::thread::Builder::new()
        .stack_size(DIFF_THREAD_STACK_SIZE)
        .spawn(move || compare_queries_inner(&left, &right, dialect))
        .map_err(|e| format!("failed to spawn comparison thread: {e}"))?
        .join()
        .map_err(|_| "comparison thread panicked".to_string())?
}

/// Inner implementation of compare_queries.
fn compare_queries_inner(
    left_sql: &str,
    right_sql: &str,
    dialect: SqlDialect,
) -> Result<CompareResult, String> {
    let pg_dialect = to_polyglot_dialect(dialect);

    let left_expr =
        polyglot_sql::parse_one(left_sql, pg_dialect).map_err(|e| format!("Left query: {e}"))?;
    let right_expr =
        polyglot_sql::parse_one(right_sql, pg_dialect).map_err(|e| format!("Right query: {e}"))?;

    let edits = diff::diff(&left_expr, &right_expr, true);

    let diffs: Vec<DiffEntry> = edits
        .iter()
        .map(|edit| match edit {
            diff::Edit::Insert { expression } => DiffEntry {
                change_type: "insert".to_string(),
                description: format!("Added: {expression:?}"),
            },
            diff::Edit::Remove { expression } => DiffEntry {
                change_type: "remove".to_string(),
                description: format!("Removed: {expression:?}"),
            },
            diff::Edit::Update { source, target } => DiffEntry {
                change_type: "update".to_string(),
                description: format!("Changed: {source:?} -> {target:?}"),
            },
            diff::Edit::Move { source, target } => DiffEntry {
                change_type: "move".to_string(),
                description: format!("Moved: {source:?} -> {target:?}"),
            },
            diff::Edit::Keep { .. } => DiffEntry {
                change_type: "keep".to_string(),
                description: "Unchanged".to_string(),
            },
        })
        .collect();

    let diff_count = diffs.len();
    Ok(CompareResult {
        identical: diff_count == 0,
        diff_count,
        diffs,
    })
}
