//! Channels target — emits `channels.ts` (typed message envelopes + channel
//! registry) and `channels.hooks.tsx` (`useXChannel` React hooks) from the
//! `x-mizan-channels` extension. Django-only feature; the FastAPI backend's
//! IR carries an empty channels list and this target emits nothing.

use std::path::PathBuf;

use askama::Template;
use indexmap::IndexMap;

use crate::config::Config;
use crate::emit::CodegenTarget;
use crate::emit::EmittedFile;
use crate::ir::{MizanChannel, MizanIR, NamedType, Primitive, StructField, TypeShape};


pub struct ChannelsTarget;


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

    fn emit(&self, ir: &MizanIR, _config: &Config) -> Vec<EmittedFile> {
        if ir.channels.is_empty() {
            return Vec::new();
        }

        let schemas_block = emit_channel_schemas(&ir.channels, &ir.types);

        let types_content = ChannelsTypes {
            channels: ir.channels.iter().map(ChannelView::from_ir).collect(),
            schemas_block,
        }.render().expect("channels.ts renders");

        let mut type_imports: Vec<String> = Vec::new();
        for ch in &ir.channels {
            if ch.has_params() { if let Some(t) = &ch.params_type { type_imports.push(t.clone()); } }
            if ch.has_react_message() { if let Some(t) = &ch.react_message_type { type_imports.push(t.clone()); } }
            if ch.has_django_message() { if let Some(t) = &ch.django_message_type { type_imports.push(t.clone()); } }
        }

        let hooks_content = ChannelsHooks {
            channels: ir.channels.iter().map(ChannelView::from_ir).collect(),
            type_imports,
        }.render().expect("channels.hooks.tsx renders");

        vec![
            EmittedFile::new(PathBuf::from("channels.ts"), types_content),
            EmittedFile::new(PathBuf::from("channels.hooks.tsx"), hooks_content),
        ]
    }
}


#[derive(Template)]
#[template(path = "channels/channels.ts.j2", escape = "none")]
struct ChannelsTypes<'a> {
    channels: Vec<ChannelView<'a>>,
    schemas_block: String,
}


#[derive(Template)]
#[template(path = "channels/channels.hooks.tsx.j2", escape = "none")]
struct ChannelsHooks<'a> {
    channels: Vec<ChannelView<'a>>,
    type_imports: Vec<String>,
}


struct ChannelView<'a> {
    name: &'a str,
    pascal_name: &'a str,
    has_params: bool,
    has_react_message: bool,
    has_django_message: bool,
    params_type: String,
    react_message_type: String,
    django_message_type: String,
    params_type_or_record: String,
    react_msg_type_or_never: String,
    django_msg_type_or_never: String,
}


impl<'a> ChannelView<'a> {
    fn from_ir(ch: &'a MizanChannel) -> Self {
        let params_type = ch.params_type.clone().unwrap_or_default();
        let react_message_type = ch.react_message_type.clone().unwrap_or_default();
        let django_message_type = ch.django_message_type.clone().unwrap_or_default();

        Self {
            name: &ch.name,
            pascal_name: &ch.pascal_name,
            has_params: ch.has_params(),
            has_react_message: ch.has_react_message(),
            has_django_message: ch.has_django_message(),
            params_type_or_record: if ch.has_params() { params_type.clone() } else { "Record<string, never>".to_string() },
            react_msg_type_or_never: if ch.has_react_message() { react_message_type.clone() } else { "never".to_string() },
            django_msg_type_or_never: if ch.has_django_message() { django_message_type.clone() } else { "never".to_string() },
            params_type,
            react_message_type,
            django_message_type,
        }
    }
}


fn emit_channel_schemas(
    channels: &[MizanChannel],
    types: &IndexMap<String, NamedType>,
) -> String {
    let mut blocks: Vec<String> = Vec::new();
    for ch in channels {
        for ty in [&ch.params_type, &ch.react_message_type, &ch.django_message_type].iter().filter_map(|t| t.as_ref()) {
            if let Some(named) = types.get(ty) {
                blocks.push(emit_named_type_as_ts(ty, named));
            }
        }
    }
    blocks.join("\n\n")
}


fn emit_named_type_as_ts(name: &str, ty: &NamedType) -> String {
    match ty {
        NamedType::Struct(fields) => emit_interface(name, fields),
        NamedType::List(inner) => format!("export type {name} = {}[]", ts_type_expression(inner)),
        NamedType::Enum(variants) => {
            let union = variants.iter().map(|v| format!("\"{v}\"")).collect::<Vec<_>>().join(" | ");
            format!("export type {name} = {union}")
        }
        NamedType::Alias(inner) => format!("export type {name} = {}", ts_type_expression(inner)),
    }
}


fn emit_interface(name: &str, fields: &[StructField]) -> String {
    if fields.is_empty() {
        return format!("export interface {name} {{}}");
    }
    let body = fields.iter()
        .map(|f| {
            let is_required = f.required || f.default.is_some();
            let opt = if is_required { "" } else { "?" };
            format!("    {}{opt}: {}", f.name, ts_type_expression(&f.shape))
        })
        .collect::<Vec<_>>()
        .join("\n");
    format!("export interface {name} {{\n{body}\n}}")
}


fn ts_type_expression(shape: &TypeShape) -> String {
    match shape {
        TypeShape::Ref(name) => name.clone(),
        TypeShape::Primitive(p) => primitive_to_ts(*p).to_string(),
        TypeShape::List(inner) => format!("{}[]", ts_type_expression(inner)),
        TypeShape::Optional(inner) => format!("{} | null", ts_type_expression(inner)),
        TypeShape::Enum(variants) => variants.iter()
            .map(|v| format!("\"{v}\""))
            .collect::<Vec<_>>()
            .join(" | "),
        TypeShape::Union(branches) => branches.iter()
            .map(ts_type_expression)
            .collect::<Vec<_>>()
            .join(" | "),
    }
}


fn primitive_to_ts(p: Primitive) -> &'static str {
    match p {
        Primitive::Integer | Primitive::Number => "number",
        Primitive::Boolean => "boolean",
        Primitive::String => "string",
    }
}
