use std::collections::HashMap;

use serde_json::Value as Json;

use crate::block::BlockContext;
use crate::context::{Context, merge_json};
use crate::error::RenderError;
use crate::output::Output;
use crate::registry::Registry;
use crate::render::{Decorator, RenderContext, Renderable};
use crate::template::Template;
use crate::{Path, RenderErrorReason, StringOutput};

pub(crate) const PARTIAL_BLOCK: &str = "@partial-block";

fn find_partial<'reg: 'rc, 'rc>(
    rc: &RenderContext<'reg, 'rc>,
    r: &'reg Registry<'reg>,
    name: &str,
) -> Result<Option<&'rc Template>, RenderError> {
    if let Some(partial) = rc.get_partial(name) {
        return Ok(Some(partial));
    }

    if let Some(t) = rc.get_dev_mode_template(name) {
        return Ok(Some(t));
    }

    if let Some(t) = r.get_template(name) {
        return Ok(Some(t));
    }

    Ok(None)
}

pub fn expand_partial<'reg: 'rc, 'rc>(
    d: &Decorator<'rc>,
    r: &'reg Registry<'reg>,
    ctx: &'rc Context,
    rc: &mut RenderContext<'reg, 'rc>,
    out: &mut dyn Output,
) -> Result<(), RenderError> {
    let tname = d.name();

    let current_template_before = rc.get_current_template_name();
    let indent_before = rc.get_indent_string().cloned();

    if rc.is_current_template(tname) {
        return Err(RenderErrorReason::CannotIncludeSelf.into());
    }

    // check if referencing partial_block
    if tname == PARTIAL_BLOCK {
        if let Some(Some(content)) = rc.peek_partial_block() {
            out.write(content.as_str())?;
            Ok(())
        } else if let Some(fallback) = d.template() {
            // no partial_block for this scope, render fallback from block syntax
            let result = fallback.render(r, ctx, rc, out);
            rc.set_current_template_name(current_template_before);
            rc.set_indent_string(indent_before);
            result
        } else {
            // no partial_block for this scope
            Err(RenderErrorReason::PartialBlockNotFound.into())
        }
    } else {
        // normal partial
        let original_partial = find_partial(rc, r, tname)?;

        let partial = if let Some(partial) = original_partial {
            partial
        } else if let Some(inner_template) = d.template() {
            inner_template
        } else {
            return Err(RenderErrorReason::PartialNotFound(tname.to_owned()).into());
        };

        // hash
        let hash_ctx = d
            .hash()
            .iter()
            .map(|(k, v)| (*k, v.value()))
            .collect::<HashMap<&str, &Json>>();

        let mut partial_include_block = BlockContext::new();
        // overwrite parent block's params
        for (name, value) in &hash_ctx {
            partial_include_block
                .set_block_param(name, crate::BlockParamHolder::Value((*value).clone()));
        }

        // inherit local variables (@key/@index/@first/@last) from the
        // enclosing block so they remain accessible inside the partial.
        // This matches Handlebars.js, where the implicit data variables of
        // an enclosing `{{#each}}`/`{{#with}}` are inherited across the
        // partial boundary. Block params (`as |v k|`) are deliberately NOT
        // inherited, as they are scoped to their block.
        if let Some(parent) = rc.block() {
            partial_include_block
                .local_variables_mut()
                .clone_from(parent.local_variables());
        }

        // evaluate context for partial
        let merged_context = if let Some(p) = d.param(0) {
            if let Some(relative_path) = p.relative_path() {
                // path as parameter provided
                if let Some(rc_context) = rc.context() {
                    merge_json(
                        rc.evaluate(&rc_context, relative_path)?.as_json(),
                        &hash_ctx,
                    )
                } else {
                    merge_json(rc.evaluate(ctx, relative_path)?.as_json(), &hash_ctx)
                }
            } else {
                // literal provided
                merge_json(p.value(), &hash_ctx)
            }
        } else {
            // use current path
            if let Some(rc_context) = rc.context() {
                merge_json(
                    rc.evaluate2(&rc_context, &Path::current())?.as_json(),
                    &hash_ctx,
                )
            } else {
                merge_json(rc.evaluate2(ctx, &Path::current())?.as_json(), &hash_ctx)
            }
        };
        partial_include_block.set_base_value(merged_context);

        // Push partial's context block (doesn't clear parent blocks)
        // This allows inline partials from parent blocks to remain accessible
        rc.push_block(partial_include_block);

        // check if this inclusion has a block, make sure we are not rendering
        // the template itself
        if original_partial.is_some() {
            if let Some(current_parital_block) = d.template() {
                let mut tmp_out = StringOutput::new();
                // render will also eval the block, so any inline directives will be
                // evaluated
                current_parital_block.render(r, ctx, rc, &mut tmp_out)?;
                rc.push_partial_block(Some(tmp_out.into_string()?));
            } else {
                rc.push_partial_block(None);
            }
        } else {
            rc.push_partial_block(None);
        }

        // indent
        rc.set_indent_string(d.indent().cloned());

        let result = partial.render(r, ctx, rc, out);

        // cleanup
        let trailing_newline = rc.get_trailine_newline();

        // remove current partial_block
        rc.pop_partial_block();

        // Remove partial's context block
        rc.pop_block();
        rc.set_trailing_newline(trailing_newline);
        rc.set_current_template_name(current_template_before);
        rc.set_indent_string(indent_before);

        result
    }
}

