if(NOT BUILD_DOCS)
  return()
endif()

# Prefer the sphinx-build that lives in the conda env where myst-parser /
# pydata-sphinx-theme / breathe / exhale are installed. Override on the
# CMake command line:  -DSPHINX_BUILD=/path/to/sphinx-build
find_program(SPHINX_BUILD
  NAMES sphinx-build
  DOC "Path to sphinx-build tool")

if(NOT SPHINX_BUILD)
  message(STATUS "docs_sphinx: sphinx-build not found; `sphinx` target disabled")
  return()
endif()

find_program(DOT_EXECUTABLE NAMES dot
  DOC "Path to graphviz `dot` (required for API collaboration diagrams)")
if(NOT DOT_EXECUTABLE)
  message(WARNING
    "docs_sphinx: graphviz `dot` not found — API class-page collaboration "
    "diagrams will be skipped. Install graphviz (e.g. `sudo apt-get install "
    "graphviz`) and re-run cmake so the Doxygen HTML build enables HAVE_DOT.")
else()
  message(STATUS
    "docs_sphinx: graphviz dot found (${DOT_EXECUTABLE}) — "
    "collaboration diagrams enabled")
endif()

set(_SPHINX_CONFDIR    "${CMAKE_CURRENT_SOURCE_DIR}")
set(_SPHINX_OUTDIR     "${CMAKE_CURRENT_BINARY_DIR}/html")
set(_SPHINX_INPUT_ROOT "${CMAKE_CURRENT_BINARY_DIR}/docs_sphinx_input")

set(_SPHINX_INPUT_TUTORIALS "${_SPHINX_INPUT_ROOT}/tutorials")
set(_SPHINX_INPUT_CONTRIB   "${_SPHINX_INPUT_ROOT}/tutorials_contrib")
file(REMOVE_RECURSE "${_SPHINX_INPUT_ROOT}")
file(MAKE_DIRECTORY "${_SPHINX_INPUT_TUTORIALS}")

# Main tree: symlink the master file + each main module subtree.
file(CREATE_LINK
  "${CMAKE_SOURCE_DIR}/doc/tutorials/tutorials.markdown"
  "${_SPHINX_INPUT_TUTORIALS}/tutorials.markdown"
  SYMBOLIC COPY_ON_ERROR)

file(CREATE_LINK
  "${CMAKE_SOURCE_DIR}/doc/faq.markdown"
  "${_SPHINX_INPUT_ROOT}/faq.markdown"
  SYMBOLIC COPY_ON_ERROR)
file(CREATE_LINK
  "${CMAKE_SOURCE_DIR}/modules/core/doc/intro.markdown"
  "${_SPHINX_INPUT_ROOT}/intro.markdown"
  SYMBOLIC COPY_ON_ERROR)
file(GLOB _main_tutorial_children
     RELATIVE "${CMAKE_SOURCE_DIR}/doc/tutorials"
     "${CMAKE_SOURCE_DIR}/doc/tutorials/*")
foreach(_d ${_main_tutorial_children})
  if(IS_DIRECTORY "${CMAKE_SOURCE_DIR}/doc/tutorials/${_d}")
    file(CREATE_LINK
      "${CMAKE_SOURCE_DIR}/doc/tutorials/${_d}"
      "${_SPHINX_INPUT_TUTORIALS}/${_d}"
      SYMBOLIC COPY_ON_ERROR)
  endif()
endforeach()

# js_tutorials, py_tutorials, images/ sit directly under opencv/doc/.
foreach(_root js_tutorials py_tutorials images)
  if(EXISTS "${CMAKE_SOURCE_DIR}/doc/${_root}")
    file(CREATE_LINK
      "${CMAKE_SOURCE_DIR}/doc/${_root}"
      "${_SPHINX_INPUT_ROOT}/${_root}"
      SYMBOLIC COPY_ON_ERROR)
  endif()
endforeach()

if(OPENCV_EXTRA_MODULES_PATH AND EXISTS "${OPENCV_EXTRA_MODULES_PATH}")
  file(MAKE_DIRECTORY "${_SPHINX_INPUT_CONTRIB}")
  set(_contrib_root_md "${_SPHINX_INPUT_CONTRIB}/contrib_root.markdown")
  file(WRITE "${_contrib_root_md}"
    "Tutorials for contrib modules {#tutorial_contrib_root}\n"
    "=============================\n\n")
  file(GLOB _contrib_subdirs RELATIVE "${OPENCV_EXTRA_MODULES_PATH}"
       "${OPENCV_EXTRA_MODULES_PATH}/*")
  foreach(_m ${_contrib_subdirs})
    set(_tut_dir "${OPENCV_EXTRA_MODULES_PATH}/${_m}/tutorials")
    if(IS_DIRECTORY "${_tut_dir}")
      file(CREATE_LINK "${_tut_dir}" "${_SPHINX_INPUT_CONTRIB}/${_m}"
           SYMBOLIC COPY_ON_ERROR)
      file(GLOB _tocs RELATIVE "${_tut_dir}" "${_tut_dir}/*.markdown")
      foreach(_t ${_tocs})
        file(STRINGS "${_tut_dir}/${_t}" _id LIMIT_COUNT 1 REGEX ".*\\{#[^}]+\\}")
        string(REGEX REPLACE ".*\\{#([^}]+)\\}.*" "\\1" _id "${_id}")
        if(_id)
          file(APPEND "${_contrib_root_md}" "- @subpage ${_id}\n")
        endif()
      endforeach()
    endif()
  endforeach()
