//! Compiler bridge: connects pfcl-parser, pfcl-resolver to ClosureNode emission.
//!
//! Pipeline: source → parse → convert → resolve → emit_from_rexpr → optimize

use pfcl_catalog::{FunctionCatalog, FunctionKind, Intrinsic};
use pfcl_types::Value;
use crate::closure_node::{ClosureArm, ClosureNode, ClosurePattern};
use crate::evaluator::optimize;

use pfcl_parser::ast as parser_ast;
use pfcl_parser::span::Span as ParserSpan;
use pfcl_resolver::{
    self as resolver,
    CatalogLookup, RExpr, RExprKind, RMatchArm,
    Expr as RInput, ExprKind as RInputKind,
    Param as RParam, Span as RSpan,
    MatchArm as RInputArm, Pattern as RPattern, PatternKind as RPatternKind,
};

// ── CatalogLookup adapter ─────────────────────────────────────────────────────

pub struct CatalogLookupAdapter<'a> {
    pub catalog: &'a FunctionCatalog,
}

impl<'a> CatalogLookup for CatalogLookupAdapter<'a> {
    fn lookup(&self, segments: &[String]) -> Option<String> {
        let name = segments.join(".");
        self.catalog.get_by_name(&name).map(|_| format!("hash:{}", name))
    }
}

// ── Parser AST → Resolver AST conversion ─────────────────────────────────────

fn convert_span(s: &ParserSpan) -> RSpan {
    RSpan::new(s.start, s.end)
}

pub fn convert_expr(expr: &parser_ast::Expr) -> RInput {
    let span = convert_span(&expr.span);
    let kind = match &expr.kind {
        parser_ast::ExprKind::Int(n)    => RInputKind::Int(*n),
        parser_ast::ExprKind::Float(f)  => RInputKind::Float(*f),
        parser_ast::ExprKind::Str(s)    => RInputKind::Str(s.clone()),
        parser_ast::ExprKind::Bool(b)   => RInputKind::Bool(*b),
        parser_ast::ExprKind::Nothing   => RInputKind::Nothing,
        parser_ast::ExprKind::Var(name) => RInputKind::Var(name.clone()),
        parser_ast::ExprKind::DottedName(segs) => RInputKind::DottedName(segs.clone()),

        parser_ast::ExprKind::Lambda(params, body) => {
            let rparams: Vec<RParam> = params.iter().map(|p| RParam {
                name: p.name.clone(),
                span: convert_span(&p.span),
            }).collect();
            RInputKind::Lambda(rparams, Box::new(convert_expr(body)))
        }

        parser_ast::ExprKind::Apply(func, args) =>
            RInputKind::Apply(Box::new(convert_expr(func)), args.iter().map(convert_expr).collect()),

        parser_ast::ExprKind::Let(name, value, body) =>
            RInputKind::Let(name.clone(), Box::new(convert_expr(value)), Box::new(convert_expr(body))),

        parser_ast::ExprKind::Match(scrutinee, arms) =>
            RInputKind::Match(
                Box::new(convert_expr(scrutinee)),
                arms.iter().map(|arm| RInputArm {
                    pattern: convert_pattern(&arm.pattern),
                    body: convert_expr(&arm.body),
                    span: convert_span(&arm.span),
                }).collect(),
            ),

        parser_ast::ExprKind::List(elems)  => RInputKind::List(elems.iter().map(convert_expr).collect()),
        parser_ast::ExprKind::Tuple(elems) => RInputKind::Tuple(elems.iter().map(convert_expr).collect()),

        parser_ast::ExprKind::Record(fields) =>
            RInputKind::Record(fields.iter().map(|(k, v)| (k.clone(), convert_expr(v))).collect()),

        // Pipe (`|>`) was retired from the language; the parser no
        // longer produces it.

        // A1: typed hole — pass name + raw type-hint text through to the
        // resolver's RInputKind::Hole. The parser's `type_hint` is a
        // `PType`; the resolver-input AST is parser-independent and carries
        // the hint as raw text, so format the PType back to source text.
        parser_ast::ExprKind::Hole { name, type_hint } =>
            RInputKind::Hole {
                name: name.clone(),
                type_hint: type_hint.as_ref().map(|t| pfcl_parser::type_parser::format_type(t)),
            },

        parser_ast::ExprKind::Constructor(name, args) =>
            RInputKind::Constructor(name.clone(), args.iter().map(convert_expr).collect()),
    };
    RInput { kind, span }
}

