# 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.
from dataclasses import dataclass
from enum import Enum
from typing import Optional


class AggregationKind(Enum):
  """Allowed aggregation types for data that uses "aggregate" declaration."""
  NONE = "none"
  ARRAY = "array"
  MAP = "map"


@dataclass
class AggregationDetails:
  """Aggregation rules, if specified by the processed JSON file."""
  kind: AggregationKind
  name: Optional[str]
  export_items: bool
  elements: dict[str, str]
  map_key_type: Optional[str]

  def GetSortedArrayElements(self) -> list[str]:
    """Returns sorted list of names of all elements."""
    return sorted(self.elements.keys())

  def GetSortedMapElements(self) -> list[tuple[str, str]]:
    """Returns sorted mapping of all elements, including aliases."""
    keys = sorted(self.elements.keys())
    return [(key, self.elements[key]) for key in keys]


def GetAggregationDetails(description) -> AggregationDetails:
  """Extracts aggregation details from a JSON structure.

  This function processes a JSON data object to determine its aggregation
  properties. Aggregation details are specified within an optional "aggregation"
  descriptor in the JSON input. If the descriptor is missing, no aggregation is
  performed.

  **Aggregation Descriptor Structure:**
      - `type` (str): Defines the aggregation type. Options include:
          - `"none"`: No aggregation (default/implied if descriptor is missing).
          - `"array"`: Uses `std::span<Type>` for aggregation.
          - `"map"`: Uses `base::fixed_flat_map<std::string_view, Type>` for
            aggregation.
      - `name` (str): The name assigned to the generated array or map.
      - `export_items` (bool, optional): Whether aggregated items should be
        exported (defaults to `true`).
      - `map_aliases` (dict[str, str]) - if the aggregation `type` is set to
        `"map"`, this field allows specifying additional aliases - elements
        pointing to already defined structures.
      - `map_key_type` (str) - the type representing the map key. Must be a
        constexpr-constructible from const char[].

  **Default Behavior:**
      - If the `aggregation` descriptor is missing, the function defaults to:
          - `type = "none"`
          - `export_items = True`
      - The `name` field is only relevant when aggregation is `array` or `map`.

  **Parameters:**
      description (dict): input JSON data file (not schema).

  **Returns:**
      AggregationDetails populated with relevant fields. This is always
      returned, even if the data object does not include aggregation descriptor.
  """
  aggregation = description.get('aggregation', {})
  kind = AggregationKind(aggregation.get('type', 'none'))
  name = aggregation.get('name', None)
  export_items = aggregation.get('export_items', True)
  map_aliases = aggregation.get('map_aliases', {})
  map_key_type = None

  if kind != AggregationKind.NONE and not name:
    raise Exception("Aggregation container needs a `name`.")

  elements = {}
  for element in description.get('elements', {}).keys():
    elements.update({element: element})

  confirmed_aliases = {}
  if kind == AggregationKind.MAP:
    map_key_type = aggregation.get('map_key_type', 'std::string_view')
    for alias, element in map_aliases.items():
      # Confirmation check for duplicate entries.
      # Note: we do not need to verify duplicate aliases, because `map_aliases`
      # is already a dict - all keys should be unique.
      if elements.get(alias, None):
        raise Exception(f"Alias `{alias}` already defined as element.")

      # Detect that alias does not point to a valid element.
      if not elements.get(element, None):
        raise Exception(f"Aliased element `{element}` does not exist.")

      confirmed_aliases.update({alias: element})
    elements.update(confirmed_aliases)

  return AggregationDetails(kind, name, export_items, elements, map_key_type)


def GenerateCCAggregation(type_name: str,
                          aggregation: AggregationDetails) -> Optional[str]:
  """
    Generates C++ aggregation code based on the aggregation kind.

    Parameters:
        type_name (str): The type name to be used in the aggregation.
        aggregation (AggregationDetails): The aggregation details.

    Returns:
        Optional[str]: The generated C++ aggregation code if applicable, otherwise None.
    """
  if aggregation.kind == AggregationKind.ARRAY:
    return _GenerateCCArray(type_name, aggregation)

  if aggregation.kind == AggregationKind.MAP:
    return _GenerateCCMap(type_name, aggregation)

  return None


def _GenerateCCArray(type_name: str, aggregation: AggregationDetails) -> str:
  """
    Generates C++ code for an array aggregation.

    Parameters:
        type_name (str): The type name to be used in the aggregation.
        aggregation (AggregationDetails): The aggregation details.

    Returns:
        str: The generated C++ array aggregation code.
    """
  res = f'\nconst auto {aggregation.name} =\n'
  res += f'    std::array<const {type_name}*, {len(aggregation.elements)}>'

  res += '({{\n'
  for element_name in aggregation.elements.values():
    res += f'  &{element_name},\n'
  res += '}});\n'
  return res


def _GenerateCCMap(type_name: str, aggregation: AggregationDetails) -> str:
  """
    Generates C++ code for a map aggregation.

    Parameters:
        type_name (str): The type name to be used in the aggregation.
        aggregation (AggregationDetails): The aggregation details.

    Returns:
        str: The generated C++ map aggregation code.
    """
  key_type = aggregation.map_key_type

  res = f'\nconst auto {aggregation.name} =\n'
  res += f'    base::MakeFixedFlatMap<{key_type}, const {type_name}*>'

  res += '({\n'
  for (alias_name, element_name) in aggregation.GetSortedMapElements():
    res += f'  {{{key_type}("{alias_name}"), &{element_name}}},\n'
  res += '});\n'
  return res


def GenerateHHAggregation(type_name: str,
                          aggregation: AggregationDetails) -> Optional[str]:
  """
    Generates header file aggregation code based on the aggregation kind.

    Parameters:
        type_name (str): The type name to be used in the aggregation.
        aggregation (AggregationDetails): The aggregation details.

    Returns:
        Optional[str]: The generated header file aggregation code if applicable, otherwise None.
    """
  if aggregation.kind == AggregationKind.ARRAY:
    return _GenerateHHArray(type_name, aggregation)

  if aggregation.kind == AggregationKind.MAP:
    return _GenerateHHMap(type_name, aggregation)

  return None


def _GenerateHHArray(type_name: str, aggregation: AggregationDetails) -> str:
  """
    Generates header file code for an array aggregation.

    Parameters:
        type_name (str): The type name to be used in the aggregation.
        aggregation (AggregationDetails): The aggregation details.

    Returns:
        str: The generated header file array aggregation declaration.
    """
  res = '\nextern const '
  res += f'std::array<const {type_name}*, {len(aggregation.elements)}> '
  res += f'{aggregation.name};\n'
  return res


def _GenerateHHMap(type_name: str, aggregation: AggregationDetails) -> str:
  """
    Generates header file code for a map aggregation.

    Parameters:
        type_name (str): The type name to be used in the aggregation.
        aggregation (AggregationDetails): The aggregation details.

    Returns:
        str: The generated header file map aggregation declaration.
    """
  res = '\nextern const '
  res += f'base::fixed_flat_map<{aggregation.map_key_type}, '
  res += f'const {type_name}*, {len(aggregation.GetSortedMapElements())}> '
  res += f'{aggregation.name};\n'
  return res
