<?php

namespace Drupal\group\Entity\Form;

use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\group\Entity\GroupRoleInterface;
use Drupal\group\PermissionScopeInterface;

/**
 * Form controller for group role forms.
 */
class GroupRoleForm extends EntityForm {

  /**
   * {@inheritdoc}
   */
  public function form(array $form, FormStateInterface $form_state) {
    $form = parent::form($form, $form_state);

    assert($this->entity instanceof GroupRoleInterface);
    $group_role = $this->entity;

    $form['label'] = [
      '#title' => $this->t('Name'),
      '#type' => 'textfield',
      '#default_value' => $group_role->label(),
      '#description' => $this->t('The human-readable name of this group role. This text will be displayed on the group permissions page.'),
      '#required' => TRUE,
      '#size' => 30,
    ];

    // UI-generated group roles have the group type prefixed to their ID.
    if ($this->operation === 'add') {
      // Since group role IDs are prefixed by the group type's ID followed by a
      // period, we need to save some space for that.
      $subtract = strlen($group_role->getGroupTypeId()) + 1;
      $form['id'] = [
        '#type' => 'machine_name',
        '#description' => $this->t('A unique machine-readable name for this group role. It must only contain lowercase letters, numbers, and underscores.'),
        '#maxlength' => EntityTypeInterface::ID_MAX_LENGTH - $subtract,
        '#field_prefix' => $group_role->getGroupTypeId() . '-',
        '#machine_name' => [
          'exists' => [$this, 'exists'],
          'source' => ['label'],
        ],
      ];
    }
    else {
      $form['id'] = [
        '#type' => 'value',
        '#value' => $group_role->id(),
      ];
    }

    $form['weight'] = [
      '#type' => 'value',
      '#value' => $group_role->getWeight(),
    ];

    $form['scope'] = [
      '#title' => $this->t('Scope'),
      '#type' => 'radios',
      '#options' => [
        PermissionScopeInterface::OUTSIDER_ID => $this->t('Outsider: <em>Assigned to all non-members who have the corresponding global role</em>'),
        PermissionScopeInterface::INSIDER_ID => $this->t('Insider: <em>Assigned to all members who have the corresponding global role</em>'),
        PermissionScopeInterface::INDIVIDUAL_ID => $this->t('Individual: <em>Can be assigned to individual members</em>'),
      ],
      '#default_value' => $group_role->getScope() ?? PermissionScopeInterface::INDIVIDUAL_ID,
      '#required' => TRUE,
    ];

    $role_labels = [];
    foreach ($this->entityTypeManager->getStorage('user_role')->loadMultiple() as $role) {
      $role_labels[$role->id()] = $role->label();
    }
    $form['global_role'] = [
      '#title' => $this->t('Global role'),
      '#type' => 'select',
      '#options' => $role_labels,
      '#default_value' => $group_role->getGlobalRoleId(),
      '#states' => [
        'invisible' => [':input[name="scope"]' => ['value' => PermissionScopeInterface::INDIVIDUAL_ID]],
        'disabled' => [':input[name="scope"]' => ['value' => PermissionScopeInterface::INDIVIDUAL_ID]],
      ],
    ];

    $form['admin'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Admin role'),
      '#description' => $this->t('<strong>Warning</strong>: An admin role will always have all permissions, assign with caution.'),
      '#default_value' => $group_role->isAdmin(),
    ];

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  protected function actions(array $form, FormStateInterface $form_state) {
    $actions = parent::actions($form, $form_state);
    $actions['submit']['#value'] = $this->t('Save group role');
    $actions['delete']['#value'] = $this->t('Delete group role');
    return $actions;
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {
    parent::validateForm($form, $form_state);

    // '0' is invalid, since elsewhere we might check it using empty().
    $id = trim($form_state->getValue('id'));
    if ($id == '0') {
      $form_state->setErrorByName('id', $this->t('Invalid machine-readable name. Enter a name other than %invalid.', ['%invalid' => $id]));
    }

    // Config entity forms do not validate constraints by default.
    $violations = $this->entity->getTypedData()->validate();
    foreach ($violations as $violation) {
      $name = static::mapViolationPropertyPathToFormName($violation->getPropertyPath());
      $form_state->setErrorByName($name, $violation->getMessage());
    }
  }

  /**
   * Maps a violation property path to a form name.
   *
   * @param string $property_path
   *   The violation property path.
   *
   * @return string
   *   The mapped form name(s) for the violation property path.
   */
  protected static function mapViolationPropertyPathToFormName($property_path) {
    return str_replace('.', '][', $property_path);
  }

  /**
   * {@inheritdoc}
   */
  public function save(array $form, FormStateInterface $form_state) {
    assert($this->entity instanceof GroupRoleInterface);

    $group_role = $this->entity;
    $group_role->set('label', trim($group_role->label()));

    // UI-generated group roles have the group type prefixed to their ID.
    if ($this->operation === 'add') {
      $group_role->set('id', $group_role->getGroupTypeId() . '-' . $group_role->id());
    }

    // Make sure the global_role property is NULL rather than FALSE.
    if ($group_role->getScope() === PermissionScopeInterface::INDIVIDUAL_ID) {
      $group_role->set('global_role', NULL);
    }

    $status = $group_role->save();
    $t_args = ['%label' => $group_role->label()];

    if ($status == SAVED_UPDATED) {
      $this->messenger()->addStatus($this->t('The group role %label has been updated.', $t_args));
    }
    elseif ($status == SAVED_NEW) {
      $this->messenger()->addStatus($this->t('The group role %label has been added.', $t_args));

      $context = array_merge($t_args, ['link' => $group_role->toLink($this->t('View'), 'collection')->toString()]);
      $this->logger('group')->notice('Added group role %label.', $context);
    }

    $form_state->setRedirectUrl($group_role->toUrl('collection'));
    return $status;
  }

  /**
   * Checks whether a group role ID exists already.
   *
   * @param string $id
   *   The group role ID.
   *
   * @return bool
   *   Whether the ID is taken.
   */
  public function exists($id) {
    assert($this->entity instanceof GroupRoleInterface);
    $group_role = $this->entity;
    return (boolean) $this->entityTypeManager->getStorage('group_role')->load($group_role->getGroupTypeId() . '-' . $id);
  }

  /**
   * {@inheritdoc}
   */
  public function getEntityFromRouteMatch(RouteMatchInterface $route_match, $entity_type_id) {
    if ($route_match->getRawParameter($entity_type_id) !== NULL) {
      return $route_match->getParameter($entity_type_id);
    }

    // If we are on the create form, we can't extract an entity from the route,
    // so we need to create one based on the route parameters.
    $values = [];
    if ($route_match->getRawParameter('group_type') !== NULL) {
      $values['group_type'] = $route_match->getRawParameter('group_type');
    }
    return $this->entityTypeManager->getStorage($entity_type_id)->create($values);
  }

}