endif()

set(_LEGACY_DOXYFILE "${CMAKE_BINARY_DIR}/doc/Doxyfile")
set(_DOXYGEN_XML_DIR "${CMAKE_BINARY_DIR}/doc/doxygen/xml")

if(NOT DEFINED SPHINX_API_MODULES)
  # Any module whose include tree declares an @defgroup, keyed on dir name; matlab excluded.
  set(SPHINX_API_MODULES "")
  set(_api_module_roots "${CMAKE_SOURCE_DIR}/modules")
  if(OPENCV_EXTRA_MODULES_PATH AND EXISTS "${OPENCV_EXTRA_MODULES_PATH}")
    list(APPEND _api_module_roots "${OPENCV_EXTRA_MODULES_PATH}")
  endif()
  foreach(_root ${_api_module_roots})
    file(GLOB _api_module_dirs RELATIVE "${_root}" "${_root}/*")
    foreach(_m ${_api_module_dirs})
      if(IS_DIRECTORY "${_root}/${_m}/include/opencv2" AND NOT _m STREQUAL "matlab")
        file(GLOB_RECURSE _api_hdrs "${_root}/${_m}/include/opencv2/*.hpp")
        foreach(_hdr ${_api_hdrs})
          file(READ "${_hdr}" _hdr_contents)
          if(_hdr_contents MATCHES "@defgroup")
            list(APPEND SPHINX_API_MODULES "${_m}")
            break()
          endif()
        endforeach()
      endif()
    endforeach()
  endforeach()
  list(REMOVE_DUPLICATES SPHINX_API_MODULES)
  list(SORT SPHINX_API_MODULES)
  message(STATUS "docs_sphinx: discovered API modules: ${SPHINX_API_MODULES}")
endif()
set(_doxy_input "")
foreach(_m ${SPHINX_API_MODULES})
  foreach(_base "${CMAKE_SOURCE_DIR}/modules" "${OPENCV_EXTRA_MODULES_PATH}")
    if(EXISTS "${_base}/${_m}/include")
      string(APPEND _doxy_input " ${_base}/${_m}/include")
      break()
    endif()
  endforeach()
endforeach()

if(EXISTS "${_LEGACY_DOXYFILE}")
  set(_SPHINX_DOXYFILE "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile-xml")
  file(WRITE "${_SPHINX_DOXYFILE}"
    "@INCLUDE = ${_LEGACY_DOXYFILE}\n"
    "GENERATE_HTML = NO\n"
    "GENERATE_LATEX = NO\n"
    "GENERATE_XML = YES\n"
    "XML_OUTPUT = xml\n"
    "XML_PROGRAMLISTING = NO\n"
    "CREATE_SUBDIRS = NO\n"
    "CLASS_GRAPH = NO\n"
    "COLLABORATION_GRAPH = NO\n"
    "GROUP_GRAPHS = NO\n"
    "INCLUDE_GRAPH = NO\n"
    "INCLUDED_BY_GRAPH = NO\n"
    "DIRECTORY_GRAPH = NO\n"
    "INPUT =${_doxy_input}\n"
    "RECURSIVE = YES\n"
    "EXCLUDE_SYMBOLS = cv::DataType<*> int void CV__* T __CV* cv::gapi::detail*\n"
    "MACRO_EXPANSION = YES\n"
    "EXPAND_ONLY_PREDEF = YES\n"
    "PREDEFINED += __device__= __host__= __forceinline__= __global__= __constant__= __shared__= __restrict__=\n"
  )
  find_program(DOXYGEN_EXE NAMES doxygen)
  if(DOXYGEN_EXE)
    # Stamp-based: Doxygen only re-runs when Doxyfile changes, not on every build.
    set(_SPHINX_XML_STAMP "${_DOXYGEN_XML_DIR}/.sphinx_xml.stamp")
    add_custom_command(
      OUTPUT "${_SPHINX_XML_STAMP}"
      COMMAND ${CMAKE_COMMAND} -E make_directory ${_DOXYGEN_XML_DIR}
      COMMAND ${DOXYGEN_EXE} ${_SPHINX_DOXYFILE}
      COMMAND ${CMAKE_COMMAND} -E touch "${_SPHINX_XML_STAMP}"
      DEPENDS "${_SPHINX_DOXYFILE}"
      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/doc
      COMMENT "Doxygen XML (for Sphinx breathe) -> ${_DOXYGEN_XML_DIR}"
      VERBATIM)
    add_custom_target(sphinx-xml DEPENDS "${_SPHINX_XML_STAMP}")
    message(STATUS "docs_sphinx: sphinx-xml target enabled (doxygen: ${DOXYGEN_EXE})")
  else()
    message(STATUS "docs_sphinx: doxygen not found; `sphinx-xml` disabled")
  endif()
