use crate::ProgramEnvironment;
use std::borrow::Cow;

use itertools::Either;
use ruff_db::diagnostic::Span;
use ruff_python_ast::name::Name;
use ruff_python_stdlib::identifiers::is_identifier;
use ruff_text_size::TextRange;
use ty_module_resolver::KnownModule;

use crate::place::PlaceAndQualifiers;
use crate::place::known_module_symbol;
use crate::types::callable::{CallableFunctionProvenance, CallableTypeKind};
use crate::types::class::{DynamicClassHeaderAnchor, dynamic_class_header_range};
use crate::types::generics::GenericContext;
use crate::types::member::Member;
use crate::types::mro::Mro;
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
use crate::types::typed_dict::{
    SynthesizedTypedDictType, TypedDictField, TypedDictOpenness, TypedDictSchema,
    deferred_functional_typed_dict_openness, deferred_functional_typed_dict_schema,
};
use crate::types::{
    BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, ClassType, KnownClass,
    MemberLookupPolicy, Type, TypeContext, TypeMapping, TypeVarVariance, TypedDictModule,
    TypedDictType, UnionType, determine_upper_bound,
};
use crate::{Db, FxIndexMap};
use ty_python_core::definition::Definition;
use ty_python_core::scope::ScopeId;

pub(super) fn synthesize_typed_dict_method<'db>(
    db: &'db dyn Db,
    env: &ProgramEnvironment<'db>,
    typed_dict: TypedDictType<'db>,
    method_name: &str,
    fields: impl Fn() -> TypedDictFields<'db>,
) -> Option<Type<'db>> {
    let instance_ty = Type::TypedDict(typed_dict);
    match method_name {
        "__init__" => Some(synthesize_typed_dict_init(db, env, typed_dict, fields())),
        "__getitem__" => Some(synthesize_typed_dict_getitem(db, env, typed_dict, fields())),
        "__setitem__" => Some(synthesize_typed_dict_setitem(db, env, typed_dict, fields())),
        "__delitem__" => Some(synthesize_typed_dict_delitem(db, env, typed_dict, fields())),
        "get" => Some(synthesize_typed_dict_get(db, env, typed_dict, fields())),
        "update" => Some(synthesize_typed_dict_update(db, env, typed_dict, fields())),
        "pop" => Some(synthesize_typed_dict_pop(db, env, typed_dict, fields())),
        "setdefault" => Some(synthesize_typed_dict_setdefault(
            db,
            env,
            typed_dict,
            fields(),
        )),
        "clear" if typed_dict.supports_arbitrary_key_deletion(db) => Some(
            synthesize_typed_dict_no_argument_method(db, typed_dict, Type::none(db, env)),
        ),
        "popitem" if typed_dict.supports_arbitrary_key_deletion(db) => {
            let return_ty = Type::heterogeneous_tuple(
                db,
                env,
                [
                    KnownClass::Str.to_instance(db, env),
                    typed_dict.value_type(db, env),
                ],
            );
            Some(synthesize_typed_dict_no_argument_method(
                db, typed_dict, return_ty,
            ))
        }
        "__iter__" if typed_dict.openness(db).is_closed() => {
            let return_ty = KnownClass::Iterator.to_specialized_instance(
                db,
                env,
                &[typed_dict.key_type(db, env)],
            );
            Some(synthesize_typed_dict_no_argument_method(
                db, typed_dict, return_ty,
            ))
        }
        "items" if !typed_dict.openness(db).is_implicitly_open() => Some(
            synthesize_typed_dict_view_method(db, env, typed_dict, "dict_items"),
        ),
        "keys" if !typed_dict.openness(db).is_implicitly_open() => Some(
            synthesize_typed_dict_view_method(db, env, typed_dict, "dict_keys"),
        ),
        "values" if !typed_dict.openness(db).is_implicitly_open() => Some(
            synthesize_typed_dict_view_method(db, env, typed_dict, "dict_values"),
        ),
        "__or__" | "__ror__" | "__ior__" => Some(synthesize_typed_dict_merge(
            db,
            env,
            instance_ty,
            method_name,
        )),
        _ => None,
    }
}

/// Enum unifying the field schema for both dynamic and static `TypedDict` representations.
#[derive(Debug, Copy, Clone)]
pub(super) enum TypedDictFields<'db> {
    Dynamic(&'db TypedDictSchema<'db>),
    Static(&'db FxIndexMap<Name, super::Field<'db>>),
}

impl<'db> TypedDictFields<'db> {
    fn len(self) -> usize {
        match self {
            TypedDictFields::Dynamic(schema) => schema.len(),
            TypedDictFields::Static(fields) => fields.len(),
        }
    }

    fn iter(self) -> impl Iterator<Item = (&'db Name, Cow<'db, TypedDictField<'db>>)> {
        match self {
            TypedDictFields::Dynamic(schema) => Either::Left(
                schema
                    .iter()
                    .map(|(name, field)| (name, Cow::Borrowed(field))),
            ),
            TypedDictFields::Static(fields) => Either::Right(
                fields
                    .iter()
                    .map(|(name, field)| (name, Cow::Owned(TypedDictField::from_field(field)))),
            ),
        }
    }
}

