//! **icore** — JSON interchange for [`crate::core_ir::UnifiedModule`].
//!
//! File extension **`.icore`** (or `#!in parser=icore`) loads a versioned JSON document so any
//! front (codegen, tree-sitter bridge, another compiler) can feed the same Core IR + SIL path
//! without re-implementing the `.in` lexer.

use crate::boundary_ir::{BoundaryModule, CompileArtifact};
use crate::boundary_verify::boundary_ir_verify;
use crate::core_ir::{Decl, Typ, UnifiedModule};
use crate::core_ir::{Expr, Stmt};
use serde::Deserialize;
use serde_json::{Map, Value};
use std::collections::HashSet;
use std::fs;
use std::path::Path;

#[derive(Debug, Deserialize)]
struct IcoreFile {
    #[serde(default, rename = "icoreVersion")]
    icore_version: u32,
    decls: Vec<IcoreDecl>,
    #[serde(default)]
    boundary: Option<BoundaryModule>,
}

#[derive(Debug, Deserialize)]
#[serde(tag = "kind", rename_all = "lowercase")]
enum IcoreDecl {
    Struct {
        name: String,
        fields: Vec<IcoreField>,
    },
    Function {
        name: String,
        params: Vec<IcoreParam>,
        #[serde(rename = "return")]
        ret: String,
        #[serde(default)]
        body: Vec<serde_json::Value>,
    },
}

#[derive(Debug, Deserialize)]
struct IcoreField {
    name: String,
    #[serde(rename = "type")]
    ty: String,
}

#[derive(Debug, Deserialize)]
struct IcoreParam {
    name: String,
    #[serde(rename = "type")]
    ty: String,
}

fn parse_typ(s: &str) -> Typ {
    let s = s.trim();
    if s.eq_ignore_ascii_case("void") {
        return Typ::Void;
    }
    match s {
        "Int" => Typ::Int,
        "String" => Typ::String,
        "Bool" => Typ::Bool,
        "Void" => Typ::Void,
        other => Typ::Named(other.to_string()),
    }
}

/// Parse `.icore` JSON into [`UnifiedModule`]. Version **1** supports only **empty** function bodies
/// (`body` must be `[]`); statements are accepted from `.in` or future versions.
pub fn parse_icore_file(path: &Path) -> Result<UnifiedModule, String> {
    parse_icore_artifact(path).map(|artifact| artifact.semantic)
}

pub fn parse_icore_artifact(path: &Path) -> Result<CompileArtifact, String> {
    let raw = fs::read_to_string(path).map_err(|e| format!("{}: {e}", path.display()))?;
    parse_icore_artifact_source(&raw)
}

pub fn parse_icore_artifact_source(raw: &str) -> Result<CompileArtifact, String> {
    let file: IcoreFile = serde_json::from_str(raw).map_err(|e| format!("icore JSON: {e}"))?;
    let boundary = file.boundary.map(|mut b| {
        if b.layout_hash.is_empty() {
            b = b.with_layout_hash();
        }
        b
    });
    if let Some(ref module) = boundary {
        let report = boundary_ir_verify(module);
        if !report.ok {
            return Err(format!("icore boundary: {}", report.diagnostics.join("; ")));
        }
    }
    let boundary_layout_names = boundary
        .as_ref()
        .map(|module| {
            module
                .layouts
                .iter()
                .map(|layout| layout.name.as_str())
                .collect::<HashSet<_>>()
        })
        .unwrap_or_default();
    let semantic = parse_icore_decls(
        file.icore_version,
        file.decls,
        boundary.is_some(),
        &boundary_layout_names,
    )?;
    Ok(if let Some(boundary) = boundary {
        CompileArtifact::with_boundary(semantic, boundary)
    } else {
        CompileArtifact::from_semantic(semantic)
    })
}

/// Parse JSON (for tests and tooling that already hold the string).
pub fn parse_icore_source(raw: &str) -> Result<UnifiedModule, String> {
    parse_icore_artifact_source(raw).map(|artifact| artifact.semantic)
}

