<?php

namespace Drupal\author_field\Plugin\Field\FieldWidget;

use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use GuzzleHttp\ClientInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Plugin implementation of the 'author_field_widget' widget.
 *
 * A widget author renders the author fields inside forms.
 *
 * @FieldWidget(
 *   id = "author_field_widget",
 *   module = "author_field",
 *   label = @Translation("Author Field Widget"),
 *   field_types = {
 *     "author_field"
 *   },
 * )
 */
class AuthorFieldWidget extends WidgetBase {
  /**
   * A http client service.
   */
  protected ClientInterface $httpClient;

  /**
   * The configuration factory service.
   */
  protected ConfigFactoryInterface $configFactory;

  /**
   * Constructs a new CustomWidget object.
   */
  public function __construct($plugin_id,
  $plugin_definition,
        FieldDefinitionInterface $field_definition,
        array $settings,
        array $third_party_settings,
        ConfigFactoryInterface $config_factory,
        ClientInterface $http_client) {
    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
    $this->configFactory = $config_factory;
    $this->httpClient = $http_client;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): WidgetBase|ContainerFactoryPluginInterface|AuthorFieldWidget|static {
    return new static(
          $plugin_id,
          $plugin_definition,
          $configuration['field_definition'],
          $configuration['settings'],
          $configuration['third_party_settings'],
          $container->get('config.factory'),
          $container->get('http_client'),
      );
  }

  /**
   * As the default setting, show all author fields.
   */
  public static function defaultSettings(): array {
    return array_merge(
          [
            'hideGivenName' => FALSE,
            'hideFamilyName' => FALSE,
            'hideEmail' => FALSE,
            'hideOrganizationName' => FALSE,
            'hideNameIdentifier' => FALSE,
          ],
          parent::defaultSettings());
  }

  /**
   * Summarize the currently selected settings in the summary for the widget.
   */
  public function settingsSummary(): array {
    $summary = [];

    if ($this->getSetting('hideGivenName')) {
      $summary[] = 'Hidden: Given Name field ';
    }

    if ($this->getSetting('hideFamilyName')) {
      $summary[] = 'Hidden: Family Name field ';
    }

    if ($this->getSetting('hideEmail')) {
      $summary[] = 'Hidden: Email field ';
    }

    if ($this->getSetting('hideOrganizationName')) {
      $summary[] = 'Hidden: Organization Name field ';
    }

    if ($this->getSetting('hideNameIdentifier')) {
      $summary[] = 'Hidden: Name Identifier (ORCID ID) field ';
    }

    if (empty($summary) === TRUE) {
      $summary[] = 'Hidden: None';
    }

    return $summary;
  }

  /**
   * Allow for users to change the values for the setting.
   */
  public function settingsForm(array $form, FormStateInterface $form_state): array {
    $this->sanitizeSettings();

    $weight = 0;
    $marker = rand();

    $elements['hideGivenName'] = [
      '#title' => $this->t('Hide Given Name field'),
      '#type' => 'checkbox',
      '#default_value' => $this->getSetting('hideGivenName'),
      '#weight' => $weight++,
      '#wrapper_attributes' => [
        'class' => [
          'formatter_suite-padding-left-15',
        ],
      ],
      '#attributes' => [
        'class' => [
          'hideGivenName-' . $marker,
        ],
      ],
    ];

    $elements['hideFamilyName'] = [
      '#title' => $this->t('Hide Family Name field'),
      '#type' => 'checkbox',
      '#default_value' => $this->getSetting('hideFamilyName'),
      '#weight' => $weight++,
      '#wrapper_attributes' => [
        'class' => [
          'formatter_suite-padding-left-15',
        ],
      ],
      '#attributes' => [
        'class' => [
          'hideFamilyName-' . $marker,
        ],
      ],
    ];

    $elements['hideEmail'] = [
      '#title' => $this->t('Hide Email field'),
      '#type' => 'checkbox',
      '#default_value' => $this->getSetting('hideEmail'),
      '#weight' => $weight++,
      '#wrapper_attributes' => [
        'class' => [
          'formatter_suite-padding-left-15',
        ],
      ],
      '#attributes' => [
        'class' => [
          'hideEmail-' . $marker,
        ],
      ],
    ];

    $elements['hideOrganizationName'] = [
      '#title' => $this->t('Hide Organization Name field'),
      '#type' => 'checkbox',
      '#default_value' => $this->getSetting('hideOrganizationName'),
      '#weight' => $weight++,
      '#wrapper_attributes' => [
        'class' => [
          'formatter_suite-padding-left-15',
        ],
      ],
      '#attributes' => [
        'class' => [
          'hideOrganizationName-' . $marker,
        ],
      ],
    ];

    $elements['hideNameIdentifier'] = [
      '#title' => $this->t('Hide Name Identifier (ORCID ID) field'),
      '#type' => 'checkbox',
      '#default_value' => $this->getSetting('hideNameIdentifier'),
      '#weight' => $weight++,
      '#wrapper_attributes' => [
        'class' => [
          'formatter_suite-padding-left-15',
        ],
      ],
      '#attributes' => [
        'class' => [
          'hideNameIdentifier-' . $marker,
        ],
      ],
    ];

    return $elements;
  }