/// Synthesize the `__init__` method for a `TypedDict`.
///
/// overloads:
/// 1. `__init__(self, __map: TD, /, *, field1: T1 = ..., field2: T2 = ...) -> None`
///    Allows passing another instance of the `TypedDict` when creating a new instance.
///    Technically, `__map` could accept a subset of the `TypedDict` if the remaining
///    fields are provided as keyword arguments. Such mixed calls use dedicated constructor
///    validation instead because this overload cannot describe the overwritten mapping entries.
///    Fields that are not valid Python identifiers are collapsed into `**kwargs`.
/// 2. `__init__(self, *, field1: T1, field2: T2 = ...) -> None`
///    Keyword-only. Fields that are not valid Python identifiers are collapsed into `**kwargs`.
fn synthesize_typed_dict_init<'db>(
    db: &'db dyn Db,
    env: &ProgramEnvironment<'db>,
    typed_dict: TypedDictType<'db>,
    fields: TypedDictFields<'db>,
) -> Type<'db> {
    let instance_ty = Type::TypedDict(typed_dict);
    // Only a bare generic class exposes a generic method. Explicit aliases already substitute
    // their arguments into both the receiver and the fields.
    let generic_context = typed_dict.defining_class().and_then(|class| {
        let alias = class.into_generic_alias()?;
        let specialization = alias.specialization(db);
        let generic_context = specialization.generic_context(db);
        (specialization == generic_context.identity_specialization(db)).then_some(generic_context)
    });
    let keyword_fields: Vec<_> = fields
        .iter()
        .filter(|(name, _)| is_identifier(name))
        .collect();

    let keyword_rest_param = typed_dict
        .explicit_extra_items(db)
        .map(|extra_items| {
            Parameter::keyword_variadic(Name::new_static("kwargs"))
                .with_annotated_type(extra_items.declared_ty)
        })
        .or_else(|| {
            (keyword_fields.len() != fields.len())
                .then(|| Parameter::keyword_variadic(Name::new_static("kwargs")))
        });

    let self_param =
        Parameter::positional_only(Some(Name::new_static("self"))).with_annotated_type(instance_ty);

    let map_param =
        Parameter::positional_only(Some(Name::new_static("map"))).with_annotated_type(instance_ty);

    let params_with_default = keyword_fields.iter().map(|(name, field)| {
        Parameter::keyword_only((*name).clone())
            .with_annotated_type(field.declared_ty)
            .with_default_type(field.declared_ty)
            .with_definition(field.first_declaration())
    });

    let map_overload = Signature::new_generic(
        generic_context,
        Parameters::standard(
            [self_param.clone(), map_param]
                .into_iter()
                .chain(params_with_default)
                .chain(keyword_rest_param.clone()),
        ),
        Type::none(db, env),
    );

    let keyword_field_params = keyword_fields.iter().map(|(name, field)| {
        let param = Parameter::keyword_only((*name).clone())
            .with_annotated_type(field.declared_ty)
            .with_definition(field.first_declaration());
        if field.is_required() {
            param
        } else {
            param.with_default_type(field.declared_ty)
        }
    });

    let keyword_overload = Signature::new_generic(
        generic_context,
        Parameters::standard(
            std::iter::once(self_param)
                .chain(keyword_field_params)
                .chain(keyword_rest_param),
        ),
        Type::none(db, env),
    );

    Type::Callable(CallableType::new(
        db,
        CallableSignature::from_overloads([map_overload, keyword_overload]),
        CallableTypeKind::FunctionLike,
        CallableFunctionProvenance::None,
    ))
}

/// Synthesize the `__getitem__` method for a `TypedDict`.
fn synthesize_typed_dict_getitem<'db>(
    db: &'db dyn Db,
    env: &ProgramEnvironment<'db>,
    typed_dict: TypedDictType<'db>,
    fields: TypedDictFields<'db>,
) -> Type<'db> {
    let instance_ty = Type::TypedDict(typed_dict);
    let overloads = fields
        .iter()
        .map(|(field_name, field)| {
            let key_type = Type::string_literal(db, field_name);
            let parameters = [
                Parameter::positional_only(Some(Name::new_static("self")))
                    .with_annotated_type(instance_ty),
                Parameter::positional_only(Some(Name::new_static("key")))
                    .with_annotated_type(key_type),
            ];
            Signature::new(Parameters::standard(parameters), field.declared_ty)
        })
        .chain(std::iter::once(Signature::new(
            Parameters::standard([
                Parameter::positional_only(Some(Name::new_static("self")))
                    .with_annotated_type(instance_ty),
                Parameter::positional_only(Some(Name::new_static("key")))
                    .with_annotated_type(KnownClass::Str.to_instance(db, env)),
            ]),
            if typed_dict.explicit_extra_items(db).is_some() {
                typed_dict.value_type(db, env)
            } else {
                Type::object()
            },
        )));

    Type::Callable(CallableType::new(
        db,
        CallableSignature::from_overloads(overloads),
        CallableTypeKind::FunctionLike,
        CallableFunctionProvenance::None,
    ))
}

