<?php

namespace Drupal\eb_ui\Form;

use Drupal\Core\Access\CsrfRequestHeaderAccessCheck;
use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\eb\Entity\EbDefinition;
use Drupal\eb_ui\Service\GridProviderManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Yaml\Yaml;

/**
 * Provides a YAML editor form for Entity Builder definitions.
 *
 * This form is the fallback when no grid provider is active, or when
 * explicitly selected via editor mode settings.
 */
class EbUiYamlForm extends FormBase {

  /**
   * Constructor.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Drupal\Core\Access\CsrfTokenGenerator $csrfTokenGenerator
   *   The CSRF token generator.
   * @param \Drupal\eb_ui\Service\GridProviderManagerInterface $providerManager
   *   The grid provider manager.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   The config factory.
   */
  public function __construct(
    protected EntityTypeManagerInterface $entityTypeManager,
    protected CsrfTokenGenerator $csrfTokenGenerator,
    protected GridProviderManagerInterface $providerManager,
    ConfigFactoryInterface $configFactory,
  ) {
    $this->configFactory = $configFactory;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container): self {
    return new self(
      $container->get('entity_type.manager'),
      $container->get('csrf_token'),
      $container->get('eb_ui.grid_provider_manager'),
      $container->get('config.factory'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId(): string {
    return 'eb_ui_yaml_form';
  }

  /**
   * {@inheritdoc}
   *
   * @param array<string, mixed> $form
   *   The form structure.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param string|null $definition_id
   *   The definition ID if editing.
   *
   * @return array<string, mixed>
   *   The form array.
   */
  public function buildForm(array $form, FormStateInterface $form_state, ?string $definition_id = NULL): array {
    // Load existing definition if editing.
    $definition = NULL;
    if ($definition_id) {
      /** @var \Drupal\eb\Entity\EbDefinition|null $definition */
      $definition = $this->entityTypeManager
        ->getStorage('eb_definition')
        ->load($definition_id);
    }

    // Defense-in-depth: verify access programmatically.
    // Routes should handle access, but this ensures form-level security.
    $this->verifyAccess($definition);

    // Add wrapper class for custom styling.
    $form['#attributes']['class'][] = 'eb-ui-yaml-form';

    // Attach the YAML editor library.
    $yaml_editor_type = $this->configFactory->get('eb_ui.settings')->get('yaml_editor') ?? 'codemirror_cdn';
    if ($yaml_editor_type === 'codemirror_cdn') {
      $form['#attached']['library'][] = 'eb_ui/codemirror';
    }
    $form['#attached']['library'][] = 'eb_ui/yaml-editor';

    // Pass settings to JavaScript.
    $form['#attached']['drupalSettings']['ebUi'] = [
      'csrfToken' => $this->csrfTokenGenerator->get(CsrfRequestHeaderAccessCheck::TOKEN_KEY),
      'yamlEditorType' => $yaml_editor_type,
    ];

    // Definition metadata.
    $form['metadata'] = [
      '#type' => 'details',
      '#title' => $this->t('Definition Metadata'),
      '#open' => TRUE,
    ];

    $form['metadata']['label'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Label'),
      '#default_value' => $definition?->label() ?? '',
      '#required' => TRUE,
    ];

    $form['metadata']['definition_id'] = [
      '#type' => 'machine_name',
      '#title' => $this->t('Definition ID'),
      '#default_value' => $definition?->id() ?? '',
      '#machine_name' => [
        'exists' => [$this, 'definitionExists'],
        'source' => ['metadata', 'label'],
      ],
      '#required' => TRUE,
      '#disabled' => $definition !== NULL,
    ];

    $form['metadata']['description'] = [
      '#type' => 'textarea',
      '#title' => $this->t('Description'),
      '#default_value' => $definition?->getDescription() ?? '',
      '#rows' => 2,
    ];

    // Toolbar with validation and preview buttons.
    $form['toolbar'] = [
      '#type' => 'inline_template',
      '#template' => '
        <div class="eb-yaml-toolbar">
          <div class="eb-yaml-toolbar__group">
            <button type="button" class="eb-yaml-toolbar__btn eb-validate-btn">{{ validate_label }}</button>
            <button type="button" class="eb-yaml-toolbar__btn eb-preview-btn">{{ preview_label }}</button>
          </div>
          <div class="eb-yaml-toolbar__status">
            <span class="eb-validation-status">
              <span class="eb-validation-status__icon"></span>
              <span class="eb-validation-status__text"></span>
            </span>
          </div>
        </div>
      ',
      '#context' => [
        'validate_label' => $this->t('Validate'),
        'preview_label' => $this->t('Preview'),
      ],
    ];

    // YAML editor container.
    $form['yaml_container'] = [
      '#type' => 'container',
      '#attributes' => [
        'class' => ['eb-yaml-container'],
      ],
    ];

    // Convert definition to YAML content.
    $yaml_content = '';
    if ($definition) {
      $yaml_content = $this->definitionToYaml($definition);
    }

    $form['yaml_container']['yaml_content'] = [
      '#type' => 'textarea',
      '#title' => $this->t('YAML Definition'),
      '#default_value' => $yaml_content,
      '#rows' => 30,
      '#attributes' => [
        'id' => 'eb-yaml-editor',
        'class' => ['eb-yaml-textarea'],
      ],
    ];

    // Preview container.
    $form['preview_container'] = [
      '#type' => 'container',
      '#attributes' => [
        'class' => ['eb-preview-container'],
        'style' => 'display: none;',
      ],
    ];

    $form['preview_container']['preview_content'] = [
      '#type' => 'inline_template',
      '#template' => '<div class="eb-preview-content"></div>',
    ];

    // Form actions.
    $form['actions'] = [
      '#type' => 'actions',
    ];

    $form['actions']['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Save Definition'),
      '#button_type' => 'primary',
    ];

    // Only show "Save and Apply" if user has apply access.
    if ($definition && $definition->access('apply')) {
      $form['actions']['apply'] = [
        '#type' => 'submit',
        '#value' => $this->t('Save and Apply'),
        '#submit' => ['::saveAndApply'],
      ];
    }

    return $form;
  }

  /**
   * Converts an EbDefinition entity to YAML content.
   *
   * @param \Drupal\eb\Entity\EbDefinition $definition
   *   The definition entity.
   *
   * @return string
   *   YAML content.
   */
  protected function definitionToYaml(EbDefinition $definition): string {
    $data = [
      'version' => EbDefinition::FORMAT_VERSION,
      'mode' => 'sync',
      'scope' => 'partial',
      'id' => $definition->id(),
      'label' => $definition->label(),
    ];

    $description = $definition->getDescription();
    if (!empty($description)) {
      $data['description'] = $description;
    }

    $bundle_definitions = $definition->getBundleDefinitions();
    if (!empty($bundle_definitions)) {
      $data['bundle_definitions'] = $bundle_definitions;
    }

    $field_definitions = $definition->getFieldDefinitions();
    if (!empty($field_definitions)) {
      $data['field_definitions'] = $field_definitions;
    }

    $field_group_definitions = $definition->getFieldGroupDefinitions();
    if (!empty($field_group_definitions)) {
      $data['field_group_definitions'] = $field_group_definitions;
    }

    $display_field_definitions = $definition->getDisplayFieldDefinitions();
    if (!empty($display_field_definitions)) {
      $data['display_field_definitions'] = $display_field_definitions;
    }

    $menu_definitions = $definition->getMenuDefinitions();
    if (!empty($menu_definitions)) {
      $data['menu_definitions'] = $menu_definitions;
    }

    return Yaml::dump($data, 10, 2);
  }

  /**
   * Checks if a definition ID already exists.
   *
   * @param string $value
   *   The machine name value.
   *
   * @return bool
   *   TRUE if the definition exists, FALSE otherwise.
   */
  public function definitionExists(string $value): bool {
    $definition = $this->entityTypeManager
      ->getStorage('eb_definition')
      ->load($value);
    return $definition !== NULL;
  }

  /**
   * {@inheritdoc}
   *
   * @param array<string, mixed> $form
   *   The form structure.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   */
  public function validateForm(array &$form, FormStateInterface $form_state): void {
    $yaml_content = $form_state->getValue('yaml_content');

    if (empty($yaml_content)) {
      $form_state->setErrorByName('yaml_content', $this->t('YAML content is required.'));
      return;
    }

    try {
      $parsed = Yaml::parse($yaml_content);
      if (!is_array($parsed)) {
        $form_state->setErrorByName('yaml_content', $this->t('Invalid YAML: content must be an array.'));
      }
    }
    catch (\Exception $e) {
      $form_state->setErrorByName('yaml_content', $this->t('Invalid YAML: @message', [
        '@message' => $e->getMessage(),
      ]));
    }
  }

  /**
   * {@inheritdoc}
   *
   * @param array<string, mixed> $form
   *   The form structure.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   */
  public function submitForm(array &$form, FormStateInterface $form_state): void {
    $definition_id = $form_state->getValue('definition_id');
    $yaml_content = $form_state->getValue('yaml_content');

    try {
      $parsed = Yaml::parse($yaml_content);

      // Load or create the definition.
      $storage = $this->entityTypeManager->getStorage('eb_definition');
      /** @var \Drupal\eb\Entity\EbDefinition|null $definition */
      $definition = $storage->load($definition_id);

      if (!$definition) {
        /** @var \Drupal\eb\Entity\EbDefinition $definition */
        $definition = $storage->create(['id' => $definition_id]);
      }

      // Set metadata.
      $definition->set('label', $form_state->getValue('label'));
      $definition->setDescription($form_state->getValue('description') ?? '');

      // Set definition data from YAML.
      $definition->setBundleDefinitions($parsed['bundle_definitions'] ?? []);
      $definition->setFieldDefinitions($parsed['field_definitions'] ?? []);
      $definition->setFieldGroupDefinitions($parsed['field_group_definitions'] ?? []);
      $definition->setDisplayFieldDefinitions($parsed['display_field_definitions'] ?? []);
      $definition->setMenuDefinitions($parsed['menu_definitions'] ?? []);

      $definition->save();

      $this->messenger()->addStatus($this->t('Definition "@label" has been saved.', [
        '@label' => $definition->label(),
      ]));

      $form_state->setRedirect('eb_ui.edit', [
        'definition_id' => $definition_id,
      ]);
    }
    catch (\Exception $e) {
      $this->messenger()->addError($this->t('Failed to save definition: @message', [
        '@message' => $e->getMessage(),
      ]));
    }
  }

  /**
   * Save and apply submit handler.
   *
   * @param array<string, mixed> $form
   *   The form structure.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   */
  public function saveAndApply(array &$form, FormStateInterface $form_state): void {
    // First save the definition.
    $this->submitForm($form, $form_state);

    // Then redirect to apply form.
    $definition_id = $form_state->getValue('definition_id');
    $form_state->setRedirect('entity.eb_definition.apply_form', [
      'eb_definition' => $definition_id,
    ]);
  }

  /**
   * Verify access to the form (defense-in-depth).
   *
   * Routes should handle access control, but this provides an additional
   * layer of security to ensure users have the proper permissions.
   *
   * @param \Drupal\eb\Entity\EbDefinition|null $definition
   *   The definition entity, or NULL for new definitions.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
   *   If the user does not have access.
   */
  protected function verifyAccess(?EbDefinition $definition): void {
    $access_handler = $this->entityTypeManager
      ->getAccessControlHandler('eb_definition');

    if ($definition === NULL) {
      // Creating new definition - check create access.
      if (!$access_handler->createAccess()) {
        throw new AccessDeniedHttpException('You do not have permission to create entity definitions.');
      }
    }
    else {
      // Editing existing definition - check update access.
      if (!$definition->access('update')) {
        throw new AccessDeniedHttpException('You do not have permission to edit this entity definition.');
      }
    }
  }

}
