# -*- coding: utf-8 -*- #
# Copyright 2023 Google LLC. All Rights Reserved.
#
# 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
#
#    http://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.
"""Utilities for adding help text for flags with an argparser type."""

from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals

import abc
from typing import Union

import six


class ArgTypeUsage(six.with_metaclass(abc.ABCMeta, object)):
  """Interface for flags types that need to provide additional usage info."""

  @property
  @abc.abstractmethod
  def hidden(self):
    """Whether the argument is hidden."""

  @abc.abstractmethod
  def GetUsageMetavar(self, is_custom_metavar, metavar):
    """Returns the metavar for flag with type self."""

  @abc.abstractmethod
  def GetUsageExample(self, shorthand):
    """Returns the example user input value for flag with type self."""

  @abc.abstractmethod
  def GetUsageHelpText(self, field_name, required, flag_name):
    """Returns the help text for flag with type self."""


class DefaultArgTypeWrapper(ArgTypeUsage):
  """Base class for processing arg_type output but maintaining usage help text.

  Attributes:
    arg_type: type function used to parse input string into correct type ie
      ArgObject(value_type=int, repeating=true), int, bool, etc
  """

  def __init__(self, arg_type):
    super(DefaultArgTypeWrapper, self).__init__()
    self.arg_type = arg_type

  @property
  def _is_usage_type(self):
    return isinstance(self.arg_type, ArgTypeUsage)

  @property
  def hidden(self):
    if self._is_usage_type:
      return self.arg_type.hidden
    else:
      return None

  def GetUsageMetavar(self, *args, **kwargs):
    """Forwards default usage metavar for arg_type."""
    if self._is_usage_type:
      return self.arg_type.GetUsageMetavar(*args, **kwargs)
    else:
      return None

  def GetUsageExample(self, *args, **kwargs):
    """Forwards default usage example for arg_type."""
    if self._is_usage_type:
      return self.arg_type.GetUsageExample(*args, **kwargs)
    else:
      return None

  def GetUsageHelpText(self, *args, **kwargs):
    """Forwards default help text for arg_type."""
    if self._is_usage_type:
      return self.arg_type.GetUsageHelpText(*args, **kwargs)
    else:
      return None


def IsHidden(arg_type):
  """Returns whether arg_type is hidden.

  Args:
    arg_type: Callable, arg type that may contain hidden attribute

  Returns:
    bool, whether the type is considered hidden
  """
  return (isinstance(arg_type, ArgTypeUsage) and arg_type.hidden) or False


ASCII_INDENT = '::\n'


def IndentAsciiDoc(text, depth=0):
  """Tabs over all lines in text using ascii doc syntax."""
  additional_tabs = ':' * depth
  return text.replace(ASCII_INDENT, additional_tabs + ASCII_INDENT)


def _FormatBasicTypeStr(arg_type):
  """Returns a user friendly name of a primitive arg_type.

  Args:
    arg_type: type | str | None, expected user input type

  Returns:
    String representation of the type
  """
  if not arg_type:
    return None
  if isinstance(arg_type, str):
    # If arg_type is a string literal, return string literal
    return arg_type

  # Return string representation of common built in callable types
  if arg_type is int:
    return 'int'
  if arg_type is float:
    return 'float'
  if arg_type is bool:
    return 'boolean'

  # Default to string
  # TODO(b/296409952) Add common complex types such as enum or resource args
  return 'string'


def _Punctuate(text):
  """Adds punctuation to text if it doesn't already exist."""
  clean_text = text.rstrip()
  if clean_text.endswith('.'):
    return clean_text
  else:
    return clean_text + '.'


def _Capitalize(text: str, ignore: bool = False) -> Union[str, None]:
  """Capitalizes the first letter of text.

  Args:
    text: The text to capitalize.
    ignore: Whether to ignore the capitalization request.

  Returns:
    The capitalized text.
  """
  if not text or ignore:
    return text
  return text[0].upper() + text[1:]


