use crate::Emitter;
use crate::calls::go_interop::wrappers::{ResolvedSink, WrapperOutcome, WrapperTarget, write_leaf};
use crate::control_flow::fallible::{Fallible, FallibleEmitter, OPTION_SOME_TAG};
use crate::expressions::context::ExpressionContext;
use crate::write_line;
use syntax::ast::Expression;
use syntax::types::Type;

struct CollectionUnwrapShape {
    raw_collection_ty: String,
    is_pointer_bridged: bool,
    emit_nil_else: bool,
}

impl Emitter<'_> {
    pub(super) fn emit_go_option_call_wrapped(
        &mut self,
        output: &mut String,
        call_expression: &Expression,
        option_ty: &Type,
    ) -> String {
        let call_str = self.emit_call(output, call_expression, None, ExpressionContext::value());
        self.emit_comma_ok_wrapping(output, &call_str, option_ty, true, WrapperTarget::FreshSlot)
            .expect("wrapper produced no slot")
    }

    pub(super) fn emit_go_sentinel_call_wrapped(
        &mut self,
        output: &mut String,
        call_expression: &Expression,
        option_ty: &Type,
        sentinel: i64,
    ) -> String {
        let call_str = self.emit_call(output, call_expression, None, ExpressionContext::value());
        self.emit_sentinel_wrapping(
            output,
            &call_str,
            option_ty,
            sentinel,
            WrapperTarget::FreshSlot,
        )
        .expect("wrapper produced no slot")
    }

    /// Capture the call's raw return into a temp, then reuse
    /// `OptionFromCommaOk` with `raw != sentinel` as the bool.
    pub(crate) fn emit_sentinel_wrapping(
        &mut self,
        output: &mut String,
        call_str: &str,
        option_ty: &Type,
        sentinel: i64,
        target: WrapperTarget<'_>,
    ) -> WrapperOutcome {
        self.requirements.require_stdlib();
        let raw = self.hoist_tmp_value(output, "ret", call_str);
        let inner_ty_str = self.go_type_as_string(&option_ty.ok_type());
        let value_expr = format!(
            "lisette.OptionFromCommaOk[{}]({}, {} != {})",
            inner_ty_str, raw, raw, sentinel
        );
        self.emit_simple_wrapper_value(output, target, "option", &value_expr)
    }

    /// Wrap a comma-ok-returning call into a tagged `Option`. `tuple_flattened`
    /// distinguishes Go-imported callees that return `(T1, ..., Tn, bool)` from
    /// Lisette callees whose lowered signature is `(Tuple_n[...], bool)`.
    pub(crate) fn emit_comma_ok_wrapping(
        &mut self,
        output: &mut String,
        call_str: &str,
        option_ty: &Type,
        tuple_flattened: bool,
        target: WrapperTarget<'_>,
    ) -> WrapperOutcome {
        self.requirements.require_stdlib();

        let inner_ty = option_ty.ok_type();
        let inner_tuple_arity = inner_ty.tuple_arity();
        let needs_nilable_validation = self.facts.is_nullable_option(option_ty);

        let needs_complex =
            needs_nilable_validation || (tuple_flattened && inner_tuple_arity.is_some());

        if !needs_complex {
            let inner_ty_str = self.go_type_as_string(&inner_ty);
            let value_expr = format!("lisette.OptionFromCommaOk[{}]({})", inner_ty_str, call_str);
            return self.emit_simple_wrapper_value(output, target, "option", &value_expr);
        }

        let fallible = Fallible::from_type(option_ty).expect("Option type expected");

        let val_vars = if tuple_flattened && let Some(arity) = inner_tuple_arity {
            self.create_temp_vars("ret", arity)
        } else {
            self.create_temp_vars("ret", 1)
        };
        let ok_var = self.fresh_var(Some("ret"));
        self.declare(&ok_var);

        let all_vars: Vec<&str> = val_vars
            .iter()
            .map(|s| s.as_str())
            .chain(std::iter::once(ok_var.as_str()))
            .collect();
        write_line!(output, "{} := {}", all_vars.join(", "), call_str);

        let val_expression = if tuple_flattened && inner_tuple_arity.is_some() {
            self.build_tuple_literal(&val_vars, &inner_ty)
        } else {
            val_vars[0].clone()
        };

        let option_ty_str = {
            let mut fe = FallibleEmitter::new(self, &fallible);
            fe.full_type_string()
        };

        let condition = if self.is_interface_option(option_ty) {
            format!("{} && !lisette.IsNilInterface({})", ok_var, val_vars[0])
        } else if needs_nilable_validation {
            format!("{} && {} != nil", ok_var, val_vars[0])
        } else {
            ok_var.clone()
        };

        let (sink, outcome) = self.open_wrapper_slot(output, target, &option_ty_str, "option");
        write_line!(output, "if {} {{", condition);
        self.write_some_none_leaves(output, &sink, &fallible, &val_expression);
        output.push_str("}\n");

        outcome
    }

    /// Inside an `if cond { ... } else { ... }` block, write the success leaf
    /// (`Some(val_expression)`) and failure leaf (`None`) for an option-shaped
    /// wrapper. The caller is responsible for the surrounding `if`/`}`.
    fn write_some_none_leaves(
        &mut self,
        output: &mut String,
        sink: &ResolvedSink,
        fallible: &Fallible,
        val_expression: &str,
    ) {
        let some_wrapper = {
            let mut fe = FallibleEmitter::new(self, fallible);
            fe.emit_success(val_expression)
        };
        write_leaf(output, sink, &some_wrapper);

        output.push_str("} else {\n");

        let none_wrapper = {
            let mut fe = FallibleEmitter::new(self, fallible);
            fe.emit_failure(None)
        };
        write_leaf(output, sink, &none_wrapper);
    }

    pub(crate) fn emit_nil_check_option_wrap(
        &mut self,
        output: &mut String,
        raw_value: &str,
        option_ty: &Type,
        target: WrapperTarget<'_>,
    ) -> WrapperOutcome {
        self.requirements.require_stdlib();

        let inner_ty = option_ty.ok_type();
        let inner_ty_str = self.go_type_as_string(&inner_ty);
        let is_nil_check = if self.is_interface_option(option_ty) {
            format!("lisette.IsNilInterface({})", raw_value)
        } else {
            format!("{} == nil", raw_value)
        };
        let value_expr = format!(
            "lisette.OptionFromNilable[{}]({}, {})",
            inner_ty_str, raw_value, is_nil_check
        );
        self.emit_simple_wrapper_value(output, target, "option", &value_expr)
    }

    pub(crate) fn emit_option_projection(
        &mut self,
        output: &mut String,
        option_value: &str,
        slot_hint: &str,
        slot_ty: &str,
        address: bool,
    ) -> String {
        let opt_var = self.fresh_var(Some("opt"));
        self.declare(&opt_var);
        let slot_var = self.fresh_var(Some(slot_hint));
        self.declare(&slot_var);

        self.requirements.require_stdlib();

        let amp = if address { "&" } else { "" };
        write_line!(output, "{} := {}", opt_var, option_value);
        write_line!(output, "var {} {}", slot_var, slot_ty);
        write_line!(output, "if {}.Tag == {} {{", opt_var, OPTION_SOME_TAG);
        write_line!(output, "{} = {}{}.SomeVal", slot_var, amp, opt_var);
        output.push_str("}\n");

        slot_var
    }

    /// Wrap a Go `*T` (T value-typed) into Lisette `Option<T>`.
    pub(crate) fn emit_pointer_to_option_wrap(
        &mut self,
        output: &mut String,
        ptr_value: &str,
        option_ty: &Type,
    ) -> String {
        self.requirements.require_stdlib();
        let inner_ty_str = self.go_type_as_string(&option_ty.ok_type());
        let option_var = self.fresh_var(Some("option"));
        self.declare(&option_var);
        write_line!(
            output,
            "{} := lisette.OptionFromPointer[{}]({})",
            option_var,
            inner_ty_str,
            ptr_value
        );
        option_var
    }

    pub(crate) fn emit_collection_nullable_wrap(
        &mut self,
        output: &mut String,
        raw_value: &str,
        collection_ty: &Type,
        elem_option_ty: &Type,
    ) -> String {
        self.requirements.require_stdlib();

        let lisette_collection_ty = self.go_type_as_string(collection_ty);
        let src_var = self.fresh_var(Some("src"));
        self.declare(&src_var);
        let wrapped_var = self.fresh_var(Some("wrapped"));
        self.declare(&wrapped_var);
        let idx_var = self.fresh_var(Some("i"));
        self.declare(&idx_var);
        let val_var = self.fresh_var(Some("v"));
        self.declare(&val_var);

        write_line!(output, "{} := {}", src_var, raw_value);
        write_line!(
            output,
            "{} := make({}, len({}))",
            wrapped_var,
            lisette_collection_ty,
            src_var
        );

        write_line!(
            output,
            "for {}, {} := range {} {{",
            idx_var,
            val_var,
            src_var
        );

        let fallible = Fallible::from_type(elem_option_ty).expect("Option type expected");
        let is_pointer_bridged = self.is_non_nilable_option(elem_option_ty);

        let is_interface = self.is_interface_option(elem_option_ty);
        if is_interface {
            write_line!(output, "if !lisette.IsNilInterface({}) {{", val_var);
        } else {
            write_line!(output, "if {} != nil {{", val_var);
        }
        let some_input = if is_pointer_bridged {
            format!("*{}", val_var)
        } else {
            val_var.clone()
        };
        let mut fe = FallibleEmitter::new(self, &fallible);
        let some_wrapper = fe.emit_success(&some_input);
        write_line!(output, "{}[{}] = {}", wrapped_var, idx_var, some_wrapper);
        output.push_str("} else {\n");
        let mut fe = FallibleEmitter::new(self, &fallible);
        let none_wrapper = fe.emit_failure(None);
        write_line!(output, "{}[{}] = {}", wrapped_var, idx_var, none_wrapper);
        output.push_str("}\n");

        output.push_str("}\n");

        wrapped_var
    }

    pub(crate) fn emit_collection_nullable_unwrap(
        &mut self,
        output: &mut String,
        lisette_value: &str,
        collection_ty: &Type,
        elem_option_ty: &Type,
    ) -> String {
        self.requirements.require_stdlib();
        let shape = self.classify_collection_unwrap_shape(collection_ty, elem_option_ty);

        let src_var = self.fresh_var(Some("src"));
        self.declare(&src_var);
        let unwrapped_var = self.fresh_var(Some("unwrapped"));
        self.declare(&unwrapped_var);
        let idx_var = self.fresh_var(Some("i"));
        self.declare(&idx_var);
        let val_var = self.fresh_var(Some("v"));
        self.declare(&val_var);

        write_line!(output, "{} := {}", src_var, lisette_value);
        write_line!(
            output,
            "{} := make({}, len({}))",
            unwrapped_var,
            shape.raw_collection_ty,
            src_var
        );

        write_line!(
            output,
            "for {}, {} := range {} {{",
            idx_var,
            val_var,
            src_var
        );

        let some_assignment = if shape.is_pointer_bridged {
            format!("&{}.SomeVal", val_var)
        } else {
            format!("{}.SomeVal", val_var)
        };
        write_line!(output, "if {}.Tag == {} {{", val_var, OPTION_SOME_TAG);
        write_line!(
            output,
            "{}[{}] = {}",
            unwrapped_var,
            idx_var,
            some_assignment
        );
        if shape.emit_nil_else {
            output.push_str("} else {\n");
            write_line!(output, "{}[{}] = nil", unwrapped_var, idx_var);
        }
        output.push_str("}\n");
        output.push_str("}\n");

        unwrapped_var
    }

    /// Per-emission shape of a collection unwrap: the Go collection type, the
    /// prefix that promotes the `SomeVal` to a pointer when needed, and
    /// whether the `Some` branch needs an explicit `else nil` for the
    /// `Option<T>` => `nil`-on-`None` projection.
    fn classify_collection_unwrap_shape(
        &mut self,
        collection_ty: &Type,
        elem_option_ty: &Type,
    ) -> CollectionUnwrapShape {
        let shape = self
            .nullable_collection_shape(collection_ty)
            .expect("nullable collection unwrap requires alias-aware shape");
        let is_map = matches!(shape.kind, crate::types::shape::CollectionKind::Map);
        let is_pointer_bridged = self.is_non_nilable_option(elem_option_ty);
        let inner_ty = elem_option_ty.ok_type();
        let inner_ty_str = self.go_type_as_string(&inner_ty);
        let raw_elem_ty = if is_pointer_bridged {
            format!("*{}", inner_ty_str)
        } else {
            inner_ty_str
        };
        let raw_collection_ty = if let Some(key_ty) = shape.key_ty.as_ref() {
            let key_ty_str = self.go_type_as_string(key_ty);
            format!("map[{}]{}", key_ty_str, raw_elem_ty)
        } else {
            format!("[]{}", raw_elem_ty)
        };
        CollectionUnwrapShape {
            raw_collection_ty,
            is_pointer_bridged,
            emit_nil_else: is_map || is_pointer_bridged,
        }
    }

    pub(super) fn emit_go_single_return_option_wrapped(
        &mut self,
        output: &mut String,
        call_expression: &Expression,
        option_ty: &Type,
    ) -> String {
        let call_str = self.emit_call(output, call_expression, None, ExpressionContext::value());
        let raw_var = self.hoist_tmp_value(output, "raw", &call_str);
        self.emit_nil_check_option_wrap(output, &raw_var, option_ty, WrapperTarget::FreshSlot)
            .expect("wrapper produced no slot")
    }
}