fn parse_icore_decls(
    icore_version: u32,
    icore_decls: Vec<IcoreDecl>,
    has_boundary: bool,
    boundary_layout_names: &HashSet<&str>,
) -> Result<UnifiedModule, String> {
    if !matches!(icore_version, 1..=3) {
        return Err(format!(
            "icore: unsupported icoreVersion {icore_version} (only 1, 2, and 3 supported)"
        ));
    }

    let mut decls = Vec::new();
    for d in icore_decls {
        match d {
            IcoreDecl::Struct { name, fields } => {
                let flds: Vec<(String, Typ)> = fields
                    .into_iter()
                    .map(|f| (f.name, parse_typ(&f.ty)))
                    .collect();
                decls.push(Decl::Struct {
                    name,
                    fields: flds,
                    type_params: vec![],
                });
            }
            IcoreDecl::Function {
                name,
                params,
                ret,
                body,
            } => {
                let body = if icore_version == 1 {
                    if !body.is_empty() {
                        return Err(format!(
                            "icore: function `{name}` has non-empty body (v1 only supports body: [])"
                        ));
                    }
                    Vec::new()
                } else {
                    parse_v2_body(&name, body)?
                };
                let params: Vec<(String, Typ)> = params
                    .into_iter()
                    .map(|p| (p.name, parse_typ(&p.ty)))
                    .collect();
                decls.push(Decl::Function {
                    name,
                    params,
                    ret: parse_typ(&ret),
                    body,
                    type_params: vec![],
                });
            }
        }
    }

    let module = UnifiedModule::new(decls);
    validate_module(&module, icore_version, has_boundary, boundary_layout_names)?;
    Ok(module)
}

fn parse_v2_body(function_name: &str, body: Vec<Value>) -> Result<Vec<Stmt>, String> {
    body.into_iter()
        .enumerate()
        .map(|(index, stmt)| {
            let context = format!("icore v2 function `{function_name}` statement {index}");
            parse_v2_stmt(&stmt, &context)
        })
        .collect()
}

fn parse_v2_stmt(value: &Value, context: &str) -> Result<Stmt, String> {
    let object = value_object(value, context)?;
    let kind = string_field(object, "kind", context)?;
    match kind {
        "return" => parse_optional_expr_field(object, context).map(Stmt::Return),
        "assign" => {
            let target = one_of_string_fields(object, "target", "name", context)?;
            let value = required_field(object, "value", context)?;
            Ok(Stmt::Assign(
                target.to_string(),
                parse_v2_expr(value, context)?,
            ))
        }
        "let" => {
            let name = string_field(object, "name", context)?;
            let typ = object.get("type").and_then(Value::as_str).map(parse_typ);
            let value = required_field(object, "value", context)?;
            Ok(Stmt::Let(
                name.to_string(),
                typ,
                parse_v2_expr(value, context)?,
            ))
        }
        "expr" | "expression" => {
            let value = expr_or_value_field(object, context)?;
            Ok(Stmt::Expr(parse_v2_expr(value, context)?))
        }
        "call" => Ok(Stmt::Expr(parse_v2_expr(value, context)?)),
        "if" => parse_if_stmt(object, context),
        other => Err(format!("{context}: unsupported statement kind `{other}`")),
    }
}

fn parse_if_stmt(object: &Map<String, Value>, context: &str) -> Result<Stmt, String> {
    let cond = required_field(object, "cond", context)?;
    let then_body = object
        .get("then_body")
        .and_then(|v| v.as_array())
        .ok_or_else(|| format!("{context}: `if` needs `then_body` array"))?;
    let else_body = object
        .get("else_body")
        .and_then(|v| v.as_array())
        .cloned()
        .unwrap_or_default();
    let then_stmts: Vec<Stmt> = then_body
        .iter()
        .enumerate()
        .map(|(idx, s)| {
            let ctx = format!("{context}.then_body[{idx}]");
            parse_v2_stmt(s, &ctx)
        })
        .collect::<Result<Vec<_>, _>>()?;
    let else_stmts: Vec<Stmt> = else_body
        .iter()
        .enumerate()
        .map(|(idx, s)| {
            let ctx = format!("{context}.else_body[{idx}]");
            parse_v2_stmt(s, &ctx)
        })
        .collect::<Result<Vec<_>, _>>()?;
    Ok(Stmt::If {
        cond: parse_v2_expr(cond, context)?,
        then_body: then_stmts,
        else_body: else_stmts,
    })
}