#[cfg(test)]
mod test {
    use crate::context::Context;
    use crate::error::RenderError;
    use crate::output::Output;
    use crate::registry::Registry;
    use crate::render::{Helper, RenderContext};
    use crate::{Decorator, RenderErrorReason};

    #[test]
    fn test() {
        let mut handlebars = Registry::new();
        assert!(
            handlebars
                .register_template_string("t0", "{{> t1}}")
                .is_ok()
        );
        assert!(
            handlebars
                .register_template_string("t1", "{{this}}")
                .is_ok()
        );
        assert!(
            handlebars
                .register_template_string("t2", "{{#> t99}}not there{{/t99}}")
                .is_ok()
        );
        assert!(
            handlebars
                .register_template_string("t3", "{{#*inline \"t31\"}}{{this}}{{/inline}}{{> t31}}")
                .is_ok()
        );
        assert!(
            handlebars
                .register_template_string(
                    "t4",
                    "{{#> t5}}{{#*inline \"nav\"}}navbar{{/inline}}{{/t5}}"
                )
                .is_ok()
        );
        assert!(
            handlebars
                .register_template_string("t5", "include {{> nav}}")
                .is_ok()
        );
        assert!(
            handlebars
                .register_template_string("t6", "{{> t1 a}}")
                .is_ok()
        );
        assert!(
            handlebars
                .register_template_string(
                    "t7",
                    "{{#*inline \"t71\"}}{{a}}{{/inline}}{{> t71 a=\"world\"}}"
                )
                .is_ok()
        );
        assert!(handlebars.register_template_string("t8", "{{a}}").is_ok());
        assert!(
            handlebars
                .register_template_string("t9", "{{> t8 a=2}}")
                .is_ok()
        );

        assert_eq!(handlebars.render("t0", &1).ok().unwrap(), "1".to_string());
        assert_eq!(
            handlebars.render("t2", &1).ok().unwrap(),
            "not there".to_string()
        );
        assert_eq!(handlebars.render("t3", &1).ok().unwrap(), "1".to_string());
        assert_eq!(
            handlebars.render("t4", &1).ok().unwrap(),
            "include navbar".to_string()
        );
        assert_eq!(
            handlebars.render("t6", &json!({"a": "2"})).ok().unwrap(),
            "2".to_string()
        );
        assert_eq!(
            handlebars.render("t7", &1).ok().unwrap(),
            "world".to_string()
        );
        assert_eq!(handlebars.render("t9", &1).ok().unwrap(), "2".to_string());
    }

    #[test]
    fn test_include_partial_block() {
        let t0 = "hello {{> @partial-block}}";
        let t1 = "{{#> t0}}inner {{this}}{{/t0}}";

        let mut handlebars = Registry::new();
        assert!(handlebars.register_template_string("t0", t0).is_ok());
        assert!(handlebars.register_template_string("t1", t1).is_ok());

        let r0 = handlebars.render("t1", &true);
        assert_eq!(r0.ok().unwrap(), "hello inner true".to_string());
    }

    #[test]
    fn test_self_inclusion() {
        let t0 = "hello {{> t1}} {{> t0}}";
        let t1 = "some template";
        let mut handlebars = Registry::new();
        assert!(handlebars.register_template_string("t0", t0).is_ok());
        assert!(handlebars.register_template_string("t1", t1).is_ok());

        let r0 = handlebars.render("t0", &true);
        assert!(r0.is_err());
    }

