//! Block compilation: `{{#if}}` and `{{#repeat}}`.

use super::super::parse::Token;

use super::compile_nodes;
use super::cond::parse_condition;
use super::expr::compile_expr;
use super::node::Node;

pub fn compile_if(
    tokens: &[Token<'_>],
    pos: &mut usize,
    var_names: &[&str],
    first_cond_str: &str,
) -> Result<Node, String> {
    let mut branches = Vec::new();

    let cond = parse_condition(first_cond_str, var_names)?;
    *pos += 1;
    let body = compile_if_body(tokens, pos, var_names)?;
    branches.push((cond, body));

    loop {
        if *pos >= tokens.len() {
            return Err("unclosed {{#if}}: expected {{/if}}".into());
        }
        match &tokens[*pos] {
            Token::BlockOpen { tag: "elif", rest } => {
                let cond = parse_condition(rest, var_names)?;
                *pos += 1;
                let body = compile_if_body(tokens, pos, var_names)?;
                branches.push((cond, body));
            }
            Token::Else => {
                *pos += 1;
                let else_body = compile_nodes(tokens, pos, var_names, Some("if"))?;
                return Ok(Node::If { branches: branches.into_boxed_slice(), else_body });
            }
            Token::BlockClose("if") => {
                *pos += 1;
                return Ok(Node::If {
                    branches: branches.into_boxed_slice(),
                    else_body: Box::new([]),
                });
            }
            _ => return Err("expected {{#elif}}, {{else}}, or {{/if}}".into()),
        }
    }
}

fn compile_if_body(
    tokens: &[Token<'_>],
    pos: &mut usize,
    var_names: &[&str],
) -> Result<Box<[Node]>, String> {
    let mut nodes = Vec::new();
    while *pos < tokens.len() {
        match &tokens[*pos] {
            Token::BlockOpen { tag: "elif", .. } | Token::Else | Token::BlockClose("if") => {
                return Ok(nodes.into_boxed_slice());
            }
            Token::Literal(s) => {
                nodes.push(Node::Lit((*s).into()));
                *pos += 1;
            }
            Token::Expr(e) => {
                nodes.push(compile_expr(e, var_names)?);
                *pos += 1;
            }
            Token::BlockOpen { tag: "if", rest } => {
                nodes.push(compile_if(tokens, pos, var_names, rest)?);
            }
            Token::BlockOpen { tag: "repeat", rest } => {
                nodes.push(compile_repeat(tokens, pos, var_names, rest)?);
            }
            Token::BlockOpen { tag, .. } => {
                return Err(format!("unknown block tag '{{{{#{tag}}}}}' inside if-body"));
            }
            Token::BlockClose(tag) => {
                return Err(format!("unexpected {{{{/{tag}}}}} inside if-body"));
            }
        }
    }
    Err("unclosed {{#if}}: reached end of template".into())
}

pub fn compile_repeat(
    tokens: &[Token<'_>],
    pos: &mut usize,
    var_names: &[&str],
    count_str: &str,
) -> Result<Node, String> {
    let count: u16 = count_str
        .trim()
        .parse()
        .map_err(|_| format!("{{{{#repeat {count_str}}}}}: expected a number"))?;

    *pos += 1;
    let body = compile_nodes(tokens, pos, var_names, Some("repeat"))?;

    Ok(Node::Repeat { count, body })
}