/// Synthesize the `__setitem__` method for a `TypedDict`.
fn synthesize_typed_dict_setitem<'db>(
    db: &'db dyn Db,
    env: &ProgramEnvironment<'db>,
    typed_dict: TypedDictType<'db>,
    fields: TypedDictFields<'db>,
) -> Type<'db> {
    let instance_ty = Type::TypedDict(typed_dict);
    let mut writable_fields = fields
        .iter()
        .filter(|(_, field)| !field.is_read_only())
        .peekable();
    let arbitrary_key_mutation_type = typed_dict.arbitrary_key_mutation_type(db, env);

    if writable_fields.peek().is_none() && arbitrary_key_mutation_type.is_none() {
        let parameters = [
            Parameter::positional_only(Some(Name::new_static("self")))
                .with_annotated_type(instance_ty),
            Parameter::positional_only(Some(Name::new_static("key")))
                .with_annotated_type(Type::Never),
            Parameter::positional_only(Some(Name::new_static("value")))
                .with_annotated_type(Type::any()),
        ];
        let signature = Signature::new(Parameters::standard(parameters), Type::none(db, env));
        return Type::function_like_callable(db, signature);
    }

    let overloads = writable_fields
        .map(|(field_name, field)| {
            let key_type = Type::string_literal(db, field_name);
            let parameters = [
                Parameter::positional_only(Some(Name::new_static("self")))
                    .with_annotated_type(instance_ty),
                Parameter::positional_only(Some(Name::new_static("key")))
                    .with_annotated_type(key_type),
                Parameter::positional_only(Some(Name::new_static("value")))
                    .with_annotated_type(field.declared_ty),
            ];
            Signature::new(Parameters::standard(parameters), Type::none(db, env))
        })
        .chain(arbitrary_key_mutation_type.map(|value_ty| {
            let parameters = [
                Parameter::positional_only(Some(Name::new_static("self")))
                    .with_annotated_type(instance_ty),
                Parameter::positional_only(Some(Name::new_static("key")))
                    .with_annotated_type(KnownClass::Str.to_instance(db, env)),
                Parameter::positional_only(Some(Name::new_static("value")))
                    .with_annotated_type(value_ty),
            ];
            Signature::new(Parameters::standard(parameters), Type::none(db, env))
        }));

    Type::Callable(CallableType::new(
        db,
        CallableSignature::from_overloads(overloads),
        CallableTypeKind::FunctionLike,
        CallableFunctionProvenance::None,
    ))
}

/// Synthesize the `__delitem__` method for a `TypedDict`.
fn synthesize_typed_dict_delitem<'db>(
    db: &'db dyn Db,
    env: &ProgramEnvironment<'db>,
    typed_dict: TypedDictType<'db>,
    fields: TypedDictFields<'db>,
) -> Type<'db> {
    let instance_ty = Type::TypedDict(typed_dict);
    let mut deletable_fields = fields
        .iter()
        .filter(|(_, field)| !field.is_required() && !field.is_read_only())
        .peekable();
    let supports_arbitrary_key_deletion = typed_dict.supports_arbitrary_key_deletion(db);

    if deletable_fields.peek().is_none() && !supports_arbitrary_key_deletion {
        let parameters = [
            Parameter::positional_only(Some(Name::new_static("self")))
                .with_annotated_type(instance_ty),
            Parameter::positional_only(Some(Name::new_static("key")))
                .with_annotated_type(Type::Never),
        ];
        let signature = Signature::new(Parameters::standard(parameters), Type::none(db, env));
        return Type::function_like_callable(db, signature);
    }

    let overloads = deletable_fields
        .map(|(field_name, _)| {
            let key_type = Type::string_literal(db, field_name);
            let parameters = [
                Parameter::positional_only(Some(Name::new_static("self")))
                    .with_annotated_type(instance_ty),
                Parameter::positional_only(Some(Name::new_static("key")))
                    .with_annotated_type(key_type),
            ];
            Signature::new(Parameters::standard(parameters), Type::none(db, env))
        })
        .chain(supports_arbitrary_key_deletion.then(|| {
            let parameters = [
                Parameter::positional_only(Some(Name::new_static("self")))
                    .with_annotated_type(instance_ty),
                Parameter::positional_only(Some(Name::new_static("key")))
                    .with_annotated_type(KnownClass::Str.to_instance(db, env)),
            ];
            Signature::new(Parameters::standard(parameters), Type::none(db, env))
        }));

    Type::Callable(CallableType::new(
        db,
        CallableSignature::from_overloads(overloads),
        CallableTypeKind::FunctionLike,
        CallableFunctionProvenance::None,
    ))
}