fn parse_v2_expr(value: &Value, context: &str) -> Result<Expr, String> {
    if let Some(value) = value.as_i64() {
        return Ok(Expr::IntLit(value));
    }
    if let Some(value) = value.as_bool() {
        return Ok(Expr::BoolLit(value));
    }
    if let Some(value) = value.as_str() {
        return Ok(Expr::StringLit(value.to_string()));
    }
    let object = value_object(value, context)?;
    let kind = string_field(object, "kind", context)?;
    match kind {
        "int" => required_field(object, "value", context)?
            .as_i64()
            .map(Expr::IntLit)
            .ok_or_else(|| format!("{context}: int literal `value` must be an integer")),
        "string" => required_field(object, "value", context)?
            .as_str()
            .map(|value| Expr::StringLit(value.to_string()))
            .ok_or_else(|| format!("{context}: string literal `value` must be a string")),
        "bool" => required_field(object, "value", context)?
            .as_bool()
            .map(Expr::BoolLit)
            .ok_or_else(|| format!("{context}: bool literal `value` must be a boolean")),
        "ident" | "identifier" => {
            let name = string_field(object, "name", context)?;
            Ok(Expr::Ident(name.to_string()))
        }
        "binary" | "binop" => {
            let op = string_field(object, "op", context)?;
            if !is_supported_binary_op(op) {
                return Err(format!("{context}: unsupported binary operator `{op}`"));
            }
            let lhs = parse_v2_expr(required_field(object, "lhs", context)?, context)?;
            let rhs = parse_v2_expr(required_field(object, "rhs", context)?, context)?;
            Ok(Expr::Binary {
                op: op.to_string(),
                lhs: Box::new(lhs),
                rhs: Box::new(rhs),
            })
        }
        "call" => {
            let callee = parse_callee(required_field(object, "callee", context)?, context)?;
            let args = match object.get("args") {
                Some(value) => value
                    .as_array()
                    .ok_or_else(|| format!("{context}: call `args` must be an array"))?
                    .iter()
                    .map(|arg| parse_v2_expr(arg, context))
                    .collect::<Result<Vec<_>, _>>()?,
                None => Vec::new(),
            };
            Ok(Expr::Call {
                callee: Box::new(callee),
                args,
            })
        }
        other => Err(format!("{context}: unsupported expression kind `{other}`")),
    }
}

fn parse_callee(value: &Value, context: &str) -> Result<Expr, String> {
    if let Some(name) = value.as_str() {
        return Ok(Expr::Ident(name.to_string()));
    }
    parse_v2_expr(value, context)
}

fn is_supported_binary_op(op: &str) -> bool {
    matches!(
        op,
        "+" | "-" | "*" | "/" | "%" | "==" | "!=" | "<" | ">" | "<=" | ">=" | "&&" | "||"
    )
}

fn value_object<'a>(value: &'a Value, context: &str) -> Result<&'a Map<String, Value>, String> {
    value
        .as_object()
        .ok_or_else(|| format!("{context}: expected object"))
}

fn required_field<'a>(
    object: &'a Map<String, Value>,
    field: &str,
    context: &str,
) -> Result<&'a Value, String> {
    object
        .get(field)
        .ok_or_else(|| format!("{context}: missing `{field}`"))
}

fn string_field<'a>(
    object: &'a Map<String, Value>,
    field: &str,
    context: &str,
) -> Result<&'a str, String> {
    required_field(object, field, context)?
        .as_str()
        .ok_or_else(|| format!("{context}: `{field}` must be a string"))
}

fn one_of_string_fields<'a>(
    object: &'a Map<String, Value>,
    left: &str,
    right: &str,
    context: &str,
) -> Result<&'a str, String> {
    match (object.get(left), object.get(right)) {
        (Some(_), Some(_)) => Err(format!("{context}: use `{left}` or `{right}`, not both")),
        (Some(value), None) | (None, Some(value)) => value
            .as_str()
            .ok_or_else(|| format!("{context}: `{left}`/`{right}` must be a string")),
        (None, None) => Err(format!("{context}: missing `{left}` or `{right}`")),
    }
}

