use std::borrow::Cow;
use std::collections::VecDeque;
use crate::ast::*;
use crate::common::TAB_INCR;
use crate::gast;
use crate::ids::Vector;
use crate::llbc_ast;
use crate::pretty::{fmt_with_ctx, FmtWithCtx};
use crate::ullbc_ast;
use crate::ullbc_ast as ast;
pub trait Formatter<T> {
    fn format_object(&self, x: T) -> String;
}
pub trait DeclFormatter<Id> {
    fn format_decl(&self, x: Id) -> String;
}
impl<C, T> Formatter<T> for &C
where
    C: Formatter<T>,
{
    fn format_object(&self, x: T) -> String {
        (*self).format_object(x)
    }
}
pub trait IntoFormatter {
    type C: AstFormatter;
    fn into_fmt(self) -> Self::C;
}
impl<C: AstFormatter> IntoFormatter for C {
    type C = Self;
    fn into_fmt(self) -> Self::C {
        self
    }
}
pub trait SetGenerics<'a> {
    type C: 'a + AstFormatter;
    fn set_generics(&'a self, generics: &'a GenericParams) -> Self::C;
}
impl<'a, 'b> SetGenerics<'a> for FmtCtx<'b> {
    type C = FmtCtx<'a>;
    fn set_generics(&'a self, generics: &'a GenericParams) -> Self::C {
        FmtCtx {
            translated: self.translated.as_deref(),
            generics: [Cow::Borrowed(generics)].into(),
            locals: self.locals.as_deref(),
        }
    }
}
pub trait SetLocals<'a> {
    type C: 'a + AstFormatter;
    fn set_locals(&'a self, locals: &'a Vector<VarId, ast::Var>) -> Self::C;
}
impl<'a, 'b> SetLocals<'a> for FmtCtx<'b> {
    type C = FmtCtx<'a>;
    fn set_locals(&'a self, locals: &'a Vector<VarId, ast::Var>) -> Self::C {
        FmtCtx {
            translated: self.translated.as_deref(),
            generics: self.generics.clone(),
            locals: Some(locals),
        }
    }
}
pub trait PushBoundRegions<'a> {
    type C: 'a + AstFormatter;
    fn push_bound_regions(&'a self, regions: &'a Vector<RegionId, RegionVar>) -> Self::C;
}
impl<'a, 'b> PushBoundRegions<'a> for FmtCtx<'b> {
    type C = FmtCtx<'a>;
    fn push_bound_regions(&'a self, regions: &'a Vector<RegionId, RegionVar>) -> Self::C {
        let mut generics = self.generics.clone();
        generics.push_front(Cow::Owned(GenericParams {
            regions: regions.clone(),
            ..Default::default()
        }));
        FmtCtx {
            translated: self.translated.as_deref(),
            generics,
            locals: self.locals.as_deref(),
        }
    }
}
pub trait AstFormatter = Formatter<TypeVarId>
    + Formatter<TypeDeclId>
    + Formatter<ConstGenericVarId>
    + Formatter<FunDeclId>
    + Formatter<GlobalDeclId>
    + Formatter<BodyId>
    + Formatter<TraitDeclId>
    + Formatter<TraitImplId>
    + Formatter<AnyTransId>
    + Formatter<TraitClauseId>
    + Formatter<(DeBruijnId, RegionId)>
    + Formatter<VarId>
    + Formatter<(TypeDeclId, VariantId)>
    + Formatter<(TypeDeclId, Option<VariantId>, FieldId)>
    + for<'a> Formatter<&'a ImplElem>
    + for<'a> Formatter<&'a RegionVar>
    + for<'a> Formatter<&'a Vector<ullbc_ast::BlockId, ullbc_ast::BlockData>>
    + for<'a> Formatter<&'a llbc_ast::Block>
    + for<'a> SetGenerics<'a>
    + for<'a> SetLocals<'a>
    + for<'a> PushBoundRegions<'a>;
