# Copyright 2026 DeepMind Technologies Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set(NETIMGUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../netimgui)

if(EMSCRIPTEN)
  add_subdirectory(${NETIMGUI_DIR} ${CMAKE_CURRENT_BINARY_DIR}/netimgui)

  # Browser WASM client for the Web Viewer
  add_executable(web_client
    web_client.cc
    web_client_local_ui.cc
    web_client_local_ui.h
    web_client_remote_ui.cc
    web_client_remote_ui.h
    web_client_session.cc
    web_client_session.h
    state_payload.cc
    state_payload.h
  )

  target_include_directories(web_client
    PRIVATE
      ${PROJECT_SOURCE_DIR}/include
      ${PROJECT_SOURCE_DIR}/src
  )

  target_link_libraries(web_client
    PRIVATE
      dear_imgui
      implot
      netimgui
      mujoco::mujoco
      mujoco::platform
      mujoco::render_noop
      SDL2::SDL2-static
  )

  # Ensure resource decoder plugins are linked into the WASM binary.
  target_link_options(web_client PRIVATE
    -Wl,--whole-archive $<TARGET_FILE:mujoco> -Wl,--no-whole-archive
  )

  # Compile with exception support so the try/catch around MainLoop can catch
  # throws escaping the requestAnimationFrame callback. Emscripten disables
  # exception handling by default at compile time; the -fexceptions in the
  # link options below only covers linking.
  target_compile_options(web_client PRIVATE -fexceptions)

  # Filament's OpenGL backend requires WebGL2 / full ES3 bindings.
  target_link_options(web_client PRIVATE
    --bind
    -sASSERTIONS=0
    -sGL_ASSERTIONS=0
    -sFETCH=1
    -sALLOW_MEMORY_GROWTH=1
    # embind (EMSCRIPTEN_BINDINGS registerAsset/startApp) needs eval enabled.
    -sDYNAMIC_EXECUTION=1
    -sUSE_WEBGL2=1
    -sFULL_ES3=1
    -sMIN_WEBGL_VERSION=2
    -sMAX_WEBGL_VERSION=2
    -sSTACK_SIZE=32mb
    # INITIAL_MEMORY=3072mb reserves a 3GB contiguous virtual address block upfront. This means
    # large models do not fail dynamic memory.grow calls due to browser heap fragmentation. OS
    # lazy page allocation for anonymous virtual memory means reserving 3GB of virtual address
    # space consumes much less physical RAM when loading small XML models. Therefore, reducing
    # INITIAL_MEMORY is not necessary for small models. For models that don't fit in 32-bit
    # address space (~4GB) we can switch to 64-bit WASM.
    -sINITIAL_MEMORY=3072mb
    -sMAXIMUM_MEMORY=4gb
    -fexceptions
    # Filament's GL backend calls glGetError() on every draw call via even in non-debug builds
    # (the functions are compiled into the library unconditionally). In WebGL, each glGetError()
    # forces a synchronous GPU pipeline flush across the JS-WASM bridge (~0.2ms each, hundreds
    # per frame). Wrapping it at link time replaces all call sites with a no-op.
    -Wl,--wrap=glGetError
  )

  # Stage the Studio fonts in WEB_CLIENT_FONTS_DIR so the browser can load them.
  set(WEB_CLIENT_FONTS_DIR ${CMAKE_CURRENT_BINARY_DIR}/web_fonts)
  add_custom_target(web_client_fonts
    COMMAND ${CMAKE_COMMAND} -E make_directory ${WEB_CLIENT_FONTS_DIR}
    COMMAND ${CMAKE_COMMAND} -E copy
      "${CMAKE_BINARY_DIR}/_deps/atkinson_hyperlegible_next-src/fonts/variable/AtkinsonHyperlegibleNext[wght].ttf"
      "${WEB_CLIENT_FONTS_DIR}/AtkinsonHyperlegibleNext[wght].ttf"
    COMMAND ${CMAKE_COMMAND} -E copy
      ${CMAKE_BINARY_DIR}/_deps/font_awesome-src/fonts/fontawesome-webfont.ttf
      ${WEB_CLIENT_FONTS_DIR}/fontawesome-webfont.ttf
    COMMAND ${CMAKE_COMMAND} -E copy
      "${CMAKE_BINARY_DIR}/_deps/atkinson_hyperlegible_mono-src/fonts/ttf/AtkinsonHyperlegibleMono-Regular.ttf"
      ${WEB_CLIENT_FONTS_DIR}/AtkinsonHyperlegibleMono-Regular.ttf
    COMMENT "Staging web viewer UI fonts"
  )

  # The fonts and Filament assets are served as plain files under dist/assets.
  # The page fetches them and pushes them into the client via registerAsset()
  # before startApp() runs (see index.html and web_client.cc).
  set(WEB_CLIENT_FILAMENT_ASSETS_DIR
      ${CMAKE_BINARY_DIR}/src/render/filament/assets
  )
  add_dependencies(web_client mujoco_filament_assets web_client_fonts)

  # Stage the browser artifacts into the source-tree dist/ folder, which
  # web_server.py serves by default (see _find_static_files_dir). Assets go in
  # dist/assets/ so the page can fetch them at /assets/<name>.
  set(WEB_CLIENT_DIST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/dist)
  add_custom_command(
    TARGET web_client
    POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E make_directory ${WEB_CLIENT_DIST_DIR}/assets
    COMMAND ${CMAKE_COMMAND} -E copy
        $<TARGET_FILE_DIR:web_client>/web_client.js
        $<TARGET_FILE_DIR:web_client>/web_client.wasm
        ${CMAKE_CURRENT_SOURCE_DIR}/index.html
        ${PROJECT_SOURCE_DIR}/src/experimental/studio/favicon.ico
        ${WEB_CLIENT_DIST_DIR}
    COMMAND ${CMAKE_COMMAND} -E copy_directory
        ${WEB_CLIENT_FILAMENT_ASSETS_DIR} ${WEB_CLIENT_DIST_DIR}/assets
    COMMAND ${CMAKE_COMMAND} -E copy_directory
        ${WEB_CLIENT_FONTS_DIR} ${WEB_CLIENT_DIST_DIR}/assets
    COMMENT "Staging web viewer artifacts + assets into ${WEB_CLIENT_DIST_DIR}"
  )