/// Synthesize the `get` method for a `TypedDict`.
fn synthesize_typed_dict_get<'db>(
    db: &'db dyn Db,
    env: &ProgramEnvironment<'db>,
    typed_dict: TypedDictType<'db>,
    fields: TypedDictFields<'db>,
) -> Type<'db> {
    let instance_ty = Type::TypedDict(typed_dict);
    let fallback_value_ty = if typed_dict.openness(db).is_implicitly_open() {
        Type::unknown()
    } else {
        typed_dict.value_type(db, env)
    };
    let overloads = fields
        .iter()
        .flat_map(|(field_name, field)| {
            let key_type = Type::string_literal(db, field_name);

            let get_sig_params = [
                Parameter::positional_only(Some(Name::new_static("self")))
                    .with_annotated_type(instance_ty),
                Parameter::positional_only(Some(Name::new_static("key")))
                    .with_annotated_type(key_type),
            ];
            let get_sig = Signature::new(
                Parameters::standard(get_sig_params),
                if field.is_required() {
                    field.declared_ty
                } else {
                    UnionType::from_two_elements(db, env, field.declared_ty, Type::none(db, env))
                },
            );

            let t_default = BoundTypeVarInstance::synthetic(
                db,
                env,
                Name::new_static("T"),
                TypeVarVariance::Covariant,
            );

            let get_with_default_sig_params = [
                Parameter::positional_only(Some(Name::new_static("self")))
                    .with_annotated_type(instance_ty),
                Parameter::positional_only(Some(Name::new_static("key")))
                    .with_annotated_type(key_type),
                Parameter::positional_only(Some(Name::new_static("default")))
                    .with_annotated_type(Type::TypeVar(t_default)),
            ];
            let get_with_default_sig = Signature::new_generic(
                Some(GenericContext::from_typevar_instances(db, env, [t_default])),
                Parameters::standard(get_with_default_sig_params),
                if field.is_required() {
                    field.declared_ty
                } else {
                    UnionType::from_two_elements(
                        db,
                        env,
                        field.declared_ty,
                        Type::TypeVar(t_default),
                    )
                },
            );

            // For non-required fields, add a non-generic overload that accepts the
            // field type as the default. This is ordered before the generic TypeVar
            // overload so that `td.get("key", {})` can use the field type as
            // bidirectional inference context for the default argument.
            if field.is_required() {
                Either::Left([get_sig, get_with_default_sig].into_iter())
            } else {
                let get_with_typed_default_sig = Signature::new(
                    Parameters::standard([
                        Parameter::positional_only(Some(Name::new_static("self")))
                            .with_annotated_type(instance_ty),
                        Parameter::positional_only(Some(Name::new_static("key")))
                            .with_annotated_type(key_type),
                        Parameter::positional_only(Some(Name::new_static("default")))
                            .with_annotated_type(field.declared_ty),
                    ]),
                    field.declared_ty,
                );
                Either::Right(
                    [get_sig, get_with_typed_default_sig, get_with_default_sig].into_iter(),
                )
            }
        })
        // Fallback overloads for unknown keys
        .chain(std::iter::once(Signature::new(
            Parameters::standard([
                Parameter::positional_only(Some(Name::new_static("self")))
                    .with_annotated_type(instance_ty),
                Parameter::positional_only(Some(Name::new_static("key")))
                    .with_annotated_type(KnownClass::Str.to_instance(db, env)),
            ]),
            UnionType::from_two_elements(db, env, fallback_value_ty, Type::none(db, env)),
        )))
        .chain(std::iter::once({
            let t_default = BoundTypeVarInstance::synthetic(
                db,
                env,
                Name::new_static("T"),
                TypeVarVariance::Covariant,
            );

            let parameters = [
                Parameter::positional_only(Some(Name::new_static("self")))
                    .with_annotated_type(instance_ty),
                Parameter::positional_only(Some(Name::new_static("key")))
                    .with_annotated_type(KnownClass::Str.to_instance(db, env)),
                Parameter::positional_only(Some(Name::new_static("default")))
                    .with_annotated_type(Type::TypeVar(t_default)),
            ];

            Signature::new_generic(
                Some(GenericContext::from_typevar_instances(db, env, [t_default])),
                Parameters::standard(parameters),
                UnionType::from_two_elements(db, env, fallback_value_ty, Type::TypeVar(t_default)),
            )
        }));

    Type::Callable(CallableType::new(
        db,
        CallableSignature::from_overloads(overloads),
        CallableTypeKind::FunctionLike,
        CallableFunctionProvenance::None,
    ))
}

/// Synthesize the `update` method for a `TypedDict`.
fn synthesize_typed_dict_update<'db>(
    db: &'db dyn Db,
    env: &ProgramEnvironment<'db>,
    typed_dict: TypedDictType<'db>,
    fields: TypedDictFields<'db>,
) -> Type<'db> {
    let instance_ty = Type::TypedDict(typed_dict);
    let keyword_parameters = fields
        .iter()
        .map(|(field_name, field)| {
            let ty = if field.is_read_only() {
                Type::Never
            } else {
                field.declared_ty
            };
            Parameter::keyword_only(field_name.clone())
                .with_annotated_type(ty)
                .with_default_type(ty)
                .with_definition(field.first_declaration())
        })
        .chain(
            typed_dict
                .explicit_extra_items(db)
                .filter(|extra_items| !extra_items.is_read_only())
                .map(|extra_items| {
                    Parameter::keyword_variadic(Name::new_static("kwargs"))
                        .with_annotated_type(extra_items.declared_ty)
                }),
        );

    let update_patch_ty = Type::TypedDict(typed_dict.to_update_patch(db));

    let mapping_ty = typed_dict.dict_value_type(db, env).map(|value_ty| {
        KnownClass::Mapping.to_specialized_instance(
            db,
            env,
            &[KnownClass::Str.to_instance(db, env), value_ty],
        )
    });
    let iterable_ty = typed_dict
        .arbitrary_key_mutation_type(db, env)
        .map(|value_ty| {
            let item_ty = Type::heterogeneous_tuple(
                db,
                env,
                [KnownClass::Str.to_instance(db, env), value_ty],
            );
            KnownClass::Iterable.to_specialized_instance(db, env, &[item_ty])
        });
    let value_ty = UnionType::from_elements(
        db,
        env,
        std::iter::once(update_patch_ty)
            .chain(mapping_ty)
            .chain(iterable_ty),
    );

    let parameters = [
        Parameter::positional_only(Some(Name::new_static("self"))).with_annotated_type(instance_ty),
        Parameter::positional_only(Some(Name::new_static("value")))
            .with_annotated_type(value_ty)
            .with_default_type(Type::none(db, env)),
    ]
    .into_iter()
    .chain(keyword_parameters);

    let update_signature = Signature::new(Parameters::standard(parameters), Type::none(db, env));
    Type::function_like_callable(db, update_signature)
}