    #[test]
    fn test_issue_143() {
        let main_template = "one{{> two }}three{{> two }}";
        let two_partial = "--- two ---";

        let mut handlebars = Registry::new();
        assert!(
            handlebars
                .register_template_string("template", main_template)
                .is_ok()
        );
        assert!(
            handlebars
                .register_template_string("two", two_partial)
                .is_ok()
        );

        let r0 = handlebars.render("template", &true);
        assert_eq!(r0.ok().unwrap(), "one--- two ---three--- two ---");
    }

    #[test]
    fn test_hash_context_outscope() {
        let main_template = "In: {{> p a=2}} Out: {{a}}";
        let p_partial = "{{a}}";

        let mut handlebars = Registry::new();
        assert!(
            handlebars
                .register_template_string("template", main_template)
                .is_ok()
        );
        assert!(handlebars.register_template_string("p", p_partial).is_ok());

        let r0 = handlebars.render("template", &true);
        assert_eq!(r0.ok().unwrap(), "In: 2 Out: ");
    }

    #[test]
    fn test_partial_context_hash() {
        let mut hbs = Registry::new();
        hbs.register_template_string("one", "This is a test. {{> two name=\"fred\" }}")
            .unwrap();
        hbs.register_template_string("two", "Lets test {{name}}")
            .unwrap();
        assert_eq!(
            "This is a test. Lets test fred",
            hbs.render("one", &0).unwrap()
        );
    }

    #[test]
    fn teset_partial_context_with_both_hash_and_param() {
        let mut hbs = Registry::new();
        hbs.register_template_string("one", "This is a test. {{> two this name=\"fred\" }}")
            .unwrap();
        hbs.register_template_string("two", "Lets test {{name}} and {{root_name}}")
            .unwrap();
        assert_eq!(
            "This is a test. Lets test fred and tom",
            hbs.render("one", &json!({"root_name": "tom"})).unwrap()
        );
    }

    #[test]
    fn test_partial_subexpression_context_hash() {
        let mut hbs = Registry::new();
        hbs.register_template_string("one", "This is a test. {{> (x @root) name=\"fred\" }}")
            .unwrap();
        hbs.register_template_string("two", "Lets test {{name}}")
            .unwrap();

        hbs.register_helper(
            "x",
            Box::new(
                |_: &Helper<'_>,
                 _: &Registry<'_>,
                 _: &Context,
                 _: &mut RenderContext<'_, '_>,
                 out: &mut dyn Output|
                 -> Result<(), RenderError> {
                    out.write("two")?;
                    Ok(())
                },
            ),
        );
        assert_eq!(
            "This is a test. Lets test fred",
            hbs.render("one", &0).unwrap()
        );
    }

    #[test]
    fn test_nested_partial_scope() {
        let t = "{{#*inline \"pp\"}}{{a}} {{b}}{{/inline}}{{#each c}}{{> pp a=2}}{{/each}}";
        let data = json!({"c": [{"b": true}, {"b": false}]});

        let mut handlebars = Registry::new();
        assert!(handlebars.register_template_string("t", t).is_ok());
        let r0 = handlebars.render("t", &data);
        assert_eq!(r0.ok().unwrap(), "2 true2 false");
    }

    #[test]
    fn test_nested_partial_block() {
        let mut handlebars = Registry::new();
        let template1 = "<outer>{{> @partial-block }}</outer>";
        let template2 = "{{#> t1 }}<inner>{{> @partial-block }}</inner>{{/ t1 }}";
        let template3 = "{{#> t2 }}Hello{{/ t2 }}";

        handlebars
            .register_template_string("t1", template1)
            .unwrap();
        handlebars
            .register_template_string("t2", template2)
            .unwrap();

        let page = handlebars.render_template(template3, &json!({})).unwrap();
        assert_eq!("<outer><inner>Hello</inner></outer>", page);
    }

