# -- Path setup --------------------------------------------------------------

import re
import sys
import warnings
import os
import subprocess

import snapatac2

# -- Software version --------------------------------------------------------

# The short X.Y version (including .devXXXX, rcX, b1 suffixes if present)
version = re.sub(r'(\d+\.\d+)\.\d+(.*)', r'\1\2', snapatac2.__version__)
version = re.sub(r'(\.dev\d+).*?$', r'\1', version)

# The full version, including alpha/beta/rc tags.
release = snapatac2.__version__

# pyData/Sphinx-Theme version switcher
if ".dev" in version:
    switcher_version = "dev"
else:
    switcher_version = f"{version}"

print(f'Building documentation for SnapATAC2 {release} (short version: {version}, switcher version: {switcher_version})')

# -- Project information -----------------------------------------------------

project = 'SnapATAC2'
copyright = '2022-2025, Kai Zhang'
author = 'Kai Zhang'

# -- General configuration ---------------------------------------------------

suppress_warnings = ['ref.citation']
default_role = 'code'
add_function_parentheses = False

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    "nbsphinx",
    "myst_parser",
    "sphinx.ext.autodoc",
    "sphinx.ext.intersphinx",
    "sphinx.ext.autosummary",
    "sphinx.ext.coverage",
    "sphinx.ext.mathjax",
    "sphinx.ext.napoleon",
    "sphinx.ext.linkcode",
    "sphinx_autodoc_typehints",
    "sphinx_plotly_directive",
]

source_suffix = {
    '.rst': 'restructuredtext',
    '.txt': 'markdown',
    '.md': 'markdown',
}

myst_enable_extensions = [
    "amsmath",
    #"colon_fence",
    #"deflist",
    "dollarmath",
    #"fieldlist",
    #"html_admonition",
    #"html_image",
    #"linkify",
    #"replacements",
    #"smartquotes",
    #"strikethrough",
    #"substitution",
    #"tasklist",
]

# Generate the API documentation when building
autosummary_generate = True
autodoc_member_order = 'bysource'
# autodoc_default_flags = ['members']
napoleon_google_docstring = False
napoleon_numpy_docstring = True
napoleon_include_init_with_doc = False
napoleon_use_rtype = True  # having a separate entry generally helps readability
napoleon_use_param = True
napoleon_custom_sections = [('Params', 'Parameters')]
todo_include_todos = False

intersphinx_mapping = {
    "anndata": ("https://anndata.readthedocs.io/en/stable/", None),
    "ipython": ("https://ipython.readthedocs.io/en/stable/", None),
    "matplotlib": ("https://matplotlib.org/", None),
    "numpy": ("https://numpy.org/doc/stable/", None),
    "pandas": ("https://pandas.pydata.org/docs/", None),
    "python": ("https://docs.python.org/3", None),
    "scipy": ("https://docs.scipy.org/doc/scipy/reference/", None),
    "sklearn": ("https://scikit-learn.org/stable/", None),
    "scanpy": ("https://scanpy.readthedocs.io/en/stable/", None),
}

# Configure autodoc to handle import errors gracefully
autodoc_mock_imports = []

def resolve_py_module(name):
    """Resolve Python module aliases used in documentation."""
    import sys
    if name == 'pl':
        return sys.modules.get('snapatac2.plotting')
    elif name == 'tl':
        return sys.modules.get('snapatac2.tools')
    elif name == 'pp':
        return sys.modules.get('snapatac2.preprocessing')
    elif name == 'ex':
        return sys.modules.get('snapatac2.export')
    return None

def setup(app):
    """Configure Sphinx application."""
    # Register the aliases in sys.modules for Sphinx to find them
    import snapatac2
    import sys
    
    sys.modules['snapatac2.pl'] = snapatac2.pl
    sys.modules['snapatac2.tl'] = snapatac2.tl
    sys.modules['snapatac2.pp'] = snapatac2.pp
    sys.modules['snapatac2.ex'] = snapatac2.ex

smv_branch_whitelist = r'main'  # Include all branches

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']


# -- Options for HTML output -------------------------------------------------

html_theme = 'pydata_sphinx_theme'
html_show_sphinx = False
html_show_sourcelink = False
html_static_path = ['_static']
html_css_files = [
    'css/custom.css',
]

html_theme_options = {
    "logo": {
        "text": "SnapATAC2",
        #"image_dark": "_static/logo-dark.svg",
        "alt_text": "SnapATAC2",
    },

    "github_url": "https://github.com/scverse/SnapATAC2",
    "external_links": [
        {"name": "Learn", "url": "https://kzhang.org/epigenomics-analysis/"}
    ],
    "header_links_before_dropdown": 6,

    "navbar_center": ["version-switcher", "navbar-nav"],
    "navbar_end": ["theme-switcher", "navbar-icon-links"],
    "navbar_align": "left",
    "show_version_warning_banner": switcher_version == "dev",

    "switcher": {
        "version_match": switcher_version,
        "json_url": "https://raw.githubusercontent.com/scverse/SnapATAC2/refs/heads/main/docs/_static/versions.json",
    },
}

commit = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip().decode('ascii')
code_url = f"https://github.com/scverse/SnapATAC2/blob/{commit}"

# based on numpy doc/source/conf.py
def linkcode_resolve(domain, info):
    """
    Determine the URL corresponding to Python object
    """
    import inspect

    if domain != "py":
        return None

    modname = info["module"]
    fullname = info["fullname"]

    submod = sys.modules.get(modname)
    if submod is None:
        return None

    obj = submod
    for part in fullname.split("."):
        try:
            with warnings.catch_warnings():
                # Accessing deprecated objects will generate noisy warnings
                warnings.simplefilter("ignore", FutureWarning)
                obj = getattr(obj, part)
        except AttributeError:
            return None

    try:
        fn = inspect.getsourcefile(inspect.unwrap(obj))
    except TypeError:
        try:  # property
            fn = inspect.getsourcefile(inspect.unwrap(obj.fget))
        except (AttributeError, TypeError):
            fn = None
    if not fn:
        return None

    try:
        source, lineno = inspect.getsourcelines(obj)
    except TypeError:
        try:  # property
            source, lineno = inspect.getsourcelines(obj.fget)
        except (AttributeError, TypeError):
            lineno = None
    except OSError:
        lineno = None

    if lineno:
        linespec = f"#L{lineno}-L{lineno + len(source) - 1}"
    else:
        linespec = ""

    fn = os.path.relpath(fn, start=os.path.dirname(snapatac2.__file__))

    return f"{code_url}/snapatac2-python/python/snapatac2/{fn}{linespec}"