use super::*;
use crate::interpreter::lowered::LoweredMatchArm;
use crate::ir::{
    CallLowerCtx, DispatchLiteral, DispatchTableShape, MatchDispatchPlan, SemanticConstructor,
    SemanticDispatchPattern, WrapperKind, classify_constructor_name,
    classify_match_dispatch_plan_from_patterns,
};

pub(super) struct InterpreterLowerCtx<'a> {
    interpreter: &'a Interpreter,
}

impl<'a> InterpreterLowerCtx<'a> {
    pub(super) fn new(interpreter: &'a Interpreter) -> Self {
        Self { interpreter }
    }
}

impl CallLowerCtx for InterpreterLowerCtx<'_> {
    fn is_local_value(&self, _name: &str) -> bool {
        false
    }

    fn is_user_type(&self, name: &str) -> bool {
        self.interpreter.arena.find_type_id(name).is_some()
            || name
                .rsplit('.')
                .next()
                .is_some_and(|leaf| self.interpreter.arena.find_type_id(leaf).is_some())
    }

    fn resolve_module_call<'a>(&self, dotted: &'a str) -> Option<(&'a str, &'a str)> {
        for (idx, ch) in dotted.char_indices().rev() {
            if ch != '.' {
                continue;
            }
            let prefix = &dotted[..idx];
            let suffix = &dotted[idx + 1..];
            if suffix.is_empty() || !suffix.contains('.') {
                continue;
            }
            if self.interpreter.namespace_path_exists(prefix) {
                return Some((prefix, suffix));
            }
        }
        None
    }
}

impl Interpreter {
    pub(super) fn classify_runtime_constructor_name(&self, name: &str) -> SemanticConstructor {
        match name {
            "None" => SemanticConstructor::NoneValue,
            "Ok" => SemanticConstructor::Wrapper(WrapperKind::ResultOk),
            "Err" => SemanticConstructor::Wrapper(WrapperKind::ResultErr),
            "Some" => SemanticConstructor::Wrapper(WrapperKind::OptionSome),
            _ => classify_constructor_name(name, &InterpreterLowerCtx::new(self)),
        }
    }

    pub(super) fn ctor_type_matches_runtime(
        qualified_type_name: &str,
        runtime_type_name: &str,
    ) -> bool {
        qualified_type_name == runtime_type_name
            || qualified_type_name
                .rsplit('.')
                .next()
                .is_some_and(|leaf| leaf == runtime_type_name)
    }

    pub(super) fn namespace_path_exists(&self, path: &str) -> bool {
        self.lookup_path_nv(path)
            .map(|value| value.is_namespace())
            .unwrap_or(false)
    }

    pub(super) fn apply_runtime_constructor_args_nv(
        &mut self,
        name: &str,
        args: &[NanValue],
    ) -> Result<NanValue, RuntimeError> {
        match self.classify_runtime_constructor_name(name) {
            SemanticConstructor::NoneValue => {
                if args.is_empty() {
                    Ok(NanValue::NONE)
                } else {
                    Err(RuntimeError::Error(format!(
                        "Constructor '{}' does not take an argument",
                        name
                    )))
                }
            }
            SemanticConstructor::Wrapper(kind) => match args {
                [inner] => match kind {
                    WrapperKind::ResultOk => Ok(NanValue::new_ok_value(*inner, &mut self.arena)),
                    WrapperKind::ResultErr => Ok(NanValue::new_err_value(*inner, &mut self.arena)),
                    WrapperKind::OptionSome => {
                        Ok(NanValue::new_some_value(*inner, &mut self.arena))
                    }
                },
                _ => Err(RuntimeError::Error(format!(
                    "Constructor '{}' expects exactly 1 argument",
                    name
                ))),
            },
            SemanticConstructor::TypeConstructor {
                qualified_type_name,
                variant_name,
            } => {
                let runtime_type_name = qualified_type_name
                    .rsplit('.')
                    .next()
                    .unwrap_or(&qualified_type_name);
                let type_id = self
                    .arena
                    .find_type_id(runtime_type_name)
                    .unwrap_or_else(|| {
                        self.arena
                            .register_sum_type(runtime_type_name, vec![variant_name.clone()])
                    });
                let variant_id = self
                    .arena
                    .find_variant_id(type_id, &variant_name)
                    .unwrap_or_else(|| {
                        self.arena
                            .register_variant_name(type_id, variant_name.clone())
                    });

                let value = if args.is_empty() {
                    let ctor_id =
                        self.arena
                            .find_ctor_id(type_id, variant_id)
                            .ok_or_else(|| {
                                RuntimeError::Error(format!("Unknown constructor: {}", name))
                            })?;
                    let symbol = self.arena.push_nullary_variant_symbol(ctor_id);
                    NanValue::new_nullary_variant(symbol)
                } else {
                    let variant_idx = self.arena.push_variant(type_id, variant_id, args.to_vec());
                    NanValue::new_variant(variant_idx)
                };
                Ok(value)
            }
            SemanticConstructor::Unknown(_) => Err(RuntimeError::Error(format!(
                "Unknown constructor: {}",
                name
            ))),
        }
    }

