#!/usr/bin/env python
# License: GPL v3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>

import importlib
import os
from pprint import pprint

QT_WRAPPER = 'PyQt6'

base = os.path.dirname(os.path.abspath(__file__))
module_lists = {
    'core': (
        'QtCore',
        'QtGui',
        'QtWidgets',
        'QtNetwork',
        'QtSvg',
        'QtPrintSupport',
        'QtOpenGL',
        'QtOpenGLWidgets',
        'QtQuick',
        'QtMultimedia',
        'QtMultimediaWidgets',
        'QtTextToSpeech',
    ),
    'webengine': (
        'QtWebEngineCore',
        'QtWebEngineWidgets',
        'QtWebChannel',
    ),
    'dbus': ('QtDBus',),
}


special_case = {
    'pyqtProperty': '''

_T = TypeVar('_T')
_R = TypeVar('_R')

class pyqtProperty:
    # --- Overload 1: Used as a decorator factory (e.g., @pyqtProperty(int)) ---
    @overload
    def __init__(
        self,
        type: type[_R] | str,
        fget: None = None,
        fset: None = None,
        freset: None = None,
        fdel: None = None,
        doc: str | None = None,
        designable: bool = True,
        scriptable: bool = True,
        stored: bool = True,
        user: bool = False,
        constant: bool = False,
        final: bool = False,
        notify: pyqtSignal | None = None,
        revision: int = 0,
    ) -> None: ...

    # --- Overload 2: Used as a functional constructor (e.g., x = pyqtProperty(int, get_x, set_x)) ---
    @overload
    def __init__(
        self,
        type: type[_R] | str,
        fget: Callable[[Any], _R],
        fset: Callable[[Any, _R], None] | None = None,
        freset: Callable[[Any], None] | None = None,
        fdel: Callable[[Any], None] | None = None,
        doc: str | None = None,
        designable: bool = True,
        scriptable: bool = True,
        stored: bool = True,
        user: bool = False,
        constant: bool = False,
        final: bool = False,
        notify: pyqtSignal | None = None,
        revision: int = 0,
    ) -> None: ...

    # --- Behaviors required to act like a standard Python property descriptor ---
    def __call__(self, fget: Callable[[_T], _R]) -> pyqtProperty: ...
    def __get__(self, instance: _T, owner: type[_T] | None = None) -> _R: ...
    def __set__(self, instance: _T, value: _R) -> None: ...
    def __delete__(self, instance: _T) -> None: ...

    def setter(self, fset: Callable[[_T, _R], None]) -> pyqtProperty: ...
    def deleter(self, fdel: Callable[[_T], None]) -> pyqtProperty: ...
''',
    # The following ty thinks dont exist probably cause they are missing from stub
    # files
    'QtAudio': '',
    'QStyleOptionMenuItemV2': '',
    'QPyAbstractRange': '',
    'QPySequenceRange': '',
    'QPyTableRange': '',
    'QRangeModel': '',
}


def scan(name):
    module_names = module_lists[name]
    name_map = {}
    types = []
    blocks = []
    for mod_name in module_names:
        mod = importlib.import_module(f'{QT_WRAPPER}.{mod_name}')
        full_name = name_map[mod_name] = mod.__name__
        types.append(f'import {full_name}')
        for obj_name in sorted(dir(mod)):
            if not obj_name.startswith('_') and obj_name not in name_map:
                name_map[obj_name] = full_name
                if (block := special_case.get(obj_name)) is not None:
                    blocks.append(block)
                else:
                    types.append(f'{obj_name} = {full_name}.{obj_name}')
    with open(f'{base}/{name}.py', 'w') as f:
        print('# autogenerated by __main__.py do not edit', file=f)
        print(f'from .{name}_name_map import module_names, name_map', file=f)
        print(
            '''from .loader import dynamic_load

already_imported = {}
qt_modules = {}

def __getattr__(name):
    return dynamic_load(name, name_map, already_imported, qt_modules, module_names)
''',
            end='',
            file=f,
        )
    if name == 'core':
        module_names += ('sip',)
        mod = importlib.import_module(f'{QT_WRAPPER}.sip')
        name_map['sip'] = mod.__name__
        types.append(f'import {mod.__name__}')
        types.append(f'sip = {mod.__name__}')

    with open(f'{base}/{name}.pyi', 'w') as f:
        print('# autogenerated by __main__.py do not edit', file=f)
        if name == 'core':
            print('from collections.abc import Callable', file=f)
            print('from typing import Any, TypeVar, overload', file=f)
        print('\n'.join(types), file=f)
        for block in blocks:
            if block:
                print(file=f)
                print(block.strip(), file=f)
    with open(f'{base}/{name}_name_map.py', 'w') as f:
        print('# autogenerated by __main__.py do not edit', file=f)
        print('name_map =', end=' ', file=f)
        pprint(name_map, stream=f)
        print('module_names = frozenset(', end='', file=f)
        pprint(module_names, stream=f)
        print(')', file=f)


top_level_module_names = ()
for name, module_name in module_lists.items():
    top_level_module_names += module_name
    scan(name)
with open(f'{base}/__init__.py', 'w') as f:
    print('# autogenerated by __main__.py do not edit', file=f)
    print('top_level_module_names = ', end='', file=f)
    pprint(top_level_module_names, stream=f)
    print(
        f'''

def __getattr__(name):
    if name in top_level_module_names:
        import importlib
        return importlib.import_module('{QT_WRAPPER}.' + name)
    raise AttributeError(name)
''',
        end='',
        file=f,
    )
