<?php

namespace Civi\Api4\Action\Afform;

use Civi\Core\Event\GenericHookEvent;
use CRM_Afform_ExtensionUtil as E;

/**
 * @inheritDoc
 * @package Civi\Api4\Action\Afform
 */
class Get extends \Civi\Api4\Generic\BasicGetAction {

  use \Civi\Api4\Utils\AfformFormatTrait;

  public function getRecords() {
    $afforms = [];

    /** @var \CRM_Afform_AfformScanner $scanner */
    $scanner = \Civi::service('afform_scanner');

    // Optimization: only fetch extra data if requested
    $getComputed = $this->_isFieldSelected('has_local', 'has_base', 'base_module');
    $getLayout = $this->_isFieldSelected('layout');
    $getSearchDisplays = $this->_isFieldSelected('search_displays');
    // To optimize lookups by file/module/directive name
    $getNames = array_filter([
      'name' => $this->_itemsToGet('name'),
      'module_name' => $this->_itemsToGet('module_name'),
      'directive_name' => $this->_itemsToGet('directive_name'),
    ]);
    // To optimize lookups by type
    $getTypes = $this->_itemsToGet('type');

    $names = $getNames['name'] ?? array_keys($scanner->findFilePaths());

    // Allow hooks to provide autoGenerated forms
    $hookForms = [];
    $hookParams = ['afforms' => &$hookForms, 'getNames' => $getNames, 'getTypes' => $getTypes, 'getLayout' => $getLayout];
    $event = GenericHookEvent::create($hookParams);
    \Civi::dispatcher()->dispatch('civi.afform.get', $event);
    // Set defaults for Afforms supplied by hook
    foreach ($hookForms as $afform) {
      $names[] = $afform['name'];
      $afform['has_base'] = TRUE;
      $afforms[$afform['name']] = $afform;
    }

    $names = array_unique($names);

    foreach ($names as $name) {
      $info = [
        'name' => $name,
        'module_name' => _afform_angular_module_name($name, 'camel'),
        'directive_name' => _afform_angular_module_name($name, 'dash'),
      ];
      // Skip if afform does not match requested name
      foreach ($getNames as $key => $names) {
        if (!in_array($info[$key], $names)) {
          unset($afforms[$name]);
          continue 2;
        }
      }
      $record = $scanner->getMeta($name, $getLayout || $getSearchDisplays);
      // Skip if afform does not exist or is not of requested type(s)
      if (
        (!$record && !isset($afforms[$name])) ||
        ($getTypes && isset($record['type']) && !in_array($record['type'], $getTypes, TRUE))
      ) {
        unset($afforms[$name]);
        continue;
      }
      $afforms[$name] = array_merge($afforms[$name] ?? [], $record ?? [], $info);
      if (isset($afforms[$name]['permission']) && is_string($afforms[$name]['permission'])) {
        $afforms[$name]['permission'] = explode(',', $afforms[$name]['permission']);
      }
      // No permissions specified, set default.
      if (empty($afforms[$name]['permission'])) {
        $afforms[$name]['permission'] = ['access CiviCRM'];
      }
      if (!$this->checkPermission($afforms[$name])) {
        unset($afforms[$name]);
        continue;
      }
      if ($getComputed) {
        $scanner->addComputedFields($afforms[$name]);
      }
      if ($getSearchDisplays && !empty($afforms[$name]['layout'])) {
        $afforms[$name]['search_displays'] = $this->getSearchDisplays($afforms[$name]['layout']);
      }
      if (!isset($afforms[$name]['placement']) && $this->_isFieldSelected('placement')) {
        self::convertLegacyPlacement($afforms[$name]);
      }
    }

    // Format layouts
    if ($getLayout) {
      foreach ($afforms as $name => $record) {
        if (isset($record['layout'])) {
          $this->convertLegacyContactTypes($afforms[$name]['layout']);
          if ($this->layoutFormat !== 'html') {
            $afforms[$name]['layout'] = $this->convertHtmlToOutput($afforms[$name]['layout']);
          }
        }
      }
    }

    // Fetch submission aggregates in bulk
    if ($afforms && $this->_isFieldSelected('submission_count', 'submission_date', 'submit_currently_open')) {
      $afformSubmissions = \Civi\Api4\AfformSubmission::get(FALSE)
        ->addSelect('afform_name', 'COUNT(id) AS count', 'MAX(submission_date) AS date')
        ->addWhere('afform_name', 'IN', array_keys($afforms))
        ->addGroupBy('afform_name')
        ->execute()->indexBy('afform_name');
      foreach ($afforms as $name => $record) {
        $afforms[$name]['submission_count'] = $afformSubmissions[$name]['count'] ?? 0;
        $afforms[$name]['submission_date'] = $afformSubmissions[$name]['date'] ?? NULL;
        $afforms[$name]['submit_currently_open'] = ($record['submit_enabled'] ?? TRUE) && (empty($record['submit_limit']) || $record['submit_limit'] > $afforms[$name]['submission_count']);
      }
    }

    return $afforms;
  }

  /**
   * Assert that a form is authorized.
   *
   * @return bool
   */
  protected function checkPermission($afform) {
    if (!$this->checkPermissions) {
      return TRUE;
    }
    if (($afform['permission_operator'] ?? NULL) === 'OR') {
      $afform['permission'] = [$afform['permission']];
    }
    return \CRM_Core_Permission::check($afform['permission']);
  }