    pub(super) fn apply_runtime_constructor_nv(
        &mut self,
        name: &str,
        inner: Option<NanValue>,
    ) -> Result<NanValue, RuntimeError> {
        match inner {
            Some(inner) => {
                self.apply_runtime_constructor_args_nv(name, std::slice::from_ref(&inner))
            }
            None => self.apply_runtime_constructor_args_nv(name, &[]),
        }
    }

    pub(super) fn try_dispatch_match_plan_nv(
        &mut self,
        subject: NanValue,
        arms: &[LoweredMatchArm],
    ) -> Option<(usize, Vec<(String, NanValue)>)> {
        let patterns: Vec<&Pattern> = arms.iter().map(|arm| &arm.pattern).collect();
        let plan =
            classify_match_dispatch_plan_from_patterns(&patterns, &InterpreterLowerCtx::new(self))?;

        let arm_index = match plan {
            MatchDispatchPlan::Bool(shape) => {
                if !subject.is_bool() {
                    return None;
                }
                if subject.as_bool() {
                    shape.true_arm_index
                } else {
                    shape.false_arm_index
                }
            }
            MatchDispatchPlan::List(shape) => {
                if !subject.is_list() {
                    return None;
                }
                if self.arena.list_is_empty_value(subject) {
                    shape.empty_arm_index
                } else {
                    shape.cons_arm_index
                }
            }
            MatchDispatchPlan::Table(shape) => self.dispatch_table_match_arm(subject, &shape)?,
        };

        self.match_pattern_nv(&arms[arm_index].pattern, subject)
            .map(|bindings| (arm_index, bindings))
    }

    fn dispatch_table_match_arm(
        &self,
        subject: NanValue,
        shape: &DispatchTableShape,
    ) -> Option<usize> {
        for entry in &shape.entries {
            if self.dispatch_pattern_matches_nv(subject, &entry.pattern) {
                return Some(entry.arm_index);
            }
        }
        shape.default_arm.as_ref().map(|default| default.arm_index)
    }

    fn dispatch_pattern_matches_nv(
        &self,
        subject: NanValue,
        pattern: &SemanticDispatchPattern,
    ) -> bool {
        match pattern {
            SemanticDispatchPattern::Literal(DispatchLiteral::Int(i)) => {
                subject.is_int() && subject.as_int(&self.arena) == *i
            }
            SemanticDispatchPattern::Literal(DispatchLiteral::Float(f)) => {
                subject.is_float()
                    && f.parse::<f64>()
                        .ok()
                        .is_some_and(|expected| subject.as_float().to_bits() == expected.to_bits())
            }
            SemanticDispatchPattern::Literal(DispatchLiteral::Bool(b)) => {
                subject.is_bool() && subject.as_bool() == *b
            }
            SemanticDispatchPattern::Literal(DispatchLiteral::Str(s)) => {
                subject.is_string() && self.arena.get_string_value(subject).as_str() == s.as_str()
            }
            SemanticDispatchPattern::Literal(DispatchLiteral::Unit) => subject.is_unit(),
            SemanticDispatchPattern::EmptyList => {
                subject.is_list() && self.arena.list_is_empty_value(subject)
            }
            SemanticDispatchPattern::NoneValue => subject.is_none(),
            SemanticDispatchPattern::WrapperTag(WrapperKind::ResultOk) => subject.is_ok(),
            SemanticDispatchPattern::WrapperTag(WrapperKind::ResultErr) => subject.is_err(),
            SemanticDispatchPattern::WrapperTag(WrapperKind::OptionSome) => subject.is_some(),
        }
    }
}
