# Copyright 2025 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import contextlib
import io
import pathlib

from graph import Header
from graph import IncludeDir
from graph import Target

_HEADER = """# Copyright 2025 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# AUTOGENERATED FILE. DO NOT EDIT.
# To regenerate, run build/modules/modularize/modularize.py

"""


def _update_content(path: pathlib.Path, content: str):
  """Updates the content of a file if changed, thus preventing a siso reload."""
  with contextlib.suppress(FileNotFoundError):
    if path.read_text() == content:
      return
  path.write_text(content)


def render_modulemap(out_dir: pathlib.Path, sysroot: pathlib.Path,
                     targets: list[Target]):
  """Writes a modulemap to {out_dir}"""
  f = io.StringIO()
  f.write(_HEADER.replace('#', '//'))
  for target in targets:
    if target.include_dir != IncludeDir.Sysroot:
      continue
    f.write(f'\nmodule {target.name} [system] [extern_c] {{\n')
    for header in target.headers:
      assert header.include_dir == IncludeDir.Sysroot
      f.write(f'  module {header.submodule_name} {{\n')
      for single in sorted(header.group):
        assert single.abs is not None
        f.write(
            f'    header "{single.abs.relative_to(out_dir, walk_up=True)}"\n')
      if not header.exports and header.exports is not None:
        f.write('    export *\n')
      for export in header.exports or []:
        f.write(f'    export {export.submodule_name}\n')

      f.write('  }\n')

    f.write('}\n')
  _update_content(out_dir / 'module.modulemap', f.getvalue())


def _render_string_list(f,
                        indent: int,
                        key: str,
                        values: list[str],
                        add: str | None = None):
  if not values and add is None:
    return
  indent = " " * indent
  f.write(f'{indent}{key} = ')
  if add is not None:
    f.write(add)
    if values:
      f.write(' + ')

  if len(values) == 1:
    f.write(f'[ "{values[0]}" ]\n')
  elif len(values) > 1:
    f.write(f'[\n')
    for value in values:
      f.write(f'{indent}  "{value}",\n')
    f.write(f'{indent}]\n')
  else:
    f.write('\n')


def render_build_gn(out_dir: pathlib.Path, targets: list[Target],
                    textual_headers: list[Header]):
  """Renders a BUILD.gn file for a specific platform to {out_dir}"""
  # textual configs are associated with textual headers, so we just apply them
  # indiscriminately since they're not for compiling a specific target.
  textual_configs = sorted(
      {cfg
       for hdr in textual_headers
       for cfg in hdr.public_configs})

  f = io.StringIO()
  f.write(_HEADER)
  f.write('import("//buildtools/third_party/libc++/modules.gni")\n\n')
  if textual_configs:
    _render_string_list(f, 0, 'textual_configs', textual_configs)
    f.write('\n')
  for target in sorted(targets):
    direct_deps = set()
    for hdr in target.headers:
      direct_deps.update(hdr.direct_deps_closure())
    public_deps = sorted({
        f':{hdr.root_module}'
        for hdr in direct_deps
        if hdr.root_module is not None and hdr.root_module != target.name
    })

    rule = {
        IncludeDir.LibCxx: 'libcxx_module',
        IncludeDir.Sysroot: 'sysroot_module',
        IncludeDir.Builtin: 'builtin_module',
    }[target.include_dir]
    f.write(f'{rule}("{target.name}") {{\n')
    _render_string_list(f, 2, 'public_deps', public_deps)
    configs = sorted([
        c for header in target.headers for single in header.group
        for c in single.public_configs
    ])
    _render_string_list(f,
                        2,
                        'public_configs',
                        sorted(configs),
                        add='textual_configs' if textual_configs else None)
    f.write('}\n\n')

  f.write('alias("all_modules") {\n')
  f.write('  actual = [\n')
  for target in sorted(targets):
    f.write(f'    ":{target.name}",\n')
  f.write('  ]\n')
  f.write('}\n')

  _update_content(out_dir / 'BUILD.gn', f.getvalue())
