use fnv::{FnvHashMap, FnvHashSet};
use once_cell::sync::Lazy;
use rustpython_ast::Expr;

use crate::ast::helpers::{collect_call_paths, dealias_call_path, match_call_path};

// See: https://pypi.org/project/typing-extensions/
static TYPING_EXTENSIONS: Lazy<FnvHashSet<&'static str>> = Lazy::new(|| {
    FnvHashSet::from_iter([
        "Annotated",
        "Any",
        "AsyncContextManager",
        "AsyncGenerator",
        "AsyncIterable",
        "AsyncIterator",
        "Awaitable",
        "ChainMap",
        "ClassVar",
        "Concatenate",
        "ContextManager",
        "Coroutine",
        "Counter",
        "DefaultDict",
        "Deque",
        "Final",
        "Literal",
        "LiteralString",
        "NamedTuple",
        "Never",
        "NewType",
        "NotRequired",
        "OrderedDict",
        "ParamSpec",
        "ParamSpecArgs",
        "ParamSpecKwargs",
        "Protocol",
        "Required",
        "Self",
        "TYPE_CHECKING",
        "Text",
        "Type",
        "TypeAlias",
        "TypeGuard",
        "TypeVar",
        "TypeVarTuple",
        "TypedDict",
        "Unpack",
        "assert_never",
        "assert_type",
        "clear_overloads",
        "final",
        "get_Type_hints",
        "get_args",
        "get_origin",
        "get_overloads",
        "is_typeddict",
        "overload",
        "reveal_type",
        "runtime_checkable",
    ])
});

pub fn in_extensions(name: &str) -> bool {
    TYPING_EXTENSIONS.contains(name)
}

// See: https://docs.python.org/3/library/typing.html
const SUBSCRIPTS: &[(&str, &str)] = &[
    // builtins
    ("", "dict"),
    ("", "frozenset"),
    ("", "list"),
    ("", "set"),
    ("", "tuple"),
    ("", "type"),
    // `collections`
    ("collections", "ChainMap"),
    ("collections", "Counter"),
    ("collections", "OrderedDict"),
    ("collections", "defaultdict"),
    ("collections", "deque"),
    // `collections.abc`
    ("collections.abc", "AsyncGenerator"),
    ("collections.abc", "AsyncIterable"),
    ("collections.abc", "AsyncIterator"),
    ("collections.abc", "Awaitable"),
    ("collections.abc", "ByteString"),
    ("collections.abc", "Callable"),
    ("collections.abc", "Collection"),
    ("collections.abc", "Container"),
    ("collections.abc", "Coroutine"),
    ("collections.abc", "Generator"),
    ("collections.abc", "ItemsView"),
    ("collections.abc", "Iterable"),
    ("collections.abc", "Iterator"),
    ("collections.abc", "KeysView"),
    ("collections.abc", "Mapping"),
    ("collections.abc", "MappingView"),
    ("collections.abc", "MutableMapping"),
    ("collections.abc", "MutableSequence"),
    ("collections.abc", "MutableSet"),
    ("collections.abc", "Reversible"),
    ("collections.abc", "Sequence"),
    ("collections.abc", "Set"),
    ("collections.abc", "ValuesView"),
    // `contextlib`
    ("contextlib", "AbstractAsyncContextManager"),
    ("contextlib", "AbstractContextManager"),
    // `re`
    ("re", "Match"),
    ("re", "Pattern"),
    // `typing`
    ("typing", "AbstractSet"),
    ("typing", "AsyncContextManager"),
    ("typing", "AsyncGenerator"),
    ("typing", "AsyncIterator"),
    ("typing", "Awaitable"),
    ("typing", "BinaryIO"),
    ("typing", "ByteString"),
    ("typing", "Callable"),
    ("typing", "ChainMap"),
    ("typing", "ClassVar"),
    ("typing", "Collection"),
    ("typing", "Concatenate"),
    ("typing", "Container"),
    ("typing", "ContextManager"),
    ("typing", "Coroutine"),
    ("typing", "Counter"),
    ("typing", "DefaultDict"),
    ("typing", "Deque"),
    ("typing", "Dict"),
    ("typing", "Final"),
    ("typing", "FrozenSet"),
    ("typing", "Generator"),
    ("typing", "Generic"),
    ("typing", "IO"),
    ("typing", "ItemsView"),
    ("typing", "Iterable"),
    ("typing", "Iterator"),
    ("typing", "KeysView"),
    ("typing", "List"),
    ("typing", "Mapping"),
    ("typing", "Match"),
    ("typing", "MutableMapping"),
    ("typing", "MutableSequence"),
    ("typing", "MutableSet"),
    ("typing", "Optional"),
    ("typing", "OrderedDict"),
    ("typing", "Pattern"),
    ("typing", "Reversible"),
    ("typing", "Sequence"),
    ("typing", "Set"),
    ("typing", "TextIO"),
    ("typing", "Tuple"),
    ("typing", "Type"),
    ("typing", "TypeGuard"),
    ("typing", "Union"),
    ("typing", "Unpack"),
    ("typing", "ValuesView"),
    // `typing.io`
    ("typing.io", "BinaryIO"),
    ("typing.io", "IO"),
    ("typing.io", "TextIO"),
    // `typing.re`
    ("typing.re", "Match"),
    ("typing.re", "Pattern"),
    // `typing_extensions`
    ("typing_extensions", "AsyncContextManager"),
    ("typing_extensions", "AsyncGenerator"),
    ("typing_extensions", "AsyncIterable"),
    ("typing_extensions", "AsyncIterator"),
    ("typing_extensions", "Awaitable"),
    ("typing_extensions", "ChainMap"),
    ("typing_extensions", "ClassVar"),
    ("typing_extensions", "Concatenate"),
    ("typing_extensions", "ContextManager"),
    ("typing_extensions", "Coroutine"),
    ("typing_extensions", "Counter"),
    ("typing_extensions", "DefaultDict"),
    ("typing_extensions", "Deque"),
    ("typing_extensions", "Type"),
    // `weakref`
    ("weakref", "WeakKeyDictionary"),
    ("weakref", "WeakSet"),
    ("weakref", "WeakValueDictionary"),
];