def FormatHelpText(field_name, required, help_text=None):
  """Defaults and formats specific attribute of help text.

  Args:
    field_name: None | str, attribute that is being set by flag
    required: bool, whether the flag is required
    help_text: None | str, text that describes the flag

  Returns:
    help text formatted as `{type} {required}, {help}`
  """
  if help_text:
    defaulted_help_text = _Punctuate(help_text)
  elif field_name:
    defaulted_help_text = _Capitalize(
        'sets `{}` value.'.format(field_name), required
    )
  else:
    defaulted_help_text = _Capitalize('sets value.', required)

  if required:
    return 'Required, {}'.format(defaulted_help_text)
  else:
    return defaulted_help_text


def FormatCodeSnippet(arg_name, arg_value, append=False):
  """Formats flag in markdown code snippet.

  Args:
    arg_name: str, name of the flag in snippet
    arg_value: str, flag value in snippet
    append: bool, whether to use append syntax for flag

  Returns:
    markdown string of example user input
  """
  if ' ' in arg_value:
    example_flag = "{}='{}'".format(arg_name, arg_value)
  else:
    example_flag = '{}={}'.format(arg_name, arg_value)

  if append:
    return '```\n\n{input} {input}\n\n```'.format(input=example_flag)
  else:
    return '```\n\n{}\n\n```'.format(example_flag)


def _GetNestedValueExample(arg_type, shorthand):
  """Gets an example input value for flag of arg_type.

  Args:
    arg_type: Callable[[str], Any] | str | None, expected user input type
    shorthand: bool, whether to display example in shorthand

  Returns:
    string representation of user input for type arg_type
  """
  if not arg_type:
    return None

  if isinstance(arg_type, ArgTypeUsage):
    arg_str = arg_type.GetUsageExample(shorthand=shorthand)
  else:
    arg_str = _FormatBasicTypeStr(arg_type)

  is_string_literal = isinstance(arg_type, str)
  is_string_type = arg_str == _FormatBasicTypeStr(str)
  if not shorthand and (is_string_literal or is_string_type):
    return '"{}"'.format(arg_str)
  else:
    return arg_str


def GetNestedKeyValueExample(key_type, value_type, shorthand):
  """Formats example key-value input for flag of arg_type.

  If key_type and value_type are callable types str, returns

    string=string (shorthand) or
    "string": "string" (non-shorthand)

  If key_type is a static string value such as x, returns

    x=string (shorthand) or
    "x": "string" (non-shorthand).

  If key_type or value_type are None, returns string representation of
  key or value

  Args:
    key_type: Callable[[str], Any] | str | None, type function for the key
    value_type: Callable[[str], Any] | None, type function for the value
    shorthand: bool, whether to display the example in shorthand

  Returns:
    str, example of key-value pair
  """
  key_str = _GetNestedValueExample(key_type, shorthand)
  value_str = _GetNestedValueExample(value_type, shorthand)

  if IsHidden(key_type) or IsHidden(value_type):
    return None
  elif not key_str or not value_str:
    return key_str or value_str
  elif shorthand:
    return f'{key_str}={value_str}' if value_str != '{}' else key_str
  else:
    return f'{key_str}: {value_str}'


def GetNestedUsageHelpText(field_name, arg_type, required=False):
  """Returns help text for flag with arg_type.

  Generates help text based on schema such that the final output will
  look something like...

    *Foo*
        Required, Foo help text

  Args:
    field_name: str, attribute we are generating help text for
    arg_type: Callable[[str], Any] | None, type of the attribute we are getting
      help text for
    required: bool, whether the attribute is required

  Returns:
    string help text for specific attribute
  """
  default_usage = FormatHelpText(field_name, required)

  if isinstance(arg_type, ArgTypeUsage) and arg_type.hidden:
    usage = None
  elif isinstance(arg_type, ArgTypeUsage) and not arg_type.hidden:
    usage = (
        arg_type.GetUsageHelpText(field_name, required=required)
        or default_usage
    )
  else:
    usage = default_usage

  # Shift (indent) nested content over to the right by one
  if usage:
    return '*{}*{}{}'.format(
        field_name, ASCII_INDENT, IndentAsciiDoc(usage, depth=1)
    )
  else:
    return None