  /**
   * Sanitize settings to make sure that they are safe and valid.
   *
   * @internal
   * Drupal's class hierarchy for plugins and their settings does not
   * include a 'validate' function, like that for other classes with forms.
   * Validation must therefore occur on use, rather than on form submission.
   *
   * @endinternal
   */
  protected function sanitizeSettings() {
    $hideGivenName = $this->getSetting('hideGivenName');
    $hideFamilyName = $this->getSetting('hideFamilyName');
    $hideEmail = $this->getSetting('hideEmail');
    $hideOrganizationName = $this->getSetting('hideOrganizationName');
    $hideNameIdentifier = $this->getSetting('hideNameIdentifier');

    $hideGivenName = boolval($hideGivenName);
    $hideFamilyName = boolval($hideFamilyName);
    $hideEmail = boolval($hideEmail);
    $hideOrganizationName = boolval($hideOrganizationName);
    $hideNameIdentifier = boolval($hideNameIdentifier);

    $this->setSetting('hideGivenName', $hideGivenName);
    $this->setSetting('hideFamilyName', $hideFamilyName);
    $this->setSetting('hideEmail', $hideEmail);
    $this->setSetting('hideOrganizationName', $hideOrganizationName);
    $this->setSetting('hideNameIdentifier', $hideNameIdentifier);

  }

  /**
   * The form is rebuilt on submit.
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    // Set the form to rebuild. The submitted values are maintained in the
    // form state, and used to build the search results in the form definition.
    $form_state->setRebuild(TRUE);
  }

  /**
   * Ajax callback method which gets executed.
   *
   * After getting the results from the
   * autocomplete REST API, accesses the ORCID public/sandbox API
   * to get the researcher information, and updates values in the form elements.
   *
   * @throws \GuzzleHttp\Exception\GuzzleException
   */
  public function orcidCallback(array &$form, FormStateInterface $form_state): mixed {
    $triggering = $form_state->getTriggeringElement();
    $value = $form_state->getValue($triggering['#parents']);
    $url_encoded_orcid_id = urlencode($value);

    if (!empty($value)) {
      // Load the 'author_field.settings' configuration.
      $config = $this->configFactory->getEditable('author_field.settings');
      // Get the 'author_api' value from the configuration.
      // 0 - Sandbox
      // 1 - Production.
      $api_type = $config->get('author_api');
      $params = '?q=orcid:' . $url_encoded_orcid_id . '&fl=orcid,given-names,family-name,email,current-institution-affiliation-name&start=0&rows=1';
      if ($api_type == 0) {
        $orcid_url = $config->get('sandbox_orcid_url');
      }
      else {
        $orcid_url = $config->get('orcid_url');
      }

      $result = $this->httpClient->request('GET', $orcid_url . $params,
            ['headers' => ['Accept' => 'application/vnd.orcid+json']]);

      $json_string = (string) $result->getBody();
      $json = json_decode($json_string, TRUE);

      if (isset($json['expanded-result'][0])) {
        $name_identifier = (string) $json['expanded-result'][0]['orcid-id'];
        $given_name = (string) $json['expanded-result'][0]['given-names'];
        $family_name = (string) $json['expanded-result'][0]['family-names'];
        $email = $json['expanded-result'][0]['email'][0] ?? '';
        $orcid_org_name = $json['expanded-result'][0]['institution-name'][0] ?? '';
      }
      else {
        $name_identifier = NULL;
        $given_name = NULL;
        $family_name = NULL;
        $email = NULL;
        $orcid_org_name = NULL;
      }

      $target_form_element = NestedArray::getValue($form, array_slice($triggering['#array_parents'], 0, -1));

      $target_form_element['orcid_search_info']['#value'] = NULL;
      $target_form_element['given_name']['#value'] = $given_name;
      $target_form_element['family_name']['#value'] = $family_name;
      $target_form_element['email']['#value'] = $email;
      $target_form_element['orcid_id']['#value'] = $name_identifier;
      $target_form_element['organization_name']['#value'] = $orcid_org_name;
      $target_form_element['_weight'] = [];
    }
    else {
      $target_form_element = NestedArray::getValue($form, array_slice($triggering['#array_parents'], 0, -1));
    }

    return $target_form_element;
  }