fn expr_or_value_field<'a>(
    object: &'a Map<String, Value>,
    context: &str,
) -> Result<&'a Value, String> {
    match (object.get("expr"), object.get("value")) {
        (Some(_), Some(_)) => Err(format!("{context}: use `expr` or `value`, not both")),
        (Some(value), None) | (None, Some(value)) => Ok(value),
        (None, None) => Err(format!("{context}: missing `expr` or `value`")),
    }
}

fn parse_optional_expr_field(
    object: &Map<String, Value>,
    context: &str,
) -> Result<Option<Expr>, String> {
    match (object.get("expr"), object.get("value")) {
        (Some(_), Some(_)) => Err(format!("{context}: use `expr` or `value`, not both")),
        (Some(value), None) | (None, Some(value)) => Ok(Some(parse_v2_expr(value, context)?)),
        (None, None) => Ok(None),
    }
}

fn type_known_with_boundary(
    struct_names: &HashSet<&str>,
    boundary_layout_names: &HashSet<&str>,
    t: &Typ,
) -> bool {
    match t {
        Typ::Named(name) => {
            struct_names.contains(name.as_str()) || boundary_layout_names.contains(name.as_str())
        }
        Typ::Array(item) => type_known_with_boundary(struct_names, boundary_layout_names, item),
        Typ::Int | Typ::Float | Typ::String | Typ::Bool | Typ::Void => true,
        Typ::Generic(_) => false,
    }
}