  /**
   * Generates afform blocks from custom field sets.
   *
   * @param \Civi\Core\Event\GenericHookEvent $event
   * @throws \CRM_Core_Exception
   */
  public static function getCustomGroupBlocks($event) {
    // Early return if blocks are not requested
    if ($event->getTypes && !in_array('block', $event->getTypes, TRUE)) {
      return;
    }

    $getNames = $event->getNames;
    $getLayout = $event->getLayout;
    $groupNames = [];
    $afforms =& $event->afforms;
    foreach ($getNames['name'] ?? [] as $name) {
      if (str_starts_with($name, 'afblockCustom_') && strlen($name) > 13) {
        $groupNames[] = substr($name, 14);
      }
    }
    // Early return if this api call is fetching afforms by name and those names are not custom-related
    if ((!empty($getNames['name']) && !$groupNames)
      || (!empty($getNames['module_name']) && !str_contains(implode(' ', $getNames['module_name']), 'afblockCustom'))
      || (!empty($getNames['directive_name']) && !str_contains(implode(' ', $getNames['directive_name']), 'afblock-custom'))
    ) {
      return;
    }
    $filters = [
      'is_active' => TRUE,
    ];
    if ($groupNames) {
      $filters['name'] = $groupNames;
    }
    $customGroups = \CRM_Core_BAO_CustomGroup::getAll($filters);
    foreach ($customGroups as $custom) {
      $name = 'afblockCustom_' . $custom['name'];
      $item = [
        'name' => $name,
        'type' => 'block',
        'requires' => [],
        'title' => E::ts('%1 block', [1 => $custom['title']]),
        'description' => '',
        'is_public' => FALSE,
        'permission' => ['access CiviCRM'],
        'entity_type' => $custom['extends'],
      ];
      if ($custom['is_multiple']) {
        $item['join_entity'] = 'Custom_' . $custom['name'];
      }
      if ($getLayout) {
        $item['layout'] = ($custom['help_pre'] ? '<div class="af-markup">' . $custom['help_pre'] . "</div>\n" : '');
        foreach ($custom['fields'] as $field) {
          $nameAttribute = $field['name'];
          // for multiple record fields there is no need to prepend the custom group name
          // because it is provided as the join_entity above
          if (!$custom['is_multiple']) {
            $nameAttribute = $custom['name'] . "." . $nameAttribute;
          }
          $item['layout'] .= "<af-field name=\"{$nameAttribute}\" />\n";
        }
        $item['layout'] .= ($custom['help_post'] ? '<div class="af-markup">' . $custom['help_post'] . "</div>\n" : '');
      }
      $afforms[$name] = $item;
    }
  }

  /**
   * Find search display tags in afform markup
   *
   * @param string $html
   * @return string[]
   */
  private function getSearchDisplays(string $html) {
    $tags = $searchDisplays = [];
    preg_match_all('/<\\s*crm-search-display[^>]+>/', $html, $tags);
    foreach ($tags[0] ?? [] as $tag) {
      $searchName = $displayName = [];
      preg_match('/search-name\\s*=\\s*[\'"]([^\'"]+)[\'"]/', $tag, $searchName);
      // Note: display name will be blank when using the default (autogenerated) display
      preg_match('/display-name\\s*=\\s*[\'"]([^\'"]+)[\'"]/', $tag, $displayName);
      if (!empty($searchName[1])) {
        $searchDisplays[] = $searchName[1] . (empty($displayName[1]) ? '' : '.' . $displayName[1]);
      }
    }
    return $searchDisplays;
  }

  /**
   * Backward-compatability conversion of <af-entity> Contact tags
   * to use contact-type-specific API entity names.
   *
   * Converts: <af-entity type="Contact" data="{contact_type: 'Individual'}">
   * Into:     <af-entity type="Individual" data="{}">
   *
   * @param string $layout
   * @return void
   */
  private static function convertLegacyContactTypes(&$layout): void {
    if (!$layout || !is_string($layout)) {
      return;
    }
    // It was easier to write two regexes:
    // 1. If the `type` property precedes the `data` property
    $regex1 = <<<'REGEX'
      /<af-entity([^>]+)type\s*=\s*["']Contact["']([^>]*)data\s*=\s*"\{([^>]*)'?contact_type'?\s*:\s*'(Individual|Household|Organization)'\s*,?/
      REGEX;
    $layout = preg_replace($regex1, '<af-entity$1type="$4"$2data="{', $layout);
    // 2. If the `data` property precedes the `type` property
    $regex2 = <<<'REGEX'
      /<af-entity([^>]+)data\s*=\s*"\{([^>]*)'?contact_type'?\s*:\s*'(Individual|Household|Organization)'\s*,?([^>]+)type\s*=\s*["']Contact["']/
      REGEX;
    $layout = preg_replace($regex2, '<af-entity$1data="{$2$4type="$3"', $layout);
  }

  private static function convertLegacyPlacement(array &$afform): void {
    $afform['placement'] = [];
    if (!empty($afform['is_dashlet'])) {
      $afform['placement'][] = 'dashboard_dashlet';
    }
    if (!empty($afform['is_token'])) {
      $afform['placement'][] = 'msg_token';
    }
    if (!empty($afform['contact_summary'])) {
      $afform['placement'][] = 'contact_summary_' . $afform['contact_summary'];
    }
    unset($afform['is_dashlet'], $afform['is_token'], $afform['contact_summary']);
  }

}