  /**
   * The actual form elements that represent the author field widget.
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state): array {

    $index = Html::getUniqueId('osp-orcid');
    $element['osp_author_field'] = [
      '#type' => 'details',
      '#title' => $this->t('Author Details'),
      '#open' => TRUE,
      '#attributes' => [
        'id' => "orcid-data-wrapper-$index",
      ],
      '#prefix' => '<div id="orcid-data-wrapper-' . $index . '">',
      '#suffix' => '</div>',
    ];

    $element['osp_author_field']['orcid_search_info'] = [
      '#title' => $this->t('Search'),
      '#type' => 'textfield',
      '#size' => 50,
      '#maxlength' => 255,
      '#autocomplete_route_name' => 'author_field.autocomplete',
      '#default_value' => NULL,
      '#description' => $this->t('Search your ORCID identifier'),

      '#attributes' => [
        'autocomplete' => 'off',
        'role' => 'presentation',
        'placeholder' => t('Name, ORCID ID, or organization name'),
      ],

      '#ajax' => [
        'callback' => [$this, 'orcidCallback'],
        'event' => 'autocompleteclose',
        'wrapper' => "orcid-data-wrapper-$index",
        'suppress_required_fields_validation' => TRUE,
      ],

    ];

    if (!$this->getSetting('hideFamilyName')) {
      $element['osp_author_field']['family_name'] = [
        '#title' => t('Family Name'),
        '#type' => 'textfield',
        '#size' => 20,
        '#default_value' => $items[$delta]->family_name ?? NULL,
        '#placeholder' => t('Family Name'),
      ];
    }

    if (!$this->getSetting('hideGivenName')) {
      $element['osp_author_field']['given_name'] = [
        '#title' => t('Given Name'),
        '#type' => 'textfield',
        '#size' => 20,
        '#default_value' => $items[$delta]->given_name ?? NULL,
        '#placeholder' => t('Given Name'),
      ];
    }

    if (!$this->getSetting('hideEmail')) {
      $element['osp_author_field']['email'] = [
        '#title' => t('Email'),
        '#type' => 'textfield',
        '#size' => 20,
        '#default_value' => $items[$delta]->email ?? NULL,
        '#placeholder' => t('Email'),
      ];
    }

    if (!$this->getSetting('hideNameIdentifier')) {
      $element['osp_author_field']['orcid_id'] = [
        '#title' => t('ORCID ID'),
        '#type' => 'textfield',
        '#size' => 20,
        '#default_value' => $items[$delta]->orcid_id ?? NULL,
        '#placeholder' => t('ORCID ID'),
      ];
    }

    if (!$this->getSetting('hideOrganizationName')) {
      $element['osp_author_field']['organization_name'] = [
        '#type' => 'textfield',
        '#title' => $this->t('Organization Name'),
        '#size' => 50,
        '#maxlength' => 255,
        '#default_value' => $items[$delta]->organization_name ?? NULL,
        '#placeholder' => t('Organization Name'),
      ];
    }

    // If cardinality is 1, ensure a proper label is output for the field.
    // Wrap everything in a details element.
    if ($this->fieldDefinition->getFieldStorageDefinition()->getCardinality() == 1) {
      $field_name = $this->fieldDefinition->getName();
      $element += [
        '#type' => 'markup',
        '#markup' => $this->t('<h4 style="margin-bottom: 1em;">@Author</h4><hr style="color: #0a6eb4">',
                ['@Author' => ucfirst($field_name)]),
      ];
    }

    return $element;
  }

  /**
   * Massages the form values into the format expected for field values.
   */
  public function massageFormValues(array $values, array $form, FormStateInterface $form_state): array {
    foreach ($values as $delta => $value) {
      if (isset($value['osp_author_field']['family_name'])) {
        if ($value['osp_author_field']['family_name'] === '') {
          $values[$delta]['family_name'] = NULL;
        }
        else {
          $values[$delta]['family_name'] = $value['osp_author_field']['family_name'];
        }
      }
      else {
        $values[$delta]['family_name'] = NULL;
      }

      if (isset($value['osp_author_field']['given_name'])) {
        if ($value['osp_author_field']['given_name'] === '') {
          $values[$delta]['given_name'] = NULL;
        }
        else {
          $values[$delta]['given_name'] = $value['osp_author_field']['given_name'];
        }
      }
      else {
        $values[$delta]['given_name'] = NULL;
      }

      if (isset($value['osp_author_field']['email'])) {
        if ($value['osp_author_field']['email'] === '') {
          $values[$delta]['email'] = NULL;
        }
        else {
          $values[$delta]['email'] = $value['osp_author_field']['email'];
        }
      }
      else {
        $values[$delta]['email'] = NULL;
      }

      if (isset($value['osp_author_field']['orcid_id'])) {
        if ($value['osp_author_field']['orcid_id'] === '') {
          $values[$delta]['orcid_id'] = NULL;
        }
        else {
          $values[$delta]['orcid_id'] = $value['osp_author_field']['orcid_id'];
        }
      }
      else {
        $values[$delta]['orcid_id'] = NULL;
      }

      if (isset($value['osp_author_field']['organization_name'])) {
        if ($value['osp_author_field']['organization_name'] === '') {
          $values[$delta]['organization_name'] = NULL;
        }
        else {
          $values[$delta]['organization_name'] = $value['osp_author_field']['organization_name'];
        }
      }
      else {
        $values[$delta]['organization_name'] = NULL;
      }
    }

    return $values;
  }

}