#[derive(Default)]
pub struct FmtCtx<'a> {
    pub translated: Option<&'a TranslatedCrate>,
    pub generics: VecDeque<Cow<'a, GenericParams>>,
    pub locals: Option<&'a Vector<VarId, ast::Var>>,
}
impl<'a> FmtCtx<'a> {
    pub fn new() -> Self {
        FmtCtx::default()
    }
    pub fn format_decl_id(&self, id: AnyTransId) -> String {
        match id {
            AnyTransId::Type(id) => self.format_decl(id),
            AnyTransId::Fun(id) => self.format_decl(id),
            AnyTransId::Global(id) => self.format_decl(id),
            AnyTransId::TraitDecl(id) => self.format_decl(id),
            AnyTransId::TraitImpl(id) => self.format_decl(id),
        }
    }
    pub fn get_item(&self, id: AnyTransId) -> Result<AnyTransItem<'_>, Option<&Name>> {
        let Some(translated) = &self.translated else {
            return Err(None);
        };
        translated
            .get_item(id)
            .ok_or_else(|| translated.item_names.get(&id))
    }
    fn format_any_decl(&self, id: AnyTransId) -> String {
        match self.get_item(id) {
            Ok(d) => d.fmt_with_ctx(self),
            Err(opt_name) => {
                let opt_name = opt_name
                    .map(|n| n.fmt_with_ctx(self))
                    .map(|n| format!(" ({n})"))
                    .unwrap_or_default();
                format!("Missing decl: {id:?}{opt_name}")
            }
        }
    }
}
impl<'a> Formatter<TypeDeclId> for FmtCtx<'a> {
    fn format_object(&self, id: TypeDeclId) -> String {
        self.format_object(AnyTransId::from(id))
    }
}
impl<'a> Formatter<GlobalDeclId> for FmtCtx<'a> {
    fn format_object(&self, id: GlobalDeclId) -> String {
        self.format_object(AnyTransId::from(id))
    }
}
impl<'a> Formatter<FunDeclId> for FmtCtx<'a> {
    fn format_object(&self, id: ast::FunDeclId) -> String {
        self.format_object(AnyTransId::from(id))
    }
}
impl<'a> Formatter<BodyId> for FmtCtx<'a> {
    fn format_object(&self, id: BodyId) -> String {
        match self
            .translated
            .and_then(|translated| translated.bodies.get(id))
        {
            None => "<error>".to_owned(),
            Some(d) => d.fmt_with_ctx(self),
        }
    }
}
impl<'a> Formatter<TraitDeclId> for FmtCtx<'a> {
    fn format_object(&self, id: ast::TraitDeclId) -> String {
        self.format_object(AnyTransId::from(id))
    }
}
impl<'a> Formatter<TraitImplId> for FmtCtx<'a> {
    fn format_object(&self, id: ast::TraitImplId) -> String {
        self.format_object(AnyTransId::from(id))
    }
}
impl<'a> Formatter<AnyTransId> for FmtCtx<'a> {
    fn format_object(&self, id: AnyTransId) -> String {
        match self
            .translated
            .and_then(|translated| translated.item_names.get(&id))
        {
            None => id.to_string(),
            Some(name) => name.fmt_with_ctx(self),
        }
    }
}
impl<'a> Formatter<(DeBruijnId, RegionId)> for FmtCtx<'a> {
    fn format_object(&self, (grid, id): (DeBruijnId, RegionId)) -> String {
        match self.generics.get(grid.index) {
            None => Region::BVar(grid, id).to_string(),
            Some(generics) => match generics.regions.get(id) {
                None => {
                    let region = Region::BVar(grid, id);
                    tracing::warn!(
                        "Found incorrect region `{region}` while pretty-printing. Look for \
                        \"wrong_region\" in the pretty output"
                    );
                    format!("wrong_region({region})")
                }
                Some(v) => self.format_object(v),
            },
        }
    }
}
impl<'a> Formatter<&RegionVar> for FmtCtx<'a> {
    fn format_object(&self, var: &RegionVar) -> String {
        match &var.name {
            Some(name) => name.to_string(),
            None => {
                let depth = self.generics.len() - 1;
                if depth == 0 {
                    format!("'_{}", var.index)
                } else {
                    format!("'_{depth}_{}", var.index)
                }
            }
        }
    }
}
impl<'a> Formatter<TypeVarId> for FmtCtx<'a> {
    fn format_object(&self, id: TypeVarId) -> String {
        match &self.generics.back() {
            None => id.to_pretty_string(),
            Some(generics) => match generics.types.get(id) {
                None => id.to_pretty_string(),
                Some(v) => v.to_string(),
            },
        }
    }
}
impl<'a> Formatter<ConstGenericVarId> for FmtCtx<'a> {
    fn format_object(&self, id: ConstGenericVarId) -> String {
        match &self.generics.back() {
            None => id.to_pretty_string(),
            Some(generics) => match generics.const_generics.get(id) {
                None => id.to_pretty_string(),
                Some(v) => v.to_string(),
            },
        }
    }
}
impl<'a> Formatter<ast::TraitClauseId> for FmtCtx<'a> {
    fn format_object(&self, id: ast::TraitClauseId) -> String {
        id.to_pretty_string()
    }
}
impl<'a> Formatter<&ImplElem> for FmtCtx<'a> {
    fn format_object(&self, elem: &ImplElem) -> String {
        let inner = match elem {
            ImplElem::Ty(generics, ty) => {
                ty.fmt_with_ctx(&self.set_generics(generics))
            }
            ImplElem::Trait(impl_id) => {
                match &self.translated {
                    None => format!("impl#{impl_id}"),
                    Some(translated) => match translated.trait_impls.get(*impl_id) {
                        None => format!("impl#{impl_id}"),
                        Some(timpl) => {
                            let ctx = &self.set_generics(&timpl.generics);
                            let TraitDeclRef { trait_id, generics } = &timpl.impl_trait;
                            let (ty, generics) = generics.pop_first_type_arg();
                            let tr = TraitDeclRef {
                                trait_id: *trait_id,
                                generics,
                            };
                            format!("impl {} for {}", tr.fmt_with_ctx(ctx), ty.fmt_with_ctx(ctx))
                        }
                    },
                }
            }
        };
        format!("{{{inner}}}")
    }
}
impl<'a> Formatter<(TypeDeclId, VariantId)> for FmtCtx<'a> {
    fn format_object(&self, id: (TypeDeclId, VariantId)) -> String {
        let (def_id, variant_id) = id;
        match &self.translated {
            None => format!(
                "{}::{}",
                def_id.to_pretty_string(),
                variant_id.to_pretty_string()
            ),
            Some(translated) => {
                match translated.type_decls.get(def_id) {
                    None => format!(
                        "{}::{}",
                        def_id.to_pretty_string(),
                        variant_id.to_pretty_string()
                    ),
                    Some(def) if def.kind.is_enum() => {
                        let variants = def.kind.as_enum().unwrap();
                        let mut name = def.item_meta.name.fmt_with_ctx(self);
                        let variant_name = &variants.get(variant_id).unwrap().name;
                        name.push_str("::");
                        name.push_str(variant_name);
                        name
                    }
                    _ => format!("__unknown_variant"),
                }
            }
        }
    }
}
impl<'a> Formatter<(TypeDeclId, Option<VariantId>, FieldId)> for FmtCtx<'a> {
    fn format_object(&self, id: (TypeDeclId, Option<VariantId>, FieldId)) -> String {
        let (def_id, opt_variant_id, field_id) = id;
        match &self.translated {
            None => match opt_variant_id {
                None => format!(
                    "{}::{}",
                    def_id.to_pretty_string(),
                    field_id.to_pretty_string()
                ),
                Some(variant_id) => format!(
                    "{}::{}::{}",
                    def_id.to_pretty_string(),
                    variant_id.to_pretty_string(),
                    field_id.to_pretty_string()
                ),
            },
            Some(translated) =>
            {
                match translated.type_decls.get(def_id) {
                    None => match opt_variant_id {
                        None => format!(
                            "{}::{}",
                            def_id.to_pretty_string(),
                            field_id.to_pretty_string()
                        ),
                        Some(variant_id) => format!(
                            "{}::{}::{}",
                            def_id.to_pretty_string(),
                            variant_id.to_pretty_string(),
                            field_id.to_pretty_string()
                        ),
                    },
                    Some(gen_def) => match (&gen_def.kind, opt_variant_id) {
                        (TypeDeclKind::Enum(variants), Some(variant_id)) => {
                            let field = variants
                                .get(variant_id)
                                .unwrap()
                                .fields
                                .get(field_id)
                                .unwrap();
                            match &field.name {
                                Some(name) => name.clone(),
                                None => field_id.to_string(),
                            }
                        }
                        (TypeDeclKind::Struct(fields) | TypeDeclKind::Union(fields), None) => {
                            let field = fields.get(field_id).unwrap();
                            match &field.name {
                                Some(name) => name.clone(),
                                None => field_id.to_string(),
                            }
                        }
                        _ => format!("__unknown_field"),
                    },
                }
            }
        }
    }
}
impl<'a> Formatter<VarId> for FmtCtx<'a> {
    fn format_object(&self, id: VarId) -> String {
        match &self.locals {
            None => id.to_pretty_string(),
            Some(vars) => match vars.get(id) {
                None => id.to_pretty_string(),
                Some(v) => v.to_string(),
            },
        }
    }
}
impl<'a> Formatter<&llbc_ast::Block> for FmtCtx<'a> {
    fn format_object(&self, x: &llbc_ast::Block) -> String {
        x.fmt_with_ctx(self)
    }
}
impl<'a> Formatter<&Vector<ullbc_ast::BlockId, ullbc_ast::BlockData>> for FmtCtx<'a> {
    fn format_object(&self, x: &Vector<ullbc_ast::BlockId, ullbc_ast::BlockData>) -> String {
        fmt_with_ctx::fmt_body_blocks_with_ctx(x, TAB_INCR, self)
    }
}
impl<'a> Formatter<&Ty> for FmtCtx<'a> {
    fn format_object(&self, x: &Ty) -> String {
        x.fmt_with_ctx(self)
    }
}
impl<'a> Formatter<&TypeDecl> for FmtCtx<'a> {
    fn format_object(&self, def: &TypeDecl) -> String {
        def.fmt_with_ctx(self)
    }
}
impl<'a> Formatter<&ullbc_ast::ExprBody> for FmtCtx<'a> {
    fn format_object(&self, body: &ullbc_ast::ExprBody) -> String {
        body.fmt_with_ctx(self)
    }
}
impl<'a> Formatter<&llbc_ast::ExprBody> for FmtCtx<'a> {
    fn format_object(&self, body: &llbc_ast::ExprBody) -> String {
        body.fmt_with_ctx(self)
    }
}
impl<'a> Formatter<&gast::FunDecl> for FmtCtx<'a> {
    fn format_object(&self, def: &llbc_ast::FunDecl) -> String {
        def.fmt_with_ctx(self)
    }
}
impl<'a> Formatter<&gast::GlobalDecl> for FmtCtx<'a> {
    fn format_object(&self, def: &llbc_ast::GlobalDecl) -> String {
        def.fmt_with_ctx(self)
    }
}
impl<'a> Formatter<&FunSig> for FmtCtx<'a> {
    fn format_object(&self, x: &FunSig) -> String {
        x.fmt_with_ctx(self)
    }
}
impl<'a> Formatter<&gast::TraitDecl> for FmtCtx<'a> {
    fn format_object(&self, def: &gast::TraitDecl) -> String {
        def.fmt_with_ctx(self)
    }
}
impl<'a> Formatter<&gast::TraitImpl> for FmtCtx<'a> {
    fn format_object(&self, def: &gast::TraitImpl) -> String {
        def.fmt_with_ctx(self)
    }
}
impl<'a> DeclFormatter<TypeDeclId> for FmtCtx<'a> {
    fn format_decl(&self, id: TypeDeclId) -> String {
        self.format_any_decl(id.into())
    }
}
impl<'a> DeclFormatter<GlobalDeclId> for FmtCtx<'a> {
    fn format_decl(&self, id: GlobalDeclId) -> String {
        self.format_any_decl(id.into())
    }
}
impl<'a> DeclFormatter<FunDeclId> for FmtCtx<'a> {
    fn format_decl(&self, id: FunDeclId) -> String {
        self.format_any_decl(id.into())
    }
}
impl<'a> DeclFormatter<TraitDeclId> for FmtCtx<'a> {
    fn format_decl(&self, id: TraitDeclId) -> String {
        self.format_any_decl(id.into())
    }
}
impl<'a> DeclFormatter<TraitImplId> for FmtCtx<'a> {
    fn format_decl(&self, id: TraitImplId) -> String {
        self.format_any_decl(id.into())
    }
}