<?php

namespace Civi\Api4\Action\Afform;

use Civi\Api4\CustomField;
use Civi\Api4\CustomGroup;
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() {
    /** @var \CRM_Afform_AfformScanner $scanner */
    $scanner = \Civi::service('afform_scanner');
    $getComputed = $this->_isFieldSelected('has_local', 'has_base', 'base_module');
    $getLayout = $this->_isFieldSelected('layout');
    $getSearchDisplays = $this->_isFieldSelected('search_displays');
    $values = [];

    // This helps 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'),
    ]);
    $getTypes = $this->_itemsToGet('type');

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

    // Get autogenerated blocks if type block is not excluded
    if (!$getTypes || in_array('block', $getTypes, TRUE)) {
      $values = $this->getAutoGenerated($names, $getNames, $getLayout);
    }

    if ($this->checkPermissions) {
      $names = array_filter($names, [$this, 'checkPermission']);
    }

    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)) {
          continue 2;
        }
      }
      $record = $scanner->getMeta($name);
      // Skip if afform does not exist or is not of requested type(s)
      if (
        (!$record && !isset($values[$name])) ||
        ($getTypes && isset($record['type']) && !in_array($record['type'], $getTypes, TRUE))
      ) {
        continue;
      }
      $values[$name] = array_merge($values[$name] ?? [], $record ?? [], $info);
      if ($getComputed) {
        $scanner->addComputedFields($values[$name]);
      }
      if ($getLayout || $getSearchDisplays) {
        // Autogenerated layouts will already be in values but can be overridden; scanner takes priority
        $values[$name]['layout'] = $scanner->getLayout($name) ?? $values[$name]['layout'] ?? '';
      }
      if ($getSearchDisplays) {
        $values[$name]['search_displays'] = $this->getSearchDisplays($values[$name]['layout']);
      }
    }

    if ($getLayout && $this->layoutFormat !== 'html') {
      foreach ($values as $name => $record) {
        $values[$name]['layout'] = $this->convertHtmlToOutput($record['layout']);
      }
    }

    return $values;
  }

  /**
   * Assert that a form is authorized.
   *
   * @return bool
   */
  protected function checkPermission($name) {
    return \CRM_Core_Permission::check("@afform:$name");
  }

  /**
   * Generates afform blocks from custom field sets.
   *
   * @param array $names
   * @param array $getNames
   * @param bool $getLayout
   * @return array
   * @throws \API_Exception
   */
  protected function getAutoGenerated(&$names, $getNames, $getLayout) {
    $values = $groupNames = [];
    foreach ($getNames['name'] ?? [] as $name) {
      if (strpos($name, 'afblockCustom_') === 0 && 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']) && !strstr(implode(' ', $getNames['module_name']), 'afblockCustom'))
      || (!empty($getNames['directive_name']) && !strstr(implode(' ', $getNames['directive_name']), 'afblock-custom'))
    ) {
      return $values;
    }
    $customApi = CustomGroup::get(FALSE)
      ->addSelect('name', 'title', 'help_pre', 'help_post', 'extends', 'max_multiple')
      ->addWhere('is_multiple', '=', 1)
      ->addWhere('is_active', '=', 1);
    if ($groupNames) {
      $customApi->addWhere('name', 'IN', $groupNames);
    }
    if ($getLayout) {
      $customApi->addSelect('help_pre', 'help_post');
      $customApi->addChain('fields', CustomField::get(FALSE)
        ->addSelect('name')
        ->addWhere('custom_group_id', '=', '$id')
        ->addWhere('is_active', '=', 1)
        ->addOrderBy('weight', 'ASC')
      );
    }
    foreach ($customApi->execute() as $custom) {
      $name = 'afblockCustom_' . $custom['name'];
      if (!in_array($name, $names)) {
        $names[] = $name;
      }
      $item = [
        'name' => $name,
        'type' => 'block',
        'requires' => [],
        'title' => E::ts('%1 block', [1 => $custom['title']]),
        'description' => '',
        'is_dashlet' => FALSE,
        'is_public' => FALSE,
        'is_token' => FALSE,
        'permission' => 'access CiviCRM',
        'join_entity' => 'Custom_' . $custom['name'],
        'entity_type' => $custom['extends'],
        'has_base' => TRUE,
      ];
      if ($getLayout) {
        $item['layout'] = ($custom['help_pre'] ? '<div class="af-markup">' . $custom['help_pre'] . "</div>\n" : '');
        foreach ($custom['fields'] as $field) {
          $item['layout'] .= "<af-field name=\"{$field['name']}\" />\n";
        }
        $item['layout'] .= ($custom['help_post'] ? '<div class="af-markup">' . $custom['help_post'] . "</div>\n" : '');
      }
      $values[$name] = $item;
    }
    return $values;
  }

  /**
   * 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;
  }

}
