use cord_trig::ir::{NodeId, TrigOp};
use crate::parser::{ExprParser, require_args};

impl<'a> ExprParser<'a> {
    pub(crate) fn parse_function_call(&mut self, name: &str) -> Result<NodeId, String> {
        self.expect(&crate::token::Token::LParen)?;
        let args = self.parse_arg_list()?;
        self.expect(&crate::token::Token::RParen)?;

        match name {
            "sin" => {
                require_args(name, &args, 1)?;
                Ok(self.graph.push(TrigOp::Sin(args[0])))
            }
            "cos" => {
                require_args(name, &args, 1)?;
                Ok(self.graph.push(TrigOp::Cos(args[0])))
            }
            "abs" => {
                require_args(name, &args, 1)?;
                Ok(self.graph.push(TrigOp::Abs(args[0])))
            }
            "tan" => {
                require_args(name, &args, 1)?;
                Ok(self.graph.push(TrigOp::Tan(args[0])))
            }
            "asin" | "arcsin" => {
                require_args(name, &args, 1)?;
                Ok(self.graph.push(TrigOp::Asin(args[0])))
            }
            "acos" | "arccos" | "arcos" => {
                require_args(name, &args, 1)?;
                Ok(self.graph.push(TrigOp::Acos(args[0])))
            }
            "atan" | "arctan" => {
                require_args(name, &args, 1)?;
                Ok(self.graph.push(TrigOp::Atan(args[0])))
            }
            "sinh" => {
                require_args(name, &args, 1)?;
                Ok(self.graph.push(TrigOp::Sinh(args[0])))
            }
            "cosh" => {
                require_args(name, &args, 1)?;
                Ok(self.graph.push(TrigOp::Cosh(args[0])))
            }
            "tanh" => {
                require_args(name, &args, 1)?;
                Ok(self.graph.push(TrigOp::Tanh(args[0])))
            }
            "asinh" | "arcsinh" => {
                require_args(name, &args, 1)?;
                Ok(self.graph.push(TrigOp::Asinh(args[0])))
            }
            "acosh" | "arccosh" | "arcosh" => {
                require_args(name, &args, 1)?;
                Ok(self.graph.push(TrigOp::Acosh(args[0])))
            }
            "atanh" | "arctanh" => {
                require_args(name, &args, 1)?;
                Ok(self.graph.push(TrigOp::Atanh(args[0])))
            }
            "sqrt" => {
                require_args(name, &args, 1)?;
                Ok(self.graph.push(TrigOp::Sqrt(args[0])))
            }
            "exp" => {
                require_args(name, &args, 1)?;
                Ok(self.graph.push(TrigOp::Exp(args[0])))
            }
            "ln" | "log" => {
                require_args(name, &args, 1)?;
                Ok(self.graph.push(TrigOp::Ln(args[0])))
            }
            "hypot" => {
                require_args(name, &args, 2)?;
                Ok(self.graph.push(TrigOp::Hypot(args[0], args[1])))
            }
            "atan2" => {
                require_args(name, &args, 2)?;
                Ok(self.graph.push(TrigOp::Atan2(args[0], args[1])))
            }
            "min" => {
                require_args(name, &args, 2)?;
                Ok(self.graph.push(TrigOp::Min(args[0], args[1])))
            }
            "max" => {
                require_args(name, &args, 2)?;
                Ok(self.graph.push(TrigOp::Max(args[0], args[1])))
            }
            "length" | "mag" => {
                match args.len() {
                    2 => Ok(self.graph.push(TrigOp::Hypot(args[0], args[1]))),
                    3 => {
                        let xy = self.graph.push(TrigOp::Hypot(args[0], args[1]));
                        Ok(self.graph.push(TrigOp::Hypot(xy, args[2])))
                    }
                    _ => Err(format!("{name}() requires 2 or 3 arguments")),
                }
            }
            "sphere" => {
                require_args(name, &args, 1)?;
                let ix = self.get_x();
                let iy = self.get_y();
                let iz = self.get_z();
                let xy = self.graph.push(TrigOp::Hypot(ix, iy));
                let mag = self.graph.push(TrigOp::Hypot(xy, iz));
                let r = self.graph.push(TrigOp::Sub(mag, args[0]));
                Ok(self.mark_obj(r))
            }
            "box" => {
                require_args(name, &args, 3)?;
                let ix = self.get_x();
                let iy = self.get_y();
                let iz = self.get_z();
                let zero = self.graph.push(TrigOp::Const(0.0));
                let ax = self.graph.push(TrigOp::Abs(ix));
                let ay = self.graph.push(TrigOp::Abs(iy));
                let az = self.graph.push(TrigOp::Abs(iz));
                let dx = self.graph.push(TrigOp::Sub(ax, args[0]));
                let dy = self.graph.push(TrigOp::Sub(ay, args[1]));
                let dz = self.graph.push(TrigOp::Sub(az, args[2]));
                let qx = self.graph.push(TrigOp::Max(dx, zero));
                let qy = self.graph.push(TrigOp::Max(dy, zero));
                let qz = self.graph.push(TrigOp::Max(dz, zero));
                let qxy = self.graph.push(TrigOp::Hypot(qx, qy));
                let q_len = self.graph.push(TrigOp::Hypot(qxy, qz));
                let m_yz = self.graph.push(TrigOp::Max(dy, dz));
                let m_xyz = self.graph.push(TrigOp::Max(dx, m_yz));
                let interior = self.graph.push(TrigOp::Min(m_xyz, zero));
                let r = self.graph.push(TrigOp::Add(q_len, interior));
                Ok(self.mark_obj(r))
            }
            "cylinder" => {
                require_args(name, &args, 2)?;
                let ix = self.get_x();
                let iy = self.get_y();
                let iz = self.get_z();
                let zero = self.graph.push(TrigOp::Const(0.0));
                let xy = self.graph.push(TrigOp::Hypot(ix, iy));
                let dr = self.graph.push(TrigOp::Sub(xy, args[0]));
                let az = self.graph.push(TrigOp::Abs(iz));
                let dz = self.graph.push(TrigOp::Sub(az, args[1]));
                let qr = self.graph.push(TrigOp::Max(dr, zero));
                let qz = self.graph.push(TrigOp::Max(dz, zero));
                let q_len = self.graph.push(TrigOp::Hypot(qr, qz));
                let m = self.graph.push(TrigOp::Max(dr, dz));
                let interior = self.graph.push(TrigOp::Min(m, zero));
                let r = self.graph.push(TrigOp::Add(q_len, interior));
                Ok(self.mark_obj(r))
            }
            "saw" => {
                require_args(name, &args, 1)?;
                self.build_saw(args[0])
            }
            "tri" => {
                require_args(name, &args, 1)?;
                self.build_tri(args[0])
            }
            "square" => {
                require_args(name, &args, 1)?;
                self.build_square(args[0])
            }
            "mix" | "lerp" => {
                require_args(name, &args, 3)?;
                self.build_mix(args[0], args[1], args[2])
            }
            "smoothstep" => {
                require_args(name, &args, 3)?;
                self.build_smoothstep(args[0], args[1], args[2])
            }
            "clip" | "clamp" => {
                require_args(name, &args, 3)?;
                Ok(self.graph.push(TrigOp::Clamp { val: args[0], lo: args[1], hi: args[2] }))
            }
            "quantize" => {
                require_args(name, &args, 2)?;
                self.build_quantize(args[0], args[1])
            }
            "am" => {
                require_args(name, &args, 3)?;
                self.build_am(args[0], args[1], args[2])
            }
            "fm" => {
                require_args(name, &args, 3)?;
                self.build_fm(args[0], args[1], args[2])
            }
            "lpf" => {
                require_args(name, &args, 2)?;
                self.build_lpf(args[0], args[1])
            }
            "hpf" => {
                require_args(name, &args, 2)?;
                self.build_hpf(args[0], args[1])
            }
            "bpf" => {
                require_args(name, &args, 3)?;
                self.build_bpf(args[0], args[1], args[2])
            }
            "dft" | "harmonics" => {
                require_args(name, &args, 2)?;
                self.build_dft(args[0], args[1])
            }
            "hilbert" | "envelope" => {
                require_args(name, &args, 1)?;
                let one = self.graph.push(TrigOp::Const(1.0));
                Ok(self.graph.push(TrigOp::Hypot(args[0], one)))
            }
            "phase" => {
                require_args(name, &args, 1)?;
                let one = self.graph.push(TrigOp::Const(1.0));
                Ok(self.graph.push(TrigOp::Atan2(args[0], one)))
            }
            "ngon" => {
                if args.is_empty() {
                    return Err("ngon(n, side): at least 2 arguments required".into());
                }
                let n = match self.graph.nodes.get(args[0] as usize) {
                    Some(TrigOp::Const(v)) if *v >= 3.0 && *v == (*v as u32 as f64) => *v as u32,
                    _ => return Err("ngon: first argument must be an integer >= 3".into()),
                };
                let r = self.parse_ngon(n, &args[1..])?;
                Ok(self.mark_obj(r))
            }
            "translate" | "mov" | "move" => {
                require_args(name, &args, 4)?;
                let x = self.get_x();
                let y = self.get_y();
                let z = self.get_z();
                let nx = self.graph.push(TrigOp::Sub(x, args[1]));
                let ny = self.graph.push(TrigOp::Sub(y, args[2]));
                let nz = self.graph.push(TrigOp::Sub(z, args[3]));
                let r = self.remap_inputs(args[0], nx, ny, nz);
                if self.is_obj_node(args[0]) { self.mark_obj(r); }
                Ok(r)
            }
            "rotate_x" | "rx" => {
                require_args(name, &args, 2)?;
                let x = self.get_x();
                let y = self.get_y();
                let z = self.get_z();
                let c = self.graph.push(TrigOp::Cos(args[1]));
                let s = self.graph.push(TrigOp::Sin(args[1]));
                let yc = self.graph.push(TrigOp::Mul(y, c));
                let zs = self.graph.push(TrigOp::Mul(z, s));
                let ny = self.graph.push(TrigOp::Add(yc, zs));
                let ys = self.graph.push(TrigOp::Mul(y, s));
                let zc = self.graph.push(TrigOp::Mul(z, c));
                let nz = self.graph.push(TrigOp::Sub(zc, ys));
                let r = self.remap_inputs(args[0], x, ny, nz);
                if self.is_obj_node(args[0]) { self.mark_obj(r); }
                Ok(r)
            }
            "rotate_y" | "ry" => {
                require_args(name, &args, 2)?;
                let x = self.get_x();
                let y = self.get_y();
                let z = self.get_z();
                let c = self.graph.push(TrigOp::Cos(args[1]));
                let s = self.graph.push(TrigOp::Sin(args[1]));
                let xc = self.graph.push(TrigOp::Mul(x, c));
                let zs = self.graph.push(TrigOp::Mul(z, s));
                let nx = self.graph.push(TrigOp::Sub(xc, zs));
                let xs = self.graph.push(TrigOp::Mul(x, s));
                let zc = self.graph.push(TrigOp::Mul(z, c));
                let nz = self.graph.push(TrigOp::Add(xs, zc));
                let r = self.remap_inputs(args[0], nx, y, nz);
                if self.is_obj_node(args[0]) { self.mark_obj(r); }
                Ok(r)
            }
            "rotate_z" | "rz" => {
                require_args(name, &args, 2)?;
                let x = self.get_x();
                let y = self.get_y();
                let z = self.get_z();
                let c = self.graph.push(TrigOp::Cos(args[1]));
                let s = self.graph.push(TrigOp::Sin(args[1]));
                let xc = self.graph.push(TrigOp::Mul(x, c));
                let ys = self.graph.push(TrigOp::Mul(y, s));
                let nx = self.graph.push(TrigOp::Add(xc, ys));
                let xs = self.graph.push(TrigOp::Mul(x, s));
                let yc = self.graph.push(TrigOp::Mul(y, c));
                let ny = self.graph.push(TrigOp::Sub(yc, xs));
                let r = self.remap_inputs(args[0], nx, ny, z);
                if self.is_obj_node(args[0]) { self.mark_obj(r); }
                Ok(r)
            }
            "scale" => {
                let is_obj_arg = self.is_obj_node(args[0]);
                match args.len() {
                    2 => {
                        let x = self.get_x();
                        let y = self.get_y();
                        let z = self.get_z();
                        let nx = self.graph.push(TrigOp::Div(x, args[1]));
                        let ny = self.graph.push(TrigOp::Div(y, args[1]));
                        let nz = self.graph.push(TrigOp::Div(z, args[1]));
                        let remapped = self.remap_inputs(args[0], nx, ny, nz);
                        let r = self.graph.push(TrigOp::Mul(remapped, args[1]));
                        if is_obj_arg { self.mark_obj(r); }
                        Ok(r)
                    }
                    4 => {
                        let x = self.get_x();
                        let y = self.get_y();
                        let z = self.get_z();
                        let nx = self.graph.push(TrigOp::Div(x, args[1]));
                        let ny = self.graph.push(TrigOp::Div(y, args[2]));
                        let nz = self.graph.push(TrigOp::Div(z, args[3]));
                        let remapped = self.remap_inputs(args[0], nx, ny, nz);
                        let ax = self.graph.push(TrigOp::Abs(args[1]));
                        let ay = self.graph.push(TrigOp::Abs(args[2]));
                        let az = self.graph.push(TrigOp::Abs(args[3]));
                        let mxy = self.graph.push(TrigOp::Min(ax, ay));
                        let min_s = self.graph.push(TrigOp::Min(mxy, az));
                        let r = self.graph.push(TrigOp::Mul(remapped, min_s));
                        if is_obj_arg { self.mark_obj(r); }
                        Ok(r)
                    }
                    _ => Err("scale() requires 2 or 4 arguments".into()),
                }
            }
            "mirror_x" | "mx" => {
                require_args(name, &args, 1)?;
                let x = self.get_x();
                let y = self.get_y();
                let z = self.get_z();
                let ax = self.graph.push(TrigOp::Abs(x));
                let r = self.remap_inputs(args[0], ax, y, z);
                if self.is_obj_node(args[0]) { self.mark_obj(r); }
                Ok(r)
            }
            "mirror_y" | "my" => {
                require_args(name, &args, 1)?;
                let x = self.get_x();
                let y = self.get_y();
                let z = self.get_z();
                let ay = self.graph.push(TrigOp::Abs(y));
                let r = self.remap_inputs(args[0], x, ay, z);
                if self.is_obj_node(args[0]) { self.mark_obj(r); }
                Ok(r)
            }
            "mirror_z" | "mz" => {
                require_args(name, &args, 1)?;
                let x = self.get_x();
                let y = self.get_y();
                let z = self.get_z();
                let az = self.graph.push(TrigOp::Abs(z));
                let r = self.remap_inputs(args[0], x, y, az);
                if self.is_obj_node(args[0]) { self.mark_obj(r); }
                Ok(r)
            }
            "cone" => {
                require_args(name, &args, 3)?;
                let ix = self.get_x();
                let iy = self.get_y();
                let iz = self.get_z();
                let xy = self.graph.push(TrigOp::Hypot(ix, iy));
                let half_c = self.graph.push(TrigOp::Const(0.5));
                let half_h = self.graph.push(TrigOp::Mul(args[2], half_c));
                let az = self.graph.push(TrigOp::Abs(iz));
                let dz = self.graph.push(TrigOp::Sub(az, half_h));
                let one_c = self.graph.push(TrigOp::Const(1.0));
                let inv_h = self.graph.push(TrigOp::Div(one_c, args[2]));
                let t_raw = self.graph.push(TrigOp::Mul(iz, inv_h));
                let half = self.graph.push(TrigOp::Const(0.5));
                let t = self.graph.push(TrigOp::Add(t_raw, half));
                let zero = self.graph.push(TrigOp::Const(0.0));
                let one = self.graph.push(TrigOp::Const(1.0));
                let tc = self.graph.push(TrigOp::Clamp { val: t, lo: zero, hi: one });
                let dr = self.graph.push(TrigOp::Sub(args[1], args[0]));
                let r_off = self.graph.push(TrigOp::Mul(tc, dr));
                let r_at_z = self.graph.push(TrigOp::Add(args[0], r_off));
                let d_radial = self.graph.push(TrigOp::Sub(xy, r_at_z));
                let qr = self.graph.push(TrigOp::Max(d_radial, zero));
                let qz = self.graph.push(TrigOp::Max(dz, zero));
                let q_len = self.graph.push(TrigOp::Hypot(qr, qz));
                let m = self.graph.push(TrigOp::Max(d_radial, dz));
                let interior = self.graph.push(TrigOp::Min(m, zero));
                let r = self.graph.push(TrigOp::Add(q_len, interior));
                Ok(self.mark_obj(r))
            }
            "union" => {
                require_args(name, &args, 2)?;
                let r = self.graph.push(TrigOp::Min(args[0], args[1]));
                if self.is_obj_node(args[0]) || self.is_obj_node(args[1]) { self.mark_obj(r); }
                Ok(r)
            }
            "intersect" => {
                require_args(name, &args, 2)?;
                let r = self.graph.push(TrigOp::Max(args[0], args[1]));
                if self.is_obj_node(args[0]) || self.is_obj_node(args[1]) { self.mark_obj(r); }
                Ok(r)
            }
            "smooth_union" | "smin" => {
                require_args(name, &args, 3)?;
                let a = args[0];
                let b = args[1];
                let k = args[2];
                let half = self.graph.push(TrigOp::Const(0.5));
                let one = self.graph.push(TrigOp::Const(1.0));
                let zero = self.graph.push(TrigOp::Const(0.0));
                let diff = self.graph.push(TrigOp::Sub(b, a));
                let div = self.graph.push(TrigOp::Div(diff, k));
                let scaled = self.graph.push(TrigOp::Mul(div, half));
                let shifted = self.graph.push(TrigOp::Add(half, scaled));
                let h = self.graph.push(TrigOp::Clamp { val: shifted, lo: zero, hi: one });
                let one_minus_h = self.graph.push(TrigOp::Sub(one, h));
                let term_b = self.graph.push(TrigOp::Mul(b, one_minus_h));
                let term_a = self.graph.push(TrigOp::Mul(a, h));
                let mixed = self.graph.push(TrigOp::Add(term_b, term_a));
                let kh = self.graph.push(TrigOp::Mul(k, h));
                let correction = self.graph.push(TrigOp::Mul(kh, one_minus_h));
                let r = self.graph.push(TrigOp::Sub(mixed, correction));
                if self.is_obj_node(a) || self.is_obj_node(b) { self.mark_obj(r); }
                Ok(r)
            }
            "diff" | "subtract" => {
                require_args(name, &args, 2)?;
                let neg_b = self.graph.push(TrigOp::Neg(args[1]));
                let r = self.graph.push(TrigOp::Max(args[0], neg_b));
                if self.is_obj_node(args[0]) || self.is_obj_node(args[1]) { self.mark_obj(r); }
                Ok(r)
            }
            "offset" => {
                require_args(name, &args, 2)?;
                let r = self.graph.push(TrigOp::Sub(args[0], args[1]));
                if self.is_obj_node(args[0]) { self.mark_obj(r); }
                Ok(r)
            }
            _ => {
                if name.ends_with("gon") && name.len() > 3 {
                    if let Ok(n) = name[..name.len() - 3].parse::<u32>() {
                        let r = self.parse_ngon(n, &args)?;
                        return Ok(self.mark_obj(r));
                    }
                }
                if let Some(func) = self.funcs.get(name) {
                    let params = func.params.clone();
                    let defaults = func.defaults.clone();
                    let body = func.body.clone();
                    let r = self.call_user_func_inner(params, defaults, body, &args, name)?;
                    if self.is_obj_node(r) { self.mark_obj(r); }
                    return Ok(r);
                }
                if let Some(sch) = self.schematics.get(name) {
                    let params = sch.params.clone();
                    let defaults = sch.defaults.clone();
                    let body = sch.body.clone();
                    let vr = sch.value_returning;
                    let r = self.call_schematic(params, defaults, body, vr, &args, name)?;
                    if self.is_obj_node(r) { self.mark_obj(r); }
                    return Ok(r);
                }
                Err(self.err_at(format!("unknown function: {name}()")))
            }
        }
    }
}