/// Synthesize the `pop` method for a `TypedDict`.
fn synthesize_typed_dict_pop<'db>(
    db: &'db dyn Db,
    env: &ProgramEnvironment<'db>,
    typed_dict: TypedDictType<'db>,
    fields: TypedDictFields<'db>,
) -> Type<'db> {
    let instance_ty = Type::TypedDict(typed_dict);
    let pop_overloads = |key_ty, value_ty| {
        let pop_parameters = [
            Parameter::positional_only(Some(Name::new_static("self")))
                .with_annotated_type(instance_ty),
            Parameter::positional_only(Some(Name::new_static("key"))).with_annotated_type(key_ty),
        ];
        let pop_sig = Signature::new(Parameters::standard(pop_parameters), value_ty);

        // Non-generic overload that accepts the value type as the default,
        // providing bidirectional inference context for the default argument.
        let pop_with_typed_default_parameters = [
            Parameter::positional_only(Some(Name::new_static("self")))
                .with_annotated_type(instance_ty),
            Parameter::positional_only(Some(Name::new_static("key"))).with_annotated_type(key_ty),
            Parameter::positional_only(Some(Name::new_static("default")))
                .with_annotated_type(value_ty),
        ];
        let pop_with_typed_default_sig = Signature::new(
            Parameters::standard(pop_with_typed_default_parameters),
            value_ty,
        );

        let t_default = BoundTypeVarInstance::synthetic(
            db,
            env,
            Name::new_static("T"),
            TypeVarVariance::Covariant,
        );
        let pop_with_default_parameters = [
            Parameter::positional_only(Some(Name::new_static("self")))
                .with_annotated_type(instance_ty),
            Parameter::positional_only(Some(Name::new_static("key"))).with_annotated_type(key_ty),
            Parameter::positional_only(Some(Name::new_static("default")))
                .with_annotated_type(Type::TypeVar(t_default)),
        ];
        let pop_with_default_sig = Signature::new_generic(
            Some(GenericContext::from_typevar_instances(db, env, [t_default])),
            Parameters::standard(pop_with_default_parameters),
            UnionType::from_two_elements(db, env, value_ty, Type::TypeVar(t_default)),
        );

        [pop_sig, pop_with_typed_default_sig, pop_with_default_sig]
    };

    let overloads = fields
        .iter()
        .filter(|(_, field)| !field.is_required() && !field.is_read_only())
        .flat_map(|(field_name, field)| {
            pop_overloads(Type::string_literal(db, field_name), field.declared_ty)
        })
        .chain(
            typed_dict
                .supports_arbitrary_key_deletion(db)
                .then(|| {
                    pop_overloads(
                        KnownClass::Str.to_instance(db, env),
                        typed_dict.value_type(db, env),
                    )
                })
                .into_iter()
                .flatten(),
        );

    Type::Callable(CallableType::new(
        db,
        CallableSignature::from_overloads(overloads),
        CallableTypeKind::FunctionLike,
        CallableFunctionProvenance::None,
    ))
}

/// Synthesize the `setdefault` method for a `TypedDict`.
fn synthesize_typed_dict_setdefault<'db>(
    db: &'db dyn Db,
    env: &ProgramEnvironment<'db>,
    typed_dict: TypedDictType<'db>,
    fields: TypedDictFields<'db>,
) -> Type<'db> {
    let instance_ty = Type::TypedDict(typed_dict);
    let overloads = fields
        .iter()
        .filter(|(_, field)| !field.is_read_only())
        .map(|(field_name, field)| {
            let key_type = Type::string_literal(db, field_name);
            let parameters = [
                Parameter::positional_only(Some(Name::new_static("self")))
                    .with_annotated_type(instance_ty),
                Parameter::positional_only(Some(Name::new_static("key")))
                    .with_annotated_type(key_type),
                Parameter::positional_only(Some(Name::new_static("default")))
                    .with_annotated_type(field.declared_ty),
            ];

            Signature::new(Parameters::standard(parameters), field.declared_ty)
        })
        .chain(
            typed_dict
                .arbitrary_key_mutation_type(db, env)
                .map(|default_ty| {
                    let parameters = [
                        Parameter::positional_only(Some(Name::new_static("self")))
                            .with_annotated_type(instance_ty),
                        Parameter::positional_only(Some(Name::new_static("key")))
                            .with_annotated_type(KnownClass::Str.to_instance(db, env)),
                        Parameter::positional_only(Some(Name::new_static("default")))
                            .with_annotated_type(default_ty),
                    ];
                    Signature::new(
                        Parameters::standard(parameters),
                        typed_dict.value_type(db, env),
                    )
                }),
        );

    Type::Callable(CallableType::new(
        db,
        CallableSignature::from_overloads(overloads),
        CallableTypeKind::FunctionLike,
        CallableFunctionProvenance::None,
    ))
}

