import os
from typing import Iterable, Optional

from platypush.backend import Backend
from platypush.context import get_plugin
from platypush.plugins import Plugin
from platypush.utils.manifest import get_manifests


def _get_inspect_plugin():
    p = get_plugin('inspect')
    assert p, 'Could not load the `inspect` plugin'
    return p


def get_all_plugins():
    return sorted([mf.component_name for mf in get_manifests(Plugin)])


def get_all_backends():
    return sorted([mf.component_name for mf in get_manifests(Backend)])


def get_all_events():
    return _get_inspect_plugin().get_all_events().output


def get_all_responses():
    return _get_inspect_plugin().get_all_responses().output


def _generate_components_doc(
    index_name: str,
    package_name: str,
    components: Iterable[str],
    doc_dir: Optional[str] = None,
):
    if not doc_dir:
        doc_dir = index_name

    index_file = os.path.join(
        os.path.dirname(os.path.abspath(__file__)),
        'docs',
        'source',
        f'{index_name}.rst',
    )
    docs_dir = os.path.join(
        os.path.dirname(os.path.abspath(__file__)),
        'docs',
        'source',
        'platypush',
        doc_dir,
    )

    for comp in components:
        comp_file = os.path.join(docs_dir, comp + '.rst')
        if not os.path.exists(comp_file):
            comp = f'platypush.{package_name}.{comp}'
            header = '``' + '.'.join(comp.split('.')[2:]) + '``'
            divider = '=' * len(header)
            body = f'\n.. automodule:: {comp}\n    :members:\n'
            out = '\n'.join([header, divider, body])

            with open(comp_file, 'w') as f:
                f.write(out)

    with open(index_file, 'w') as f:
        f.write(
            f'''
{index_name.title()}
{''.join(['='] * len(index_name))}

.. toctree::
    :maxdepth: 1
    :caption: {index_name.title()}:

'''
        )

        for comp in components:
            f.write(f'    platypush/{doc_dir}/{comp}.rst\n')

    _cleanup_removed_components_docs(docs_dir, components)


def _cleanup_removed_components_docs(docs_dir: str, components: Iterable[str]):
    new_components = set(components)
    existing_files = {
        os.path.join(root, file)
        for root, _, files in os.walk(docs_dir)
        for file in files
        if file.endswith('.rst')
    }

    files_to_remove = {
        file
        for file in existing_files
        if os.path.basename(file).removesuffix('.rst') not in new_components
    }

    for file in files_to_remove:
        print(f'Removing unlinked component {file}')
        os.unlink(file)


def generate_plugins_doc():
    _generate_components_doc(
        index_name='plugins', package_name='plugins', components=get_all_plugins()
    )


def generate_backends_doc():
    _generate_components_doc(
        index_name='backends',
        package_name='backend',
        components=get_all_backends(),
        doc_dir='backend',
    )


def generate_events_doc():
    _generate_components_doc(
        index_name='events',
        package_name='message.event',
        components=sorted(event for event in get_all_events().keys() if event),
    )


def generate_responses_doc():
    _generate_components_doc(
        index_name='responses',
        package_name='message.response',
        components=sorted(
            response for response in get_all_responses().keys() if response
        ),
    )


def main():
    generate_plugins_doc()
    generate_backends_doc()
    generate_events_doc()
    generate_responses_doc()


if __name__ == '__main__':
    main()


# vim:sw=4:ts=4:et: