//! `email::` Rhai bridge — outbound email (v1.1.7).
//!
//! ```rhai
//! email::send(#{
//!     to: "alice@example.com",   // String or Array of String
//!     from: "alerts@myapp.com",
//!     subject: "Build complete",
//!     text: "Your deploy finished."
//! });
//!
//! email::send_html(#{
//!     to: ["alice@x.com", "bob@y.com"],
//!     cc: ["dave@z.com"],
//!     bcc: ["audit@myapp.com"],
//!     from: "alerts@myapp.com",
//!     reply_to: "support@myapp.com",   // optional; defaults to `from`
//!     subject: "Build complete",
//!     text: "Your deploy finished.",   // plain-text fallback
//!     html: "<p>Your deploy <b>finished</b>.</p>"
//! });
//! ```
//!
//! Both map onto `EmailService::send`. `email::send` forces a text-only
//! message (any `html` key is ignored); `email::send_html` requires an
//! `html` part. `app_id` is derived from `cx.app_id` in the service.

use std::sync::Arc;

use super::bridge::{block_on, runtime_err};
use picloud_shared::{OutboundEmail, SdkCallCx, Services};
use rhai::{Array, Engine as RhaiEngine, EvalAltResult, Map, Module};

pub(super) fn register(engine: &mut RhaiEngine, services: &Services, cx: Arc<SdkCallCx>) {
    let svc = services.email.clone();
    let mut module = Module::new();

    // email::send(#{...}) — plain text (html ignored).
    {
        let svc = svc.clone();
        let cx = cx.clone();
        module.set_native_fn("send", move |opts: Map| -> Result<(), Box<EvalAltResult>> {
            let mut email = parse_email(&opts)?;
            email.html = None; // text-only path
            let svc = svc.clone();
            let cx = cx.clone();
            block_on("email", async move { svc.send(&cx, email).await })
        });
    }

    // email::send_html(#{...}) — multipart text + html (html required).
    {
        let svc = svc.clone();
        let cx = cx.clone();
        module.set_native_fn(
            "send_html",
            move |opts: Map| -> Result<(), Box<EvalAltResult>> {
                let email = parse_email(&opts)?;
                if email.html.as_ref().is_none_or(String::is_empty) {
                    return Err(runtime_err(
                        "email::send_html: an 'html' field is required (use email::send for text-only)",
                    ));
                }
                let svc = svc.clone();
                let cx = cx.clone();
                block_on("email", async move { svc.send(&cx, email).await })
            },
        );
    }

    engine.register_static_module("email", module.into());
}

/// Parse the Rhai options map into an [`OutboundEmail`]. Field-level
/// validation (required fields, address shape) happens in the service;
/// here we only do type coercion (String/Array → Vec<String>).
fn parse_email(opts: &Map) -> Result<OutboundEmail, Box<EvalAltResult>> {
    Ok(OutboundEmail {
        to: addresses(opts, "to")?,
        cc: addresses(opts, "cc")?,
        bcc: addresses(opts, "bcc")?,
        from: string_field(opts, "from").unwrap_or_default(),
        reply_to: string_field(opts, "reply_to"),
        subject: string_field(opts, "subject").unwrap_or_default(),
        text: string_field(opts, "text"),
        html: string_field(opts, "html"),
    })
}

/// Read a string field. Missing or `()` → `None`.
fn string_field(opts: &Map, key: &str) -> Option<String> {
    match opts.get(key) {
        None => None,
        Some(d) if d.is_unit() => None,
        Some(d) if d.is_string() => Some(d.clone().into_string().unwrap_or_default()),
        // Coerce non-string scalars via display (numbers, etc.).
        Some(d) => Some(d.to_string()),
    }
}

/// Read an address list: a String becomes a one-element list; an Array
/// of Strings becomes a list; missing/`()` is empty.
fn addresses(opts: &Map, key: &str) -> Result<Vec<String>, Box<EvalAltResult>> {
    match opts.get(key) {
        None => Ok(Vec::new()),
        Some(d) if d.is_unit() => Ok(Vec::new()),
        Some(d) if d.is_string() => Ok(vec![d.clone().into_string().unwrap_or_default()]),
        Some(d) => {
            if let Some(arr) = d.clone().try_cast::<Array>() {
                let mut out = Vec::with_capacity(arr.len());
                for el in arr {
                    if !el.is_string() {
                        return Err(runtime_err(&format!(
                            "email: '{key}' array must contain only strings"
                        )));
                    }
                    out.push(el.into_string().unwrap_or_default());
                }
                Ok(out)
            } else {
                Err(runtime_err(&format!(
                    "email: '{key}' must be a string or an array of strings"
                )))
            }
        }
    }
}
