use std::fmt;

use rustc_data_structures::intern::Interned;
use rustc_macros::HashStable;
use rustc_type_ir::ir_print::IrPrint;
use rustc_type_ir::{self as ir, FlagComputation, Flags};

use super::TyCtxt;
use crate::ty;

pub type PatternKind<'tcx> = ir::PatternKind<TyCtxt<'tcx>>;

#[derive(Copy, Clone, PartialEq, Eq, Hash, HashStable)]
#[rustc_pass_by_value]
pub struct Pattern<'tcx>(pub Interned<'tcx, PatternKind<'tcx>>);

impl<'tcx> Flags for Pattern<'tcx> {
    fn flags(&self) -> rustc_type_ir::TypeFlags {
        match &**self {
            ty::PatternKind::Range { start, end } => {
                FlagComputation::for_const_kind(&start.kind()).flags
                    | FlagComputation::for_const_kind(&end.kind()).flags
            }
            ty::PatternKind::Or(pats) => {
                let mut flags = pats[0].flags();
                for pat in pats[1..].iter() {
                    flags |= pat.flags();
                }
                flags
            }
            ty::PatternKind::NotNull => rustc_type_ir::TypeFlags::empty(),
        }
    }

    fn outer_exclusive_binder(&self) -> rustc_type_ir::DebruijnIndex {
        match &**self {
            ty::PatternKind::Range { start, end } => {
                start.outer_exclusive_binder().max(end.outer_exclusive_binder())
            }
            ty::PatternKind::Or(pats) => {
                let mut idx = pats[0].outer_exclusive_binder();
                for pat in pats[1..].iter() {
                    idx = idx.max(pat.outer_exclusive_binder());
                }
                idx
            }
            ty::PatternKind::NotNull => rustc_type_ir::INNERMOST,
        }
    }
}

impl<'tcx> std::ops::Deref for Pattern<'tcx> {
    type Target = PatternKind<'tcx>;

    fn deref(&self) -> &Self::Target {
        &*self.0
    }
}

impl<'tcx> fmt::Debug for Pattern<'tcx> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:?}", **self)
    }
}

impl<'tcx> IrPrint<PatternKind<'tcx>> for TyCtxt<'tcx> {
    fn print(t: &PatternKind<'tcx>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *t {
            PatternKind::Range { start, end } => {
                write!(f, "{start}")?;

                if let Some(c) = end.try_to_value() {
                    let end = c.to_leaf();
                    let size = end.size();
                    let max = match c.ty.kind() {
                        ty::Int(_) => {
                            Some(ty::ScalarInt::truncate_from_int(size.signed_int_max(), size))
                        }
                        ty::Uint(_) => {
                            Some(ty::ScalarInt::truncate_from_uint(size.unsigned_int_max(), size))
                        }
                        ty::Char => Some(ty::ScalarInt::truncate_from_uint(char::MAX, size)),
                        _ => None,
                    };
                    if let Some((max, _)) = max
                        && end == max
                    {
                        return write!(f, "..");
                    }
                }

                write!(f, "..={end}")
            }
            PatternKind::NotNull => write!(f, "!null"),
            PatternKind::Or(patterns) => {
                write!(f, "(")?;
                let mut first = true;
                for pat in patterns {
                    if first {
                        first = false
                    } else {
                        write!(f, " | ")?;
                    }
                    write!(f, "{pat:?}")?;
                }
                write!(f, ")")
            }
        }
    }

    fn print_debug(t: &PatternKind<'tcx>, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        Self::print(t, fmt)
    }
}

impl<'tcx> rustc_type_ir::inherent::IntoKind for Pattern<'tcx> {
    type Kind = PatternKind<'tcx>;
    fn kind(self) -> Self::Kind {
        *self
    }
}