fn convert_pattern(pat: &parser_ast::Pattern) -> RPattern {
    let span = convert_span(&pat.span);
    let kind = match &pat.kind {
        parser_ast::PatternKind::Wildcard             => RPatternKind::Wildcard,
        parser_ast::PatternKind::Var(name)            => RPatternKind::Var(name.clone()),
        parser_ast::PatternKind::Int(n)               => RPatternKind::Int(*n),
        parser_ast::PatternKind::Str(s)               => RPatternKind::Str(s.clone()),
        parser_ast::PatternKind::Bool(b)              => RPatternKind::Bool(*b),
        parser_ast::PatternKind::Nothing              => RPatternKind::Nothing,
        parser_ast::PatternKind::Constructor(n, args) =>
            RPatternKind::Constructor(n.clone(), args.iter().map(convert_pattern).collect()),
        parser_ast::PatternKind::Tuple(pats)          =>
            RPatternKind::Tuple(pats.iter().map(convert_pattern).collect()),
    };
    RPattern { kind, span }
}

// ── RExpr → ClosureNode emitter ───────────────────────────────────────────────

pub fn emit_from_rexpr(catalog: &FunctionCatalog, rexpr: &RExpr) -> ClosureNode {
    match &rexpr.kind {
        RExprKind::Int(n)    => ClosureNode::Literal(Value::Int(*n)),
        RExprKind::Float(f)  => ClosureNode::Literal(Value::Float(*f)),
        RExprKind::Str(s)    => ClosureNode::Literal(Value::Str(std::sync::Arc::from(s.as_str()))),
        RExprKind::Bool(b)   => ClosureNode::Literal(Value::Bool(*b)),
        RExprKind::Nothing   => ClosureNode::Literal(Value::Nothing),

        RExprKind::BoundVar { index, .. } => ClosureNode::Var(*index),

        RExprKind::CatalogRef { name, .. } => emit_catalog_ref(catalog, name),

        RExprKind::FieldAccess { expr, fields } => ClosureNode::FieldAccess {
            expr: Box::new(emit_from_rexpr(catalog, expr)),
            fields: fields.clone(),
        },

        RExprKind::Lambda { params, body } => ClosureNode::Lambda {
            arity: params.len(),
            body: Box::new(emit_from_rexpr(catalog, body)),
        },

        RExprKind::Apply { func, args } => ClosureNode::Apply {
            func: Box::new(emit_from_rexpr(catalog, func)),
            args: args.iter().map(|a| emit_from_rexpr(catalog, a)).collect(),
        },

        RExprKind::Let { value, body, .. } => ClosureNode::Let {
            value: Box::new(emit_from_rexpr(catalog, value)),
            body:  Box::new(emit_from_rexpr(catalog, body)),
        },

        RExprKind::Match { scrutinee, arms } => ClosureNode::Match {
            scrutinee: Box::new(emit_from_rexpr(catalog, scrutinee)),
            arms: arms.iter().map(|arm| emit_arm(catalog, arm)).collect(),
        },

        RExprKind::List(elems)  => ClosureNode::ListLit(elems.iter().map(|e| emit_from_rexpr(catalog, e)).collect()),
        RExprKind::Tuple(elems) => ClosureNode::TupleLit(elems.iter().map(|e| emit_from_rexpr(catalog, e)).collect()),

        RExprKind::Record(fields) => ClosureNode::RecordLit(
            fields.iter().map(|(k, v)| (k.clone(), emit_from_rexpr(catalog, v))).collect(),
        ),

        RExprKind::Constructor { name, args } => ClosureNode::Constructor {
            name: name.clone(),
            args: args.iter().map(|a| emit_from_rexpr(catalog, a)).collect(),
        },

        // A1: typed hole. Holes must never reach the evaluator — that
        // would mean the synthesis/fill stage was skipped. Emit a sentinel
        // ComposedRef with a zero hash and an `ERROR:Hole:<name>` label
        // (matching the existing unresolved-ref fallback). The evaluator
        // fails loudly with a clear name if the
        // node is executed; pure programs that never reach it still compile.
        RExprKind::Hole { name, .. } => ClosureNode::ComposedRef {
            hash: [0u8; 32],
            name: format!("ERROR:Hole:{}", name),
        },
    }
}