else()
  if(WIN32)
    # TODO(matijak) netimgui does not build on Windows yet: its client sources
    # include <sys/time.h> and its logging header calls localtime_r, neither of
    # which Windows provides. Windows has no web viewer in any case; its Kokoro
    # jobs have no WASM toolchain so its wheels ship without the browser client.
    message(STATUS "Windows: skipping the Web Viewer Python modules "
                   "(netimgui is POSIX-only).")
    return()
  endif()

  if(NOT TARGET mujoco::dep::implot)
    message(WARNING "mujoco::dep::implot target not found. The web viewer "
                    "modules will not be built.")
    return()
  endif()

  add_subdirectory(${NETIMGUI_DIR} ${CMAKE_CURRENT_BINARY_DIR}/netimgui)
  if(NOT TARGET netimgui)
    return()
  endif()

  mujoco_pybind11_module(headless_ui headless_ui.cc)
  target_link_libraries(headless_ui PRIVATE
    mujoco
    mujoco::platform  # for the shared Studio font loading (ux/fonts)
    mujoco::dep::dear_imgui
    mujoco::dep::implot
    netimgui
  )

  mujoco_pybind11_module(state_payload state_payload_py.cc state_payload.cc state_payload.h)
  target_link_libraries(state_payload PRIVATE
    mujoco
    structs_header
    structs_wrappers
  )

  set(MUJOCO_WEB_VIEWER_TARGETS
      headless_ui
      state_payload
      PARENT_SCOPE
  )
endif()