else()
  message(STATUS "docs_sphinx: legacy Doxyfile not present at ${_LEGACY_DOXYFILE}; "
                 "`sphinx-xml` disabled (re-run cmake after doc/ subdir configures)")
endif()

set(_SPHINX_ENV
  "OPENCV_SPHINX_INPUT_ROOT=${_SPHINX_INPUT_ROOT}"
  "OPENCV_CONTRIB_ROOT=${OPENCV_EXTRA_MODULES_PATH}"
  "OPENCV_DOXYGEN_XML_DIR=${_DOXYGEN_XML_DIR}"
)
if(OPENCV_PYTHON_SIGNATURES_FILE)
  list(APPEND _SPHINX_ENV "OPENCV_PYTHON_SIGNATURES_FILE=${OPENCV_PYTHON_SIGNATURES_FILE}")
endif()

if(NOT SPHINX_JOBS)
  set(SPHINX_JOBS "auto")
endif()
set(_SPHINX_WARNINGS "${CMAKE_CURRENT_BINARY_DIR}/sphinx-warnings.log")
# opencv.js (JS Try-it): download once into the build dir, bundled into html output.
set(_OPENCV_JS "${CMAKE_CURRENT_BINARY_DIR}/opencv.js")
if(NOT EXISTS "${_OPENCV_JS}")
  message(STATUS "docs_sphinx: downloading opencv.js (one-time)")
  file(DOWNLOAD "https://docs.opencv.org/5.x/opencv.js" "${_OPENCV_JS}"
       SHOW_PROGRESS STATUS _DL_STATUS)
  list(GET _DL_STATUS 0 _DL_OK)
  if(NOT _DL_OK EQUAL 0)
    message(WARNING "docs_sphinx: opencv.js download failed — Try-it won't run")
    file(REMOVE "${_OPENCV_JS}")
    set(_OPENCV_JS "")
  endif()
endif()
if(_OPENCV_JS)
  list(APPEND _SPHINX_ENV "OPENCV_JS_PATH=${_OPENCV_JS}")
endif()

add_custom_target(sphinx
  COMMAND ${CMAKE_COMMAND} -E make_directory ${_SPHINX_OUTDIR}
  COMMAND ${CMAKE_COMMAND} -E env ${_SPHINX_ENV}
          ${SPHINX_BUILD} -j ${SPHINX_JOBS} --keep-going
                          -c ${_SPHINX_CONFDIR}
                          ${_SPHINX_INPUT_ROOT}
                          ${_SPHINX_OUTDIR}
  WORKING_DIRECTORY ${_SPHINX_CONFDIR}
  COMMENT "Building Sphinx HTML site -> ${_SPHINX_OUTDIR} (jobs=${SPHINX_JOBS})"
  VERBATIM)

# Ensure breathe sees current XML on every `sphinx` build.
if(TARGET sphinx-xml)
  add_dependencies(sphinx sphinx-xml)
endif()
# Auto-generate Python binding signatures (needed for Python names in enum tables).
if(TARGET gen_opencv_python_source)
  add_dependencies(sphinx gen_opencv_python_source)
endif()

option(SPHINX_BUILD_DIAGRAMS
  "Rebuild the Doxygen HTML (collaboration diagrams) as part of the sphinx target" ON)
if(SPHINX_BUILD_DIAGRAMS AND TARGET doxygen)
  add_dependencies(sphinx doxygen)
endif()

# Clean rebuild: `cmake --build <build> --target sphinx-clean`
add_custom_target(sphinx-clean
  COMMAND ${CMAKE_COMMAND} -E rm -rf ${_SPHINX_OUTDIR}
  COMMAND ${CMAKE_COMMAND} -E rm -rf ${CMAKE_CURRENT_BINARY_DIR}/.doctrees
  COMMENT "Removing ${_SPHINX_OUTDIR} and .doctrees env cache"
  VERBATIM)

message(STATUS "docs_sphinx: sphinx target enabled (sphinx-build: ${SPHINX_BUILD})")
