//! Render: walks compiled node tree, appends to caller-owned buffer.

use seedfaker_core::ctx::Identity;
use seedfaker_core::field::{RangeSpec, Transform};
use seedfaker_core::locale::Locale;
use seedfaker_core::rng::Rng;

use super::compile::{CmpOp, CompiledTemplate, Node};

pub struct RenderCtx<'a> {
    pub values: &'a [String],
    pub rng: &'a mut Rng,
    pub locales: &'a [&'a Locale],
    pub identity: Option<&'a Identity>,
    pub tz_offset_minutes: i32,
    pub since: i64,
    pub until: i64,
}

pub fn render(tpl: &CompiledTemplate, ctx: &mut RenderCtx<'_>, buf: &mut String) {
    render_nodes(&tpl.nodes, ctx, buf);
}

fn render_nodes(nodes: &[Node], ctx: &mut RenderCtx<'_>, buf: &mut String) {
    for node in nodes {
        match node {
            Node::Lit(s) => buf.push_str(s),

            Node::Var(idx) => {
                if let Some(val) = ctx.values.get(*idx as usize) {
                    buf.push_str(val);
                }
            }

            Node::New { field, modifier, transform, range } => {
                let rng = std::mem::replace(ctx.rng, seedfaker_core::rng::Rng::new(0));
                let resolved_range = resolve_range(range, field.name, ctx.since, ctx.until);
                let mut gen_ctx = seedfaker_core::ctx::GenContext {
                    rng,
                    locales: ctx.locales,
                    modifier,
                    identity: ctx.identity,
                    tz_offset_minutes: ctx.tz_offset_minutes,
                    since: ctx.since,
                    until: ctx.until,
                    range: resolved_range,
                    ordering: seedfaker_core::field::Ordering::None,
                    numeric: None,
                };
                let start = buf.len();
                field.generate(&mut gen_ctx, buf);
                *ctx.rng = gen_ctx.rng;
                if *transform != Transform::None {
                    let raw = buf[start..].to_string();
                    buf.truncate(start);
                    buf.push_str(&transform.apply(&raw));
                }
            }

            Node::If { branches, else_body } => {
                let matched = branches.iter().find(|(cond, _)| {
                    ctx.values.get(cond.var as usize).is_some_and(|val| match cond.op {
                        CmpOp::Eq => val.as_str() == cond.value.as_ref(),
                        CmpOp::Neq => val.as_str() != cond.value.as_ref(),
                    })
                });
                let body = match matched {
                    Some((_, body)) => body.as_ref(),
                    None => else_body.as_ref(),
                };
                render_nodes(body, ctx, buf);
            }

            Node::Repeat { count, body } => {
                for _ in 0..*count {
                    render_nodes(body, ctx, buf);
                }
            }
        }
    }
}

fn resolve_range(
    range: &Option<RangeSpec>,
    field_name: &str,
    since: i64,
    until: i64,
) -> Option<(i64, i64)> {
    let r = range.as_ref()?;
    let is_date = matches!(field_name, "date" | "birthdate" | "timestamp");
    let (default_min, default_max) = if is_date { (since, until) } else { (0, 999_999) };
    let mut from = r.from.unwrap_or(default_min);
    let mut to = r.to.unwrap_or(default_max);
    if is_date {
        if from > 0 && from <= 9999 {
            from = seedfaker_core::temporal::parse(&from.to_string()).unwrap_or(from);
        }
        if to > 0 && to <= 9999 {
            to = seedfaker_core::temporal::parse_until(&to.to_string()).unwrap_or(to);
        }
    }
    if from >= to {
        return None;
    }
    Some((from, to))
}