fn validate_module(
    module: &UnifiedModule,
    icore_version: u32,
    has_boundary: bool,
    boundary_layout_names: &HashSet<&str>,
) -> Result<(), String> {
    if module.decls.is_empty() {
        return Err("icore: decls is empty".into());
    }
    let mut names = Vec::new();
    for d in &module.decls {
        match d {
            Decl::Struct { name, .. } | Decl::Function { name, .. } => names.push(name.clone()),
            Decl::Class { .. } | Decl::Interface { .. } | Decl::Component { .. } => {}
            Decl::Global { .. } => {}
        }
    }
    let mut seen = HashSet::new();
    for n in &names {
        if !seen.insert(n.clone()) {
            return Err(format!("icore: duplicate top-level name `{n}`"));
        }
    }
    let requires_main = !(icore_version == 3 && has_boundary);
    let has_main = module
        .decls
        .iter()
        .any(|d| matches!(d, Decl::Function { name, .. } if name == "main"));
    if requires_main && !has_main {
        return Err("icore: missing required function `main`".into());
    }

    let struct_names: HashSet<&str> = module
        .decls
        .iter()
        .filter_map(|d| match d {
            Decl::Struct { name, .. } => Some(name.as_str()),
            _ => None,
        })
        .collect();

    for d in &module.decls {
        match d {
            Decl::Struct { name, fields, .. } => {
                for (field, ty) in fields {
                    if !type_known_with_boundary(&struct_names, boundary_layout_names, ty) {
                        return Err(format!(
                            "icore: unknown type in struct `{name}` field `{field}`"
                        ));
                    }
                }
            }
            Decl::Function {
                name, params, ret, ..
            } => {
                for (param, ty) in params {
                    if !type_known_with_boundary(&struct_names, boundary_layout_names, ty) {
                        return Err(format!(
                            "icore: unknown type in function `{name}` parameter `{param}`"
                        ));
                    }
                }
                if !type_known_with_boundary(&struct_names, boundary_layout_names, ret) {
                    return Err(format!("icore: unknown return type in function `{name}`"));
                }
            }
            Decl::Class { .. } | Decl::Interface { .. } | Decl::Component { .. } => {}
            Decl::Global { .. } => {}
        }
    }
    Ok(())
}

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

    #[test]
    fn parses_minimal_icore() {
        let j = r#"{
            "icoreVersion": 1,
            "decls": [
                { "kind": "struct", "name": "S", "fields": [{ "name": "x", "type": "Int" }] },
                { "kind": "function", "name": "main", "params": [], "return": "Void", "body": [] }
            ]
        }"#;
        let m = parse_icore_source(j).expect("ok");
        assert_eq!(m.decls.len(), 2);
    }

    #[test]
    fn rejects_nonempty_body_v1() {
        let j = r#"{
            "icoreVersion": 1,
            "decls": [
                { "kind": "function", "name": "main", "params": [], "return": "Void", "body": [1] }
            ]
        }"#;
        let err = parse_icore_source(j).expect_err("v1 body must be rejected");
        assert!(err.contains("v1 only supports body: []"), "{err}");
    }

    #[test]
    fn parses_v2_function_bodies() {
        let j = r#"{
            "icoreVersion": 2,
            "decls": [
                {
                    "kind": "function",
                    "name": "helper",
                    "params": [],
                    "return": "Int",
                    "body": [
                        { "kind": "return", "expr": { "kind": "int", "value": 7 } }
                    ]
                },
                {
                    "kind": "function",
                    "name": "main",
                    "params": [],
                    "return": "Void",
                    "body": [
                        { "kind": "let", "name": "seed", "type": "Int", "value": 0 },
                        {
                            "kind": "assign",
                            "target": "value",
                            "value": {
                                "kind": "call",
                                "callee": "helper",
                                "args": []
                            }
                        },
                        {
                            "kind": "call",
                            "callee": { "kind": "ident", "name": "helper" },
                            "args": [
                                1,
                                "ok",
                                true
                            ]
                        },
                        { "kind": "return" }
                    ]
                }
            ]
        }"#;
        let m = parse_icore_source(j).expect("ok");
        let helper_body = m
            .decls
            .iter()
            .find_map(|d| match d {
                Decl::Function { name, body, .. } if name == "helper" => Some(body),
                _ => None,
            })
            .expect("helper body");
        assert_eq!(helper_body, &vec![Stmt::Return(Some(Expr::IntLit(7)))]);

        let main_body = m
            .decls
            .iter()
            .find_map(|d| match d {
                Decl::Function { name, body, .. } if name == "main" => Some(body),
                _ => None,
            })
            .expect("main body");
        assert_eq!(
            main_body,
            &vec![
                Stmt::Let("seed".into(), Some(Typ::Int), Expr::IntLit(0)),
                Stmt::Assign(
                    "value".into(),
                    Expr::Call {
                        callee: Box::new(Expr::Ident("helper".into())),
                        args: vec![],
                    },
                ),
                Stmt::Expr(Expr::Call {
                    callee: Box::new(Expr::Ident("helper".into())),
                    args: vec![
                        Expr::IntLit(1),
                        Expr::StringLit("ok".into()),
                        Expr::BoolLit(true),
                    ],
                }),
                Stmt::Return(None),
            ]
        );
    }

    #[test]
    fn parses_v2_modulo_binary_expression() {
        let j = r#"{
            "icoreVersion": 2,
            "decls": [
                {
                    "kind": "function",
                    "name": "main",
                    "params": [],
                    "return": "Int",
                    "body": [
                        {
                            "kind": "return",
                            "expr": {
                                "kind": "binary",
                                "op": "%",
                                "lhs": { "kind": "int", "value": 7 },
                                "rhs": { "kind": "int", "value": 4 }
                            }
                        }
                    ]
                }
            ]
        }"#;
        let m = parse_icore_source(j).expect("ok");
        let body = m
            .decls
            .iter()
            .find_map(|d| match d {
                Decl::Function { name, body, .. } if name == "main" => Some(body),
                _ => None,
            })
            .expect("main body");
        assert_eq!(
            body,
            &vec![Stmt::Return(Some(Expr::Binary {
                op: "%".into(),
                lhs: Box::new(Expr::IntLit(7)),
                rhs: Box::new(Expr::IntLit(4)),
            }))]
        );

        let sil = crate::lower_core::lower_to_textual_sil(&m, "App");

        assert!(sil.contains("integer_literal $Builtin.Int64, 3"));
        assert!(!sil.contains("builtin_binop"));
    }

    #[test]
    fn rejects_v2_unknown_binary_operator() {
        let j = r#"{
            "icoreVersion": 2,
            "decls": [
                {
                    "kind": "function",
                    "name": "main",
                    "params": [],
                    "return": "Int",
                    "body": [
                        {
                            "kind": "return",
                            "expr": {
                                "kind": "binary",
                                "op": "%%",
                                "lhs": 7,
                                "rhs": 4
                            }
                        }
                    ]
                }
            ]
        }"#;
        let err = parse_icore_source(j).expect_err("unknown binary operator must be rejected");
        assert!(err.contains("unsupported binary operator `%%`"), "{err}");
    }

    #[test]
    fn parses_if_else_statement() {
        let j = r#"{
            "icoreVersion": 2,
            "decls": [
                {
                    "kind": "function",
                    "name": "check",
                    "params": [],
                    "return": "Int",
                    "body": [
                        {
                            "kind": "if",
                            "cond": { "kind": "bool", "value": true },
                            "then_body": [
                                { "kind": "return", "expr": { "kind": "int", "value": 1 } }
                            ],
                            "else_body": [
                                { "kind": "return", "expr": { "kind": "int", "value": 0 } }
                            ]
                        }
                    ]
                },
                {
                    "kind": "function",
                    "name": "main",
                    "params": [],
                    "return": "Void",
                    "body": []
                }
            ]
        }"#;
        let m = parse_icore_source(j).expect("ok");
        let body = m
            .decls
            .iter()
            .find_map(|d| match d {
                Decl::Function { name, body, .. } if name == "check" => Some(body),
                _ => None,
            })
            .expect("check body");
        assert_eq!(
            body,
            &vec![Stmt::If {
                cond: Expr::BoolLit(true),
                then_body: vec![Stmt::Return(Some(Expr::IntLit(1)))],
                else_body: vec![Stmt::Return(Some(Expr::IntLit(0)))],
            }]
        );
    }

    #[test]
    fn parses_if_without_else() {
        let j = r#"{
            "icoreVersion": 2,
            "decls": [
                {
                    "kind": "function",
                    "name": "maybe",
                    "params": [],
                    "return": "Int",
                    "body": [
                        {
                            "kind": "if",
                            "cond": { "kind": "bool", "value": false },
                            "then_body": [
                                { "kind": "return", "expr": { "kind": "int", "value": 7 } }
                            ]
                        }
                    ]
                },
                {
                    "kind": "function",
                    "name": "main",
                    "params": [],
                    "return": "Void",
                    "body": []
                }
            ]
        }"#;
        let m = parse_icore_source(j).expect("ok");
        let body = m
            .decls
            .iter()
            .find_map(|d| match d {
                Decl::Function { name, body, .. } if name == "maybe" => Some(body),
                _ => None,
            })
            .expect("maybe body");
        assert_eq!(
            body,
            &vec![Stmt::If {
                cond: Expr::BoolLit(false),
                then_body: vec![Stmt::Return(Some(Expr::IntLit(7)))],
                else_body: vec![],
            }]
        );
    }

    #[test]
    fn parses_v3_boundary_without_main() {
        let j = r#"{
            "icoreVersion": 3,
            "decls": [
                {
                    "kind": "function",
                    "name": "person_age",
                    "params": [{"name": "p", "type": "Person"}],
                    "return": "Int",
                    "body": [
                        {"kind": "return", "value": {"kind": "int", "value": 0}}
                    ]
                }
            ],
            "boundary": {
                "abi_version": 1,
                "module": "sample.person",
                "layouts": [
                    {
                        "name": "Person",
                        "kind": "struct",
                        "repr": "c",
                        "size": 24,
                        "align": 8,
                        "stride": 24,
                        "fields": [
                            {"name": "name", "offset": 0, "type": "InSliceU8", "transfer": "borrow"},
                            {"name": "age", "offset": 16, "type": "u32", "transfer": "copy"}
                        ]
                    }
                ],
                "symbols": [
                    {
                        "name": "person_new",
                        "signature_hash": "person_new_v1",
                        "ownership": "returns-owned-handle",
                        "calling_convention": "c"
                    }
                ],
                "allocators": [
                    {"id": 1, "kind": "host_arena", "free_with": "host"}
                ]
            }
        }"#;
        let artifact = parse_icore_artifact_source(j).expect("v3 boundary ingest");
        assert!(artifact.boundary.is_some());
        assert!(
            artifact
                .semantic
                .decls
                .iter()
                .any(|d| matches!(d, Decl::Function { name, .. } if name == "person_age"))
        );
    }
}
