"""Converters used in the table data fetching methods."""

from collections.abc import Iterator
from typing import Any


class TableConverter:
  """Base class for table data converters."""

  def do_conversion(self, features: Iterator[Any]) -> Any:
    raise NotImplementedError()


class PandasConverter(TableConverter):
  """Converter from a feature generator to a `pandas.DataFrame`."""

  def do_conversion(self, features: Iterator[Any]) -> Any:
    try:
      import pandas  # pylint: disable=g-import-not-at-top
    except ImportError as exc:
      raise ImportError(
          'Using format PANDAS_DATAFRAME requires pandas.'
      ) from exc
    return pandas.DataFrame.from_records(self._convert_to_records(features))

  def _convert_to_records(
      self, features: Iterator[Any]
  ) -> Iterator[dict[str, Any]]:
    for feature in features:
      yield {
          'geo': feature.get('geometry'),
          **(feature.get('properties', {}) or {}),
      }


class GeoPandasConverter(TableConverter):
  """Converter from a feature generator to a `geopandas.GeoDataFrame`."""

  def do_conversion(self, features: Iterator[Any]) -> Any:
    try:
      import geopandas  # pylint: disable=g-import-not-at-top
    except ImportError as exc:
      raise ImportError(
          'Using format GEOPANDAS_GEODATAFRAME requires geopandas.'
      ) from exc
    return geopandas.GeoDataFrame.from_features(
        self._materialize_features(features)
    )

  def _materialize_features(self, features: Iterator[Any]) -> list[Any]:
    """Materializes the features, making several requests if necessary."""
    return list(features)


_TABLE_DATA_CONVERTERS: dict[str, type[TableConverter]] = {
    'PANDAS_DATAFRAME': PandasConverter,
    'GEOPANDAS_GEODATAFRAME': GeoPandasConverter,
}


def from_file_format(
    file_format: str | TableConverter
) -> TableConverter | None:
  if isinstance(file_format, TableConverter):
    return file_format
  if file_format in _TABLE_DATA_CONVERTERS:
    return _TABLE_DATA_CONVERTERS[file_format.upper()]()
  return None