    #[test]
    fn test_subexpression_partial_block() {
        let mut handlebars = Registry::new();
        let template1 = "<outer>{{> @partial-block }}</outer>";
        let template2 = "{{#> (x 'foo') }}<inner>{{> @partial-block }}</inner>{{/}}";
        let template3 = "{{#> (y this) }}Hello{{/}} World";

        handlebars.register_helper(
            "x",
            Box::new(
                |_: &Helper<'_>,
                 _: &Registry<'_>,
                 _: &Context,
                 _: &mut RenderContext<'_, '_>,
                 out: &mut dyn Output|
                 -> Result<(), RenderError> {
                    out.write("t1")?;
                    Ok(())
                },
            ),
        );
        handlebars.register_helper(
            "y",
            Box::new(
                |_: &Helper<'_>,
                 _: &Registry<'_>,
                 _: &Context,
                 _: &mut RenderContext<'_, '_>,
                 out: &mut dyn Output|
                 -> Result<(), RenderError> {
                    out.write("t2")?;
                    Ok(())
                },
            ),
        );
        handlebars
            .register_template_string("t1", template1)
            .unwrap();
        handlebars
            .register_template_string("t2", template2)
            .unwrap();

        let page = handlebars.render_template(template3, &json!({})).unwrap();
        assert_eq!("<outer><inner>Hello</inner></outer> World", page);
    }

    #[test]
    fn test_up_to_partial_level() {
        let outer = r#"{{>inner name="fruit:" vegetables=fruits}}"#;
        let inner = "{{#each vegetables}}{{../name}} {{this}},{{/each}}";

        let data = json!({ "fruits": ["carrot", "tomato"] });

        let mut handlebars = Registry::new();
        handlebars.register_template_string("outer", outer).unwrap();
        handlebars.register_template_string("inner", inner).unwrap();

        assert_eq!(
            handlebars.render("outer", &data).unwrap(),
            "fruit: carrot,fruit: tomato,"
        );
    }

    #[test]
    fn line_stripping_with_inline_and_partial() {
        let tpl0 = r#"{{#*inline "foo"}}foo
{{/inline}}
{{> foo}}
{{> foo}}
{{> foo}}"#;
        let tpl1 = r#"{{#*inline "foo"}}foo{{/inline}}
{{> foo}}
{{> foo}}
{{> foo}}"#;

        let hbs = Registry::new();
        assert_eq!(
            r"foo
foo
foo
",
            hbs.render_template(tpl0, &json!({})).unwrap()
        );
        assert_eq!(
            r"
foofoofoo",
            hbs.render_template(tpl1, &json!({})).unwrap()
        );
    }