// See: https://docs.python.org/3/library/typing.html
const PEP_583_SUBSCRIPTS: &[(&str, &str)] = &[
    // `typing`
    ("typing", "Annotated"),
    // `typing_extensions`
    ("typing_extensions", "Annotated"),
];

// See: https://peps.python.org/pep-0585/
const PEP_585_BUILTINS_ELIGIBLE: &[(&str, &str)] = &[
    ("typing", "Dict"),
    ("typing", "FrozenSet"),
    ("typing", "List"),
    ("typing", "Set"),
    ("typing", "Tuple"),
    ("typing", "Type"),
    ("typing_extensions", "Type"),
];

pub enum SubscriptKind {
    AnnotatedSubscript,
    PEP593AnnotatedSubscript,
}

pub fn match_annotated_subscript(
    expr: &Expr,
    from_imports: &FnvHashMap<&str, FnvHashSet<&str>>,
    import_aliases: &FnvHashMap<&str, &str>,
) -> Option<SubscriptKind> {
    let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
    if !call_path.is_empty() {
        for (module, member) in SUBSCRIPTS {
            if match_call_path(&call_path, module, member, from_imports) {
                return Some(SubscriptKind::AnnotatedSubscript);
            }
        }
        for (module, member) in PEP_583_SUBSCRIPTS {
            if match_call_path(&call_path, module, member, from_imports) {
                return Some(SubscriptKind::PEP593AnnotatedSubscript);
            }
        }
    }
    None
}

/// Returns `true` if `Expr` represents a reference to a typing object with a
/// PEP 585 built-in.
pub fn is_pep585_builtin(
    expr: &Expr,
    from_imports: &FnvHashMap<&str, FnvHashSet<&str>>,
    import_aliases: &FnvHashMap<&str, &str>,
) -> bool {
    let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
    if !call_path.is_empty() {
        for (module, member) in PEP_585_BUILTINS_ELIGIBLE {
            if match_call_path(&call_path, module, member, from_imports) {
                return true;
            }
        }
    }
    false
}