fn synthesize_typed_dict_no_argument_method<'db>(
    db: &'db dyn Db,
    typed_dict: TypedDictType<'db>,
    return_ty: Type<'db>,
) -> Type<'db> {
    Type::function_like_callable(
        db,
        Signature::new(
            Parameters::standard([Parameter::positional_only(Some(Name::new_static("self")))
                .with_annotated_type(Type::TypedDict(typed_dict))]),
            return_ty,
        ),
    )
}

/// Synthesize `items`, `keys`, or `values` for a closed or extra-items `TypedDict`.
fn synthesize_typed_dict_view_method<'db>(
    db: &'db dyn Db,
    env: &ProgramEnvironment<'db>,
    typed_dict: TypedDictType<'db>,
    view_name: &str,
) -> Type<'db> {
    let return_ty = known_module_symbol(db, env, KnownModule::CollectionsAbcInternal, view_name)
        .place
        .ignore_possibly_undefined()
        .and_then(Type::as_class_literal)
        .map(|class| {
            class.apply_specialization(db, |generic_context| {
                generic_context.specialize(
                    db,
                    &[typed_dict.key_type(db, env), typed_dict.value_type(db, env)],
                )
            })
        })
        .and_then(|class| Type::from(class).to_instance_approximation(db, env))
        .unwrap_or_else(Type::unknown);

    synthesize_typed_dict_no_argument_method(db, typed_dict, return_ty)
}

/// Synthesize a merge operator (`__or__`, `__ror__`, or `__ior__`) for a `TypedDict`.
fn synthesize_typed_dict_merge<'db>(
    db: &'db dyn Db,
    env: &ProgramEnvironment<'db>,
    instance_ty: Type<'db>,
    name: &str,
) -> Type<'db> {
    let mut overloads: smallvec::SmallVec<[Signature<'db>; 3]>;

    let first_overload_value_ty = if name == "__ior__"
        && let Type::TypedDict(typed_dict) = instance_ty
    {
        Type::TypedDict(typed_dict.to_update_patch(db))
    } else {
        instance_ty
    };

    let first_overload_parameters = [
        Parameter::positional_only(Some(Name::new_static("self"))).with_annotated_type(instance_ty),
        Parameter::positional_only(Some(Name::new_static("value")))
            .with_annotated_type(first_overload_value_ty),
    ];

    overloads = smallvec::smallvec![Signature::new(
        Parameters::standard(first_overload_parameters),
        instance_ty,
    )];

    if name != "__ior__" {
        let partial_ty = if let Type::TypedDict(td) = instance_ty {
            Type::TypedDict(td.to_partial(db))
        } else {
            instance_ty
        };

        let dict_param_ty = KnownClass::Dict.to_specialized_instance(
            db,
            env,
            &[KnownClass::Str.to_instance(db, env), Type::any()],
        );

        let dict_return_ty = KnownClass::Dict.to_specialized_instance(
            db,
            env,
            &[
                KnownClass::Str.to_instance(db, env),
                KnownClass::Object.to_instance(db, env),
            ],
        );

        let overload_two_parameters = [
            Parameter::positional_only(Some(Name::new_static("self")))
                .with_annotated_type(instance_ty),
            Parameter::positional_only(Some(Name::new_static("value")))
                .with_annotated_type(partial_ty),
        ];
        overloads.push(Signature::new(
            Parameters::standard(overload_two_parameters),
            instance_ty,
        ));

        let overload_three_parameters = [
            Parameter::positional_only(Some(Name::new_static("self")))
                .with_annotated_type(instance_ty),
            Parameter::positional_only(Some(Name::new_static("value")))
                .with_annotated_type(dict_param_ty),
        ];
        overloads.push(Signature::new(
            Parameters::standard(overload_three_parameters),
            dict_return_ty,
        ));
    }

    Type::Callable(CallableType::new(
        db,
        CallableSignature::from_overloads(overloads),
        CallableTypeKind::FunctionLike,
        CallableFunctionProvenance::None,
    ))
}

/// Represents a `TypedDict` created via the functional form:
/// ```python
/// Movie = TypedDict("Movie", {"name": str, "year": int})
/// Movie = TypedDict("Movie", {"name": str, "year": int}, total=False)
/// ```
///
/// The type of `Movie` would be `type[Movie]` where `Movie` is a `DynamicTypedDictLiteral`.
///
/// The field schema is represented by a separate [`TypedDictSchema`].
#[derive(Clone, Debug, PartialEq, Eq, Hash, get_size2::GetSize, salsa::SalsaValue)]
pub enum DynamicTypedDictAnchor<'db> {
    /// The `TypedDict()` call is assigned to a variable.
    ///
    /// The `Definition` uniquely identifies this `TypedDict`. Field types are computed lazily
    /// during deferred inference so recursive `TypedDict` definitions can resolve correctly.
    Definition(Definition<'db>),

    /// The `TypedDict()` call is "dangling" (not assigned to a variable).
    ///
    /// The offset is relative to the enclosing scope's anchor node index. The eagerly
    /// computed `spec` preserves field types for inline uses like
    /// `TypedDict("Point", {"x": int})(x=1)`.
    ScopeOffset {
        scope: ScopeId<'db>,
        offset: u32,
        schema: TypedDictSchema<'db>,
        openness: TypedDictOpenness<'db>,
    },
}

