//! Vue target — composable per context + composable per call.
//! Output shape lives at `templates/vue/vue.ts.j2`.

use std::path::PathBuf;

use askama::Template;

use crate::config::Config;
use crate::emit::CodegenTarget;
use crate::emit::EmittedFile;
use crate::emit::casing::pascal_case;
use crate::ir::{IsContext, MizanFunction, MizanIR};


pub struct VueAdapter;


impl CodegenTarget for VueAdapter {
    fn name(&self) -> &'static str { "vue" }

    fn emit(&self, ir: &MizanIR, _config: &Config) -> Vec<EmittedFile> {
        let content = build_template(ir).render().expect("vue template renders");
        vec![EmittedFile::new(PathBuf::from("vue.ts"), content)]
    }
}


#[derive(Template)]
#[template(path = "vue/vue.ts.j2", escape = "none")]
struct VueTemplate<'a> {
    stage1_imports: Vec<String>,
    contexts: Vec<CtxRender<'a>>,
    calls: Vec<CallRender>,
}


struct CtxRender<'a> {
    pascal: String,
    name: &'a str,
    has_params: bool,
    params_arg: &'static str,
    fns: Vec<FnRender<'a>>,
}


struct FnRender<'a> {
    camel_name: &'a str,
    name: &'a str,
    output_type: &'a str,
}


struct CallRender {
    pascal: String,
    has_input: bool,
}


fn build_template(ir: &MizanIR) -> VueTemplate<'_> {
    let contexts: Vec<CtxRender> = ir.contexts.iter()
        .map(|(ctx_name, ctx_meta)| {
            let has_params = !ctx_meta.params.is_empty();
            let ctx_fns: Vec<FnRender> = ir.functions.iter()
                .filter(|f| f.is_context.as_str() == Some(ctx_name.as_str()))
                .map(|f| FnRender {
                    camel_name: &f.camel_name,
                    name: &f.name,
                    output_type: &f.output_type,
                })
                .collect();
            CtxRender {
                pascal: pascal_case(ctx_name),
                name: ctx_name,
                has_params,
                params_arg: if has_params { "params" } else { "{} as any" },
                fns: ctx_fns,
            }
        })
        .collect();

    let mutations: Vec<&MizanFunction> = ir.functions.iter()
        .filter(|f| matches!(f.is_context, IsContext::No) && !f.is_form && !f.affects.is_empty())
        .collect();
    let plain_fns: Vec<&MizanFunction> = ir.functions.iter()
        .filter(|f| matches!(f.is_context, IsContext::No) && !f.is_form && f.affects.is_empty())
        .collect();

    let calls: Vec<CallRender> = mutations.iter().chain(plain_fns.iter())
        .map(|f| CallRender {
            pascal: pascal_case(&f.camel_name),
            has_input: f.has_input,
        })
        .collect();

    let mut stage1: Vec<String> = Vec::new();
    for ctx_name in ir.contexts.keys() {
        let p = pascal_case(ctx_name);
        stage1.push(format!("fetch{p}Context"));
        stage1.push(format!("type {p}ContextData"));
        stage1.push(format!("type {p}ContextParams"));
    }
    for fn_meta in mutations.iter().chain(plain_fns.iter()) {
        stage1.push(format!("call{}", pascal_case(&fn_meta.camel_name)));
    }

    VueTemplate { stage1_imports: stage1, contexts, calls }
}