fn emit_catalog_ref(catalog: &FunctionCatalog, name: &str) -> ClosureNode {
    // `nothing` is a value, not a function — emit as literal so it survives
    // strict argument positions (e.g. inside `conditional`).
    if name == "nothing" || name == "maybe.nothing" {
        return ClosureNode::Literal(pfcl_types::Value::Nothing);
    }
    if let Some(entry) = catalog.get_by_name(name) {
        match &entry.kind {
            FunctionKind::Primitive(f)                  => ClosureNode::Primitive { func: *f, name: name.to_string() },
            FunctionKind::Accelerated { native_fn, .. } => ClosureNode::Primitive { func: *native_fn, name: name.to_string() },
            FunctionKind::Composed(_)                   => ClosureNode::ComposedRef { hash: entry.hash, name: name.to_string() },
            FunctionKind::Intrinsic(id)                 => ClosureNode::Intrinsic { id: *id, name: name.to_string() },
            FunctionKind::DynPrimitive(_)               => ClosureNode::ComposedRef { hash: entry.hash, name: name.to_string() },
        }
    } else {
        ClosureNode::ComposedRef { hash: [0u8; 32], name: name.to_string() }
    }
}

fn emit_arm(catalog: &FunctionCatalog, arm: &RMatchArm) -> ClosureArm {
    ClosureArm { pattern: emit_resolver_pattern(&arm.pattern), body: emit_from_rexpr(catalog, &arm.body) }
}

fn emit_resolver_pattern(pat: &resolver::Pattern) -> ClosurePattern {
    match &pat.kind {
        resolver::PatternKind::Wildcard              => ClosurePattern::Wildcard,
        resolver::PatternKind::Var(_)                => ClosurePattern::Var,
        resolver::PatternKind::Int(n)                => ClosurePattern::IntPat(*n),
        resolver::PatternKind::Str(s)                => ClosurePattern::StringPat(s.clone()),
        resolver::PatternKind::Bool(b)               => ClosurePattern::BoolPat(*b),
        resolver::PatternKind::Nothing               => ClosurePattern::NothingPat,
        resolver::PatternKind::Constructor(name, args) => ClosurePattern::Constructor {
            name: name.clone(),
            args: args.iter().map(emit_resolver_pattern).collect(),
        },
        resolver::PatternKind::Tuple(pats) => ClosurePattern::TuplePat(
            pats.iter().map(emit_resolver_pattern).collect(),
        ),
    }
}

// ── Full pipeline ─────────────────────────────────────────────────────────────

/// Compile a PFCL source string to an optimized ClosureNode.
///
/// Pipeline: source -> parse -> convert -> resolve -> emit_from_rexpr -> optimize
pub fn compile_source(catalog: &FunctionCatalog, source: &str) -> Result<ClosureNode, String> {
    // Strip typed body prefix: "T1 -> T2 : body" -> "body"
    let actual_source = strip_typed_prefix(source);

    // Stage 1: Parse
    let (parsed, parse_errors) = pfcl_parser::parse(&actual_source);
    if !parse_errors.is_empty() {
        let msgs: Vec<String> = parse_errors.iter().map(|e| format!("{}", e)).collect();
        return Err(format!("Parse errors: {}", msgs.join("; ")));
    }
    let parser_expr = parsed.ok_or("Parser produced no AST")?;

    // Stage 2: Convert parser AST → resolver input
    let resolver_input = convert_expr(&parser_expr);

    // Stage 3: Resolve names (de Bruijn indexing + catalog lookup)
    let lookup = CatalogLookupAdapter { catalog };
    let (resolved, resolve_errors) = resolver::resolve(&lookup, &resolver_input);
    if !resolve_errors.is_empty() {
        let msgs: Vec<String> = resolve_errors.iter().map(|e| format!("{:?}", e.kind)).collect();
        return Err(format!("Resolution errors: {}", msgs.join("; ")));
    }
    let rexpr = resolved.ok_or("Resolver produced no AST")?;

    // Stage 4: Emit ClosureNode
    let node = emit_from_rexpr(catalog, &rexpr);

    // Stage 5: Optimize (L2)
    Ok(optimize::optimize(node))
}

/// Strip the typed prefix `<sig> : ` from a PFCL body string.
/// Handles multi-word type expressions by finding the last ` : ` that
/// looks like a body separator (followed by `\(`).
fn strip_typed_prefix(source: &str) -> String {
    // Simple heuristic: find " : " and check if what follows looks like PFCL
    if let Some(pos) = source.find(" : ") {
        let after = source[pos + 3..].trim();
        // If after the colon we see a lambda, let, match, or literal — it's a body
        if after.starts_with('\\') || after.starts_with("let ")
            || after.starts_with("match ") || after.starts_with('[')
            || after.starts_with('{') || after.chars().next().map(|c| c.is_alphanumeric()).unwrap_or(false)
        {
            return after.to_string();
        }
    }
    source.to_string()
}
