# -----------------------------------------------------------------------------
# Copyright (c) 2012 - 2022, Anaconda, Inc., and Bokeh Contributors.
# All rights reserved.
#
# The full license is in the file LICENSE.txt, distributed with this software.
# -----------------------------------------------------------------------------
""" Thoroughly document Bokeh options classes.

The ``bokeh-options`` directive will automatically document all the properties
of a Bokeh Options class under a heading of "Keyword Args".

This directive takes the name of a Bokeh Options subclass as the argument, and
its module as an option:

.. code-block:: rest

    .. bokeh-options:: Opts
        :module: bokeh.sphinxext.sample

Examples
--------

For the following definition of ``bokeh.sphinxext.sample.Opts``:

.. code-block:: python

    class Opts(Options):
        ''' This is an Options class '''

        host = String(default="localhost", help="a host to connect to")
        port = Int(default=5890, help="a port to connect to")

the above usage yields the output:

    .. bokeh-options:: Opts
        :module: bokeh.sphinxext.sample

"""

# -----------------------------------------------------------------------------
# Boilerplate
# -----------------------------------------------------------------------------
from __future__ import annotations

import logging  # isort:skip

log = logging.getLogger(__name__)

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------

# Standard library imports
import importlib
import textwrap

# External imports
from docutils.parsers.rst.directives import unchanged
from sphinx.errors import SphinxError

# Bokeh imports
from bokeh.core.property._sphinx import type_link
from bokeh.util.options import Options

# Bokeh imports
from . import PARALLEL_SAFE
from .bokeh_directive import BokehDirective, py_sig_re
from .templates import OPTIONS_DETAIL

# -----------------------------------------------------------------------------
# Globals and constants
# -----------------------------------------------------------------------------

__all__ = (
    "BokehOptionsDirective",
    "setup",
)

# -----------------------------------------------------------------------------
# General API
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Dev API
# -----------------------------------------------------------------------------


class BokehOptionsDirective(BokehDirective):

    has_content = True
    required_arguments = 1
    optional_arguments = 1
    option_spec = {"module": unchanged}

    def run(self):
        sig = " ".join(self.arguments)

        m = py_sig_re.match(sig)
        if m is None:
            raise SphinxError(f"Unable to parse signature for bokeh-options: {sig!r}")
        name_prefix, options_name, arglist, retann = m.groups()

        module_name = self.options["module"]

        try:
            module = importlib.import_module(module_name)
        except ImportError:
            raise SphinxError(f"Unable to generate options reference docs for {options_name}, couldn't import module {module_name}")

        options = getattr(module, options_name, None)
        if options is None:
            raise SphinxError(f"Unable to generate options reference docs: no options {options_name} in module {module_name}")

        if not issubclass(options, Options):
            raise SphinxError(f"Unable to generate options reference docs: {options_name} is not a subclass of Options")

        options_obj = options({})

        opts = []
        for prop_name in sorted(options_obj.properties()):
            descriptor = options_obj.lookup(prop_name)
            opts.append(
                dict(
                    name=prop_name,
                    type=type_link(descriptor.property),
                    default=repr(descriptor.instance_default(options_obj)),
                    doc="" if descriptor.__doc__ is None else textwrap.dedent(descriptor.__doc__.rstrip()),
                )
            )

        rst_text = OPTIONS_DETAIL.render(opts=opts)

        return self.parse(rst_text, "<bokeh-options>")


def setup(app):
    """ Required Sphinx extension setup function. """
    app.add_directive_to_domain("py", "bokeh-options", BokehOptionsDirective)

    return PARALLEL_SAFE

# -----------------------------------------------------------------------------
# Private API
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------