impl<'db> DynamicTypedDictAnchor<'db> {
    fn recursive_type_normalized_impl(
        &self,
        db: &'db dyn Db,
        env: &ProgramEnvironment<'db>,
        div: Type<'db>,
        nested: bool,
    ) -> Option<Self> {
        match self {
            Self::Definition(definition) => Some(Self::Definition(*definition)),
            Self::ScopeOffset {
                scope,
                offset,
                schema,
                openness,
            } => Some(Self::ScopeOffset {
                scope: *scope,
                offset: *offset,
                schema: schema.recursive_type_normalized_impl(db, env, div, nested)?,
                openness: openness.recursive_type_normalized_impl(db, env, div, nested)?,
            }),
        }
    }
}

#[salsa::interned(debug, heap_size = ruff_memory_usage::heap_size)]
pub struct DynamicTypedDictLiteral<'db> {
    /// The name of the TypedDict (from the first argument).
    #[returns(ref)]
    pub(crate) name: Name,

    /// The anchor for this dynamic TypedDict, providing stable identity.
    ///
    /// - `Definition`: The call is assigned to a variable. The definition
    ///   uniquely identifies this TypedDict and can be used to find the call.
    /// - `ScopeOffset`: The call is "dangling" (not assigned). The offset
    ///   is relative to the enclosing scope's anchor node index, and the
    ///   eagerly computed spec is stored on the anchor.
    #[returns(ref)]
    pub(crate) anchor: DynamicTypedDictAnchor<'db>,

    #[returns(copy)]
    pub(crate) typed_dict_module: TypedDictModule,
}

impl get_size2::GetSize for DynamicTypedDictLiteral<'_> {}

impl<'db> DynamicTypedDictLiteral<'db> {
    pub(super) fn recursive_type_normalized_impl(
        self,
        db: &'db dyn Db,
        env: &ProgramEnvironment<'db>,
        div: Type<'db>,
        nested: bool,
    ) -> Option<Self> {
        Some(Self::new(
            db,
            self.name(db),
            self.anchor(db)
                .recursive_type_normalized_impl(db, env, div, nested)?,
            self.typed_dict_module(db),
        ))
    }
}

#[salsa::tracked]
impl<'db> DynamicTypedDictLiteral<'db> {
    /// Returns the definition where this `TypedDict` is created, if it was assigned to a variable.
    pub(crate) fn definition(self, db: &'db dyn Db) -> Option<Definition<'db>> {
        match self.anchor(db) {
            DynamicTypedDictAnchor::Definition(definition) => Some(*definition),
            DynamicTypedDictAnchor::ScopeOffset { .. } => None,
        }
    }

    /// Returns the scope in which this dynamic `TypedDict` was created.
    pub(crate) fn scope(self, db: &'db dyn Db) -> ScopeId<'db> {
        match self.anchor(db) {
            DynamicTypedDictAnchor::Definition(definition) => definition.scope(db),
            DynamicTypedDictAnchor::ScopeOffset { scope, .. } => *scope,
        }
    }

    /// Returns the range of the `TypedDict` call expression.
    pub(crate) fn header_range(self, db: &'db dyn Db) -> TextRange {
        let anchor = match self.anchor(db) {
            DynamicTypedDictAnchor::Definition(definition) => {
                DynamicClassHeaderAnchor::Definition(*definition)
            }
            DynamicTypedDictAnchor::ScopeOffset { offset, .. } => {
                DynamicClassHeaderAnchor::ScopeOffset(*offset)
            }
        };
        dynamic_class_header_range(db, self.scope(db), anchor)
    }

    /// Returns a [`Span`] pointing to the `TypedDict` call expression.
    pub(super) fn header_span(self, db: &'db dyn Db) -> Span {
        Span::from(self.scope(db).file(db)).with_range(self.header_range(db))
    }

    pub(crate) fn items(self, db: &'db dyn Db) -> &'db TypedDictSchema<'db> {
        match self.anchor(db) {
            DynamicTypedDictAnchor::Definition(definition) => {
                deferred_functional_typed_dict_schema(db, *definition)
            }
            DynamicTypedDictAnchor::ScopeOffset { schema, .. } => schema,
        }
    }

    pub(crate) fn openness(self, db: &'db dyn Db) -> TypedDictOpenness<'db> {
        match self.anchor(db) {
            DynamicTypedDictAnchor::Definition(definition) => {
                deferred_functional_typed_dict_openness(db, *definition)
            }
            DynamicTypedDictAnchor::ScopeOffset { openness, .. } => *openness,
        }
    }

    /// Get the MRO for this `TypedDict`.
    ///
    /// Functional `TypedDict` classes have the same MRO as class-based ones:
    /// [self, `TypedDict`, object]
    #[salsa::tracked(returns(ref), heap_size = ruff_memory_usage::heap_size)]
    pub(crate) fn mro(self, db: &'db dyn Db) -> Mro<'db> {
        let self_base = ClassBase::Class(ClassType::NonGeneric(self.into()));
        let env = ProgramEnvironment::from_scope(self.scope(db));
        let object_class = ClassType::object(db, &env);
        Mro::from([
            self_base,
            ClassBase::TypedDict(self.typed_dict_module(db)),
            ClassBase::Class(object_class),
        ])
    }

    /// Returns the metaclass of this `TypedDict`.
    ///
    /// `TypedDict`s use `type` as their metaclass.
    pub(crate) fn metaclass(self, db: &'db dyn Db) -> Type<'db> {
        let env = ProgramEnvironment::from_scope(self.scope(db));
        KnownClass::Type.to_class_literal(db, &env)
    }

    /// Look up a class-level member defined directly on this `TypedDict` (not inherited).
    pub(super) fn own_class_member(self, db: &'db dyn Db, name: &str) -> Member<'db> {
        let env = ProgramEnvironment::from_scope(self.scope(db));
        let typed_dict =
            TypedDictType::new(ClassType::NonGeneric(ClassLiteral::DynamicTypedDict(self)));
        synthesize_typed_dict_method(db, &env, typed_dict, name, || {
            TypedDictFields::Dynamic(self.items(db))
        })
        .map(Member::definitely_declared)
        .unwrap_or_default()
    }

    /// Look up a class-level member by name (including superclasses).
    pub(crate) fn class_member(
        self,
        db: &'db dyn Db,
        env: &ProgramEnvironment<'db>,
        name: &str,
        policy: MemberLookupPolicy,
    ) -> PlaceAndQualifiers<'db> {
        // First check synthesized members (like __getitem__, __init__, get, etc.).
        let member = self.own_class_member(db, name);
        if !member.is_undefined() {
            return member.inner;
        }

        // Fall back to TypedDictFallback for methods like __contains__, items, keys, etc.
        // This mirrors the behavior of StaticClassLiteral::typed_dict_member.
        typed_dict_class_member(
            db,
            env,
            ClassType::NonGeneric(ClassLiteral::DynamicTypedDict(self)),
            self.typed_dict_module(db),
            policy,
            name,
        )
    }
}

/// Resolves members of a schema that has no defining `TypedDict` class.
pub(in crate::types) fn synthesized_typed_dict_class_member<'db>(
    db: &'db dyn Db,
    env: &ProgramEnvironment<'db>,
    synthesized: SynthesizedTypedDictType<'db>,
    lookup_policy: MemberLookupPolicy,
    name: &str,
) -> PlaceAndQualifiers<'db> {
    let typed_dict = TypedDictType::Synthesized(synthesized);

    if let Some(member) = synthesize_typed_dict_method(db, env, typed_dict, name, || {
        TypedDictFields::Dynamic(synthesized.items(db))
    }) {
        return Member::definitely_declared(member).inner;
    }

    typed_dict_inherited_class_member(
        db,
        env,
        typed_dict,
        TypedDictModule::Typing,
        lookup_policy,
        name,
        || Type::TypedDict(typed_dict),
    )
}

pub(super) fn typed_dict_fallback_class_member<'db>(
    db: &'db dyn Db,
    env: &ProgramEnvironment<'db>,
    module: TypedDictModule,
    lookup_policy: MemberLookupPolicy,
    name: &str,
) -> PlaceAndQualifiers<'db> {
    let fallback = match module {
        TypedDictModule::Typing => KnownClass::TypedDictFallback,
        TypedDictModule::TypingExtensions => KnownClass::ExtensionTypedDictFallback,
    };

    fallback
        .to_class_literal(db, env)
        .find_name_in_mro_with_policy(db, env, name, lookup_policy)
        .expect("Will return Some() when called on class literal")
}

pub(super) fn typed_dict_class_member<'db>(
    db: &'db dyn Db,
    env: &ProgramEnvironment<'db>,
    class: ClassType<'db>,
    module: TypedDictModule,
    lookup_policy: MemberLookupPolicy,
    name: &str,
) -> PlaceAndQualifiers<'db> {
    let self_class = class.class_literal(db);

    typed_dict_inherited_class_member(
        db,
        env,
        TypedDictType::new(class),
        module,
        lookup_policy,
        name,
        || determine_upper_bound(db, env, self_class, ClassBase::is_typed_dict),
    )
}

fn typed_dict_inherited_class_member<'db>(
    db: &'db dyn Db,
    env: &ProgramEnvironment<'db>,
    typed_dict: TypedDictType<'db>,
    module: TypedDictModule,
    lookup_policy: MemberLookupPolicy,
    name: &str,
    new_upper_bound: impl FnOnce() -> Type<'db>,
) -> PlaceAndQualifiers<'db> {
    let fallback_member = typed_dict_fallback_class_member(db, env, module, lookup_policy, name)
        .map_type(|ty| {
            let mapping = TypeMapping::ReplaceSelf {
                new_upper_bound: new_upper_bound(),
            };
            ty.apply_type_mapping(db, env, &mapping, TypeContext::default())
        });
    if !fallback_member.is_undefined() {
        return fallback_member;
    }

    if let Some(value_ty) = typed_dict.dict_value_type(db, env)
        && let Some(dict_class) = KnownClass::Dict.to_specialized_class_type(
            db,
            env,
            &[KnownClass::Str.to_instance(db, env), value_ty],
        )
    {
        let member = dict_class.class_member(db, env, name, lookup_policy);
        if !member.is_undefined() {
            return member;
        }
    }

    fallback_member
}
