"""The image collector for sphinx.environment."""

from __future__ import annotations

import os
import os.path
from glob import glob
from pathlib import Path
from typing import TYPE_CHECKING

from docutils import nodes

from sphinx import addnodes
from sphinx.environment.collectors import EnvironmentCollector
from sphinx.locale import __
from sphinx.util import logging
from sphinx.util.i18n import get_image_filename_for_language, search_image_for_language
from sphinx.util.images import guess_mimetype
from sphinx.util.osutil import _relative_path

if TYPE_CHECKING:
    from collections.abc import Set

    from docutils.nodes import Node

    from sphinx.application import Sphinx
    from sphinx.environment import BuildEnvironment
    from sphinx.util.typing import ExtensionMetadata

logger = logging.getLogger(__name__)


class ImageCollector(EnvironmentCollector):
    """Image files collector for sphinx.environment."""

    def clear_doc(self, app: Sphinx, env: BuildEnvironment, docname: str) -> None:
        env.images.purge_doc(docname)

    def merge_other(
        self,
        app: Sphinx,
        env: BuildEnvironment,
        docnames: Set[str],
        other: BuildEnvironment,
    ) -> None:
        env.images.merge_other(docnames, other.images)

    def process_doc(self, app: Sphinx, doctree: nodes.document) -> None:
        """Process and rewrite image URIs."""
        docname = app.env.docname

        for node in doctree.findall(nodes.image):
            # Map the mimetype to the corresponding image.  The writer may
            # choose the best image from these candidates.  The special key * is
            # set if there is only single candidate to be used by a writer.
            # The special key ? is set for nonlocal URIs.
            candidates: dict[str, str] = {}
            node['candidates'] = candidates
            imguri = node['uri']
            if imguri.startswith('data:'):
                candidates['?'] = imguri
                continue
            if imguri.find('://') != -1:
                candidates['?'] = imguri
                continue

            if imguri.endswith(os.extsep + '*'):
                # Update `node['uri']` to a relative path from srcdir
                # from a relative path from current document.
                rel_imgpath, full_imgpath = app.env.relfn2path(imguri, docname)
                node['uri'] = rel_imgpath

                # Search language-specific figures at first
                i18n_imguri = get_image_filename_for_language(imguri, app.env)
                _, full_i18n_imgpath = app.env.relfn2path(i18n_imguri, docname)
                self.collect_candidates(app.srcdir, full_i18n_imgpath, candidates, node)

                self.collect_candidates(app.srcdir, full_imgpath, candidates, node)
            else:
                # substitute imguri by figure_language_filename
                # (ex. foo.png -> foo.en.png)
                imguri = search_image_for_language(imguri, app.env)

                # Update `node['uri']` to a relative path from srcdir
                # from a relative path from current document.
                original_uri = node['uri']
                node['uri'], _ = app.env.relfn2path(imguri, docname)
                candidates['*'] = node['uri']
                if node['uri'] != original_uri:
                    node['original_uri'] = original_uri

            # map image paths to unique image names (so that they can be put
            # into a single directory)
            for imgpath in candidates.values():
                app.env.note_dependency(imgpath)
                if not os.access(app.srcdir / imgpath, os.R_OK):
                    logger.warning(
                        __('image file not readable: %s'),
                        imgpath,
                        location=node,
                        type='image',
                        subtype='not_readable',
                    )
                    continue
                app.env.images.add_file(docname, imgpath)

    def collect_candidates(
        self,
        srcdir: Path,
        imgpath: str,
        candidates: dict[str, str],
        node: Node,
    ) -> None:
        globbed: dict[str, list[str]] = {}
        for filename in glob(imgpath):  # NoQA: PTH207
            new_imgpath = _relative_path(Path(filename), srcdir)
            try:
                mimetype = guess_mimetype(filename)
                if mimetype is None:
                    basename, suffix = os.path.splitext(filename)
                    mimetype = 'image/x-' + suffix[1:]
                if mimetype not in candidates:
                    globbed.setdefault(mimetype, []).append(new_imgpath.as_posix())
            except OSError as err:
                logger.warning(
                    __('image file %s not readable: %s'),
                    filename,
                    err,
                    location=node,
                    type='image',
                    subtype='not_readable',
                )
        for key, files in globbed.items():
            candidates[key] = min(files, key=len)  # select by similarity


class DownloadFileCollector(EnvironmentCollector):
    """Download files collector for sphinx.environment."""

    def clear_doc(self, app: Sphinx, env: BuildEnvironment, docname: str) -> None:
        env.dlfiles.purge_doc(docname)

    def merge_other(
        self,
        app: Sphinx,
        env: BuildEnvironment,
        docnames: Set[str],
        other: BuildEnvironment,
    ) -> None:
        env.dlfiles.merge_other(docnames, other.dlfiles)

    def process_doc(self, app: Sphinx, doctree: nodes.document) -> None:
        """Process downloadable file paths."""
        for node in doctree.findall(addnodes.download_reference):
            targetname = node['reftarget']
            if '://' in targetname:
                node['refuri'] = targetname
            else:
                rel_filename, filename = app.env.relfn2path(targetname, app.env.docname)
                app.env.note_dependency(rel_filename)
                if not os.access(filename, os.R_OK):
                    logger.warning(
                        __('download file not readable: %s'),
                        filename,
                        location=node,
                        type='download',
                        subtype='not_readable',
                    )
                    continue
                node['filename'] = app.env.dlfiles.add_file(
                    app.env.docname, rel_filename
                ).as_posix()


def setup(app: Sphinx) -> ExtensionMetadata:
    app.add_env_collector(ImageCollector)
    app.add_env_collector(DownloadFileCollector)

    return {
        'version': 'builtin',
        'parallel_read_safe': True,
        'parallel_write_safe': True,
    }