    #[test]
    fn test_partial_indent() {
        let outer = r"                {{> inner inner_solo}}

{{#each inners}}
                {{> inner}}
{{/each}}

        {{#each inners}}
        {{> inner}}
        {{/each}}
";
        let inner = r"name: {{name}}
";

        let mut hbs = Registry::new();

        hbs.register_template_string("inner", inner).unwrap();
        hbs.register_template_string("outer", outer).unwrap();

        let result = hbs
            .render(
                "outer",
                &json!({
                    "inner_solo": {"name": "inner_solo"},
                    "inners": [
                        {"name": "hello"},
                        {"name": "there"}
                    ]
                }),
            )
            .unwrap();

        assert_eq!(
            result,
            r"                name: inner_solo

                name: hello
                name: there

        name: hello
        name: there
"
        );
    }
    // Rule::partial_expression should not trim leading indent  by default

    #[test]
    fn test_partial_prevent_indent() {
        let outer = r"                {{> inner inner_solo}}

{{#each inners}}
                {{> inner}}
{{/each}}

        {{#each inners}}
        {{> inner}}
        {{/each}}
";
        let inner = r"name: {{name}}
";

        let mut hbs = Registry::new();
        hbs.set_prevent_indent(true);

        hbs.register_template_string("inner", inner).unwrap();
        hbs.register_template_string("outer", outer).unwrap();

        let result = hbs
            .render(
                "outer",
                &json!({
                    "inner_solo": {"name": "inner_solo"},
                    "inners": [
                        {"name": "hello"},
                        {"name": "there"}
                    ]
                }),
            )
            .unwrap();

        assert_eq!(
            result,
            r"                name: inner_solo

                name: hello
                name: there

        name: hello
        name: there
"
        );
    }

    #[test]
    fn test_nested_partials() {
        let mut hb = Registry::new();
        hb.register_template_string("partial", "{{> @partial-block}}")
            .unwrap();
        hb.register_template_string(
            "index",
            r"{{#>partial}}
    Yo
    {{#>partial}}
    Yo 2
    {{/partial}}
{{/partial}}",
        )
        .unwrap();
        assert_eq!(
            r"    Yo
    Yo 2
",
            hb.render("index", &()).unwrap()
        );

        hb.register_template_string("partial2", "{{> @partial-block}}")
            .unwrap();
        let r2 = hb
            .render_template(
                r"{{#> partial}}
{{#> partial2}}
:(
{{/partial2}}
{{/partial}}",
                &(),
            )
            .unwrap();
        assert_eq!(":(\n", r2);
    }

    #[test]
    fn test_partial_context_issue_495() {
        let mut hb = Registry::new();
        hb.register_template_string(
            "t1",
            r#"{{~#*inline "displayName"~}}
Template:{{name}}
{{/inline}}
{{#each data as |name|}}
Name:{{name}}
{{>displayName name="aaaa"}}
{{/each}}"#,
        )
        .unwrap();

        hb.register_template_string(
            "t2",
            r#"{{~#*inline "displayName"~}}
Template:{{this}}
{{/inline}}
{{#each data as |name|}}
Name:{{name}}
{{>displayName}}
{{/each}}"#,
        )
        .unwrap();

        let data = json!({
            "data": ["hudel", "test"]
        });

        assert_eq!(
            r"Name:hudel
Template:aaaa
Name:test
Template:aaaa
",
            hb.render("t1", &data).unwrap()
        );
        assert_eq!(
            r"Name:hudel
Template:hudel
Name:test
Template:test
",
            hb.render("t2", &data).unwrap()
        );
    }

    #[test]
    fn test_multiline_partial_indent() {
        let mut hb = Registry::new();

        hb.register_template_string(
            "t1",
            r#"{{#*inline "thepartial"}}
  inner first line
  inner second line
{{/inline}}
  {{> thepartial}}
outer third line"#,
        )
        .unwrap();
        assert_eq!(
            r"    inner first line
    inner second line
outer third line",
            hb.render("t1", &()).unwrap()
        );

        hb.register_template_string(
            "t2",
            r#"{{#*inline "thepartial"}}inner first line
inner second line
{{/inline}}
  {{> thepartial}}
outer third line"#,
        )
        .unwrap();
        assert_eq!(
            r"  inner first line
  inner second line
outer third line",
            hb.render("t2", &()).unwrap()
        );

        hb.register_template_string(
            "t3",
            r#"{{#*inline "thepartial"}}{{a}}{{/inline}}
  {{> thepartial}}
outer third line"#,
        )
        .unwrap();
        assert_eq!(
            r"
  inner first line
  inner second lineouter third line",
            hb.render("t3", &json!({"a": "inner first line\ninner second line"}))
                .unwrap()
        );

        hb.register_template_string(
            "t4",
            r#"{{#*inline "thepartial"}}
  inner first line
  inner second line
{{/inline}}
  {{~> thepartial}}
outer third line"#,
        )
        .unwrap();
        assert_eq!(
            r"  inner first line
  inner second line
outer third line",
            hb.render("t4", &()).unwrap()
        );

        let mut hb2 = Registry::new();
        hb2.set_prevent_indent(true);

        hb2.register_template_string(
            "t1",
            r#"{{#*inline "thepartial"}}
  inner first line
  inner second line
{{/inline}}
  {{> thepartial}}
outer third line"#,
        )
        .unwrap();
        assert_eq!(
            r"    inner first line
  inner second line
outer third line",
            hb2.render("t1", &()).unwrap()
        );
    }

    #[test]
    fn test_indent_level_on_nested_partials() {
        let nested_partial = "
<div>
    content
</div>
";
        let partial = "
<div>
    {{>nested_partial}}
</div>
";

        let partial_indented = "
<div>
    {{>partial}}
</div>
";

        let result = "
<div>
    <div>
        <div>
            content
        </div>
    </div>
</div>
";

        let mut hb = Registry::new();
        hb.register_template_string("nested_partial", nested_partial.trim_start())
            .unwrap();
        hb.register_template_string("partial", partial.trim_start())
            .unwrap();
        hb.register_template_string("partial_indented", partial_indented.trim_start())
            .unwrap();

        let s = hb.render("partial_indented", &()).unwrap();

        assert_eq!(&s, result.trim_start());
    }

    #[test]
    fn test_issue_534() {
        let t1 = "{{title}}";
        let t2 = "{{#each modules}}{{> (lookup this \"module\") content name=0}}{{/each}}";

        let data = json!({
          "modules": [
            {"module": "t1", "content": {"title": "foo"}},
            {"module": "t1", "content": {"title": "bar"}},
          ]
        });

        let mut hbs = Registry::new();
        hbs.register_template_string("t1", t1).unwrap();
        hbs.register_template_string("t2", t2).unwrap();

        assert_eq!("foobar", hbs.render("t2", &data).unwrap());
    }

    #[test]
    fn test_partial_not_found() {
        let t1 = "{{> bar}}";
        let hbs = Registry::new();
        assert!(hbs.render_template(t1, &()).is_err());
    }

    #[test]
    fn test_issue_643_this_context() {
        let t1 = "{{this}}";
        let t2 = "{{> t1 \"hello world\"}}";

        let mut hbs = Registry::new();
        hbs.register_template_string("t1", t1).unwrap();
        hbs.register_template_string("t2", t2).unwrap();

        assert_eq!("hello world", hbs.render("t2", &()).unwrap());

        let t1 = "{{a}} {{[0]}} {{[1]}}";
        let t2 = "{{> t1 \"hello world\" a=1}}";

        let mut hbs = Registry::new();
        hbs.register_template_string("t1", t1).unwrap();
        hbs.register_template_string("t2", t2).unwrap();

        assert_eq!("1 h e", hbs.render("t2", &()).unwrap());

        let t1 = "{{#each this}}{{@key}}:{{this}},{{/each}}";
        let t2 = "{{> t1 a=1}}";

        let mut hbs = Registry::new();
        hbs.register_template_string("t1", t1).unwrap();
        hbs.register_template_string("t2", t2).unwrap();

        assert_eq!("a:1,", hbs.render("t2", &()).unwrap());

        let t1 = "{{#each this}}{{@key}}:{{this}},{{/each}}";
        let t2 = "{{> t1 a=1}}";

        let mut hbs = Registry::new();
        hbs.register_template_string("t1", t1).unwrap();
        hbs.register_template_string("t2", t2).unwrap();

        assert_eq!("b:2,a:1,", hbs.render("t2", &json!({"b": 2})).unwrap());

        let t1 = "{{#each this}}{{@key}}:{{this}},{{/each}}";
        let t2 = "{{> t1 b a=1}}";

        let mut hbs = Registry::new();
        hbs.register_template_string("t1", t1).unwrap();
        hbs.register_template_string("t2", t2).unwrap();

        assert_eq!("a:1,", hbs.render("t2", &json!({"b": 2})).unwrap());
    }

    #[test]
    fn test_nested_partial_block_scope_issue() {
        let mut hs = Registry::new();
        hs.register_template_string("primary", "{{> @partial-block }}")
            .unwrap();
        hs.register_template_string("secondary", "{{#*inline \"inl\"}}Bug{{/inline}}{{#>primary}}{{> @partial-block }}{{>inl}}{{/primary}}").unwrap();
        hs.register_template_string("current", "{{>secondary}}")
            .unwrap();

        assert!(matches!(
            hs.render("current", &()).unwrap_err().reason(),
            RenderErrorReason::PartialBlockNotFound
        ));

        let mut hs = Registry::new();
        hs.register_template_string("primary", "{{> @partial-block }}")
            .unwrap();
        hs.register_template_string("secondary", "{{#*inline \"inl\"}}Bug{{/inline}}{{#>primary}}{{> @partial-block }}{{>inl}}{{/primary}}").unwrap();
        hs.register_template_string("current", "{{#>secondary}}Not a {{/secondary}}")
            .unwrap();

        assert_eq!(hs.render("current", &()).unwrap(), "Not a Bug");
    }

    #[test]
    fn test_partial_block_syntax_for_at_partial_block() {
        let mut hb = Registry::new();
        hb.register_template_string(
            "some_partial",
            "before {{#> @partial-block}}DEFAULT{{/@partial-block}} after",
        )
        .unwrap();

        let r1 = hb
            .render_template("{{> some_partial}}", &json!({}))
            .unwrap();
        assert_eq!(r1, "before DEFAULT after");

        let r2 = hb
            .render_template("{{#> some_partial}}CONTENT{{/some_partial}}", &json!({}))
            .unwrap();
        assert_eq!(r2, "before CONTENT after");
    }

    #[test]
    fn test_partial_block_fallback_restores_state() {
        let mut hb = Registry::new();
        hb.register_template_string(
            "wrapper",
            "[{{#> @partial-block}}DEFAULT{{/@partial-block}}] {{> inner}}",
        )
        .unwrap();
        hb.register_template_string("inner", "ok").unwrap();

        let r1 = hb.render_template("{{> wrapper}}", &json!({})).unwrap();
        assert_eq!(r1, "[DEFAULT] ok");

        let r2 = hb
            .render_template("{{#> wrapper}}CUSTOM{{/wrapper}}", &json!({}))
            .unwrap();
        assert_eq!(r2, "[CUSTOM] ok");
    }

    #[test]
    fn test_partial_block_fallback_restores_template_name() {
        let mut hb = Registry::new();
        hb.register_template_string(
            "self_ref",
            "{{#> @partial-block}}DEFAULT{{/@partial-block}}{{> self_ref}}",
        )
        .unwrap();

        let result = hb.render_template("{{> self_ref}}", &json!({}));
        assert!(result.is_err());
        let msg = format!("{}", result.unwrap_err());
        assert!(
            msg.contains("include current template"),
            "expected self-inclusion error, got: {msg}"
        );
    }

    #[test]
    fn test_referencing_data_in_partial() {
        fn set_decorator(
            d: &Decorator<'_>,
            _: &Registry<'_>,
            _ctx: &Context,
            rc: &mut RenderContext<'_, '_>,
        ) -> Result<(), RenderError> {
            let data_to_set = d.hash();
            for (k, v) in data_to_set {
                set_in_context(rc, k, v.value().clone());
            }
            Ok(())
        }

        /// Sets a variable to a value within the context.
        fn set_in_context(rc: &mut RenderContext<'_, '_>, key: &str, value: serde_json::Value) {
            let mut gctx = match rc.context() {
                Some(c) => (*c).clone(),
                None => Context::wraps(serde_json::Value::Object(serde_json::Map::new())).unwrap(),
            };
            if let serde_json::Value::Object(m) = gctx.data_mut() {
                m.insert(key.to_string(), value);
                rc.set_context(gctx);
            } else {
                panic!("expected object in context");
            }
        }

        let mut handlebars = Registry::new();
        handlebars.register_decorator("set", Box::new(set_decorator));

        handlebars_helper!(lower: |s: str| s.to_lowercase());
        handlebars.register_helper("lower", Box::new(lower));
        handlebars
            .register_template_string(
                "an-included-file",
                "This file is included.\n\nSee {{lower somevalue}}\n",
            )
            .unwrap();

        let data = serde_json::json!({});
        assert_eq!(
            handlebars
                .render_template(
                    r#"
{{~*set somevalue="Example"}}
{{> an-included-file }}
"#,
                    &data
                )
                .unwrap(),
            "This file is included.\n\nSee example\n"
        );
    }

    #[test]
    fn test_each_this_order_issue_760() {
        let mut reg = Registry::new();
        reg.register_partial("p", "{{#each this}}\n{{n}}\n{{/each}}")
            .unwrap();

        let template = "{{#each nums}}{{> p par=42}}{{/each}}";

        let input = json!({
            "nums": [
                [
                    {"n": "1"},
                    {"n": "2"},
                    {"n": "3"},
                    {"n": "4"},
                    {"n": "5"},
                    {"n": "6"},
                    {"n": "7"},
                    {"n": "8"},
                    {"n": "9"},
                    {"n": "10"},
                    {"n": "11"},
                ]
            ]
        });
        let rendered = reg.render_template(template, &input).unwrap();
        // Matches Handlebars.js: the array context is converted to an object
        // (indexed by stringified indices) and merged with the hash parameter,
        // so `{{#each this}}` iterates the 11 array elements *and* the `par`
        // entry (whose `{{n}}` renders empty), yielding the trailing extra
        // newline. The array indices are iterated in numeric order, not the
        // lexicographic order that would otherwise reorder "10" before "2".
        assert_eq!("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n\n", rendered);
    }

    #[test]
    fn test_partial_hash_param_merged_with_array_context_issue_760() {
        // Companion to `test_each_this_order_issue_760`: when the context is
        // an array, Handlebars.js converts it to an object and merges the hash
        // parameters into it, so `{{#each this}}` iterates both the array
        // elements and the hash entries (in numeric-then-insertion order).
        let mut reg = Registry::new();
        reg.register_partial("p", "{{#each this}}[{{this}}]{{/each}}")
            .unwrap();

        let template = "{{> p items par=\"ok\"}}";
        let input = json!({ "items": [1, 2, 3] });
        let rendered = reg.render_template(template, &input).unwrap();
        assert_eq!("[1][2][3][ok]", rendered);
    }
}
