<?php

declare(strict_types=1);

namespace Drupal\flowdrop_interrupt\Plugin\InterruptHandler;

use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\flowdrop\Service\FlowDropSchemaFormBuilderInterface;
use Drupal\flowdrop_interrupt\Attribute\FlowDropInterruptHandler;
use Drupal\flowdrop_interrupt\Entity\FlowDropInterruptInterface;
use Drupal\flowdrop_interrupt\Enum\InterruptType;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Universal HITL handler driven by a FlowDropSchema fragment.
 *
 * Every HITL interrupt is, fundamentally, "collect a value matching this
 * schema." This handler renders proper Drupal widgets from the schema via
 * FlowDropSchemaFormBuilder — boolean → checkbox/buttons, enum →
 * radios/select, string → textfield/textarea/date/email, object with
 * properties → nested fieldset, etc.
 *
 * Also handles the four legacy semantic types (`confirmation`, `choice`,
 * `text`, `form`) by synthesising a FlowDropSchema fragment from the
 * interrupt's type + options + stored schema. Routing is handled in
 * {@see \Drupal\flowdrop_interrupt\Service\InterruptHandlerPluginManager::loadHandler()}.
 *
 * @api
 */
#[FlowDropInterruptHandler(
  id: 'schema_form',
  label: new TranslatableMarkup('Schema-driven form'),
  description: 'Universal HITL: collect data described by a FlowDropSchema fragment',
)]
final class SchemaFormHandler extends AbstractInterruptHandler implements HitlInterruptHandlerInterface {

  use StringTranslationTrait;

  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    private readonly FlowDropSchemaFormBuilderInterface $formBuilder,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(
    ContainerInterface $container,
    array $configuration,
    $plugin_id,
    $plugin_definition,
  ): static {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('flowdrop.schema_form_builder'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function buildResolveForm(array $form, FormStateInterface $formState, FlowDropInterruptInterface $interrupt): array {
    $schema = $this->effectiveSchema($interrupt);

    // Confirmation uses a button pair instead of a normal form element;
    // route it through a separate path so submitted values aren't run
    // through extractValue(), which can't make sense of a button container.
    if ($this->isConfirmation($schema)) {
      return $this->buildConfirmationForm($form, $interrupt, $schema);
    }

    $form['response'] = $this->formBuilder->buildElement(
      $schema,
      $interrupt->getDefaultValue(),
      ['title' => $this->t('Your response'), 'required' => TRUE],
    );

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

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $formState, FlowDropInterruptInterface $interrupt): void {
    $schema = $this->effectiveSchema($interrupt);
    // Confirmation: nothing to validate — the submitted button identity is
    // the response.
    if ($this->isConfirmation($schema)) {
      return;
    }
    $raw = $formState->getValue('response');
    $value = $this->formBuilder->extractValue($schema, $raw);
    $this->formBuilder->validateValue($schema, $value, $formState, ['response']);
  }

  /**
   * {@inheritdoc}
   */
  public function extractResponseFromForm(FormStateInterface $formState, FlowDropInterruptInterface $interrupt): mixed {
    $schema = $this->effectiveSchema($interrupt);
    if ($this->isConfirmation($schema)) {
      $triggering = $formState->getTriggeringElement();
      return ($triggering['#name'] ?? '') === 'confirm';
    }
    $raw = $formState->getValue('response');
    return $this->formBuilder->extractValue($schema, $raw);
  }

  /**
   * {@inheritdoc}
   *
   * Returns the raw extracted value — extractResponseFromForm has already
   * coerced through FlowDropSchemaFormBuilder.
   */
  public function normalizeResponse(mixed $raw, FlowDropInterruptInterface $interrupt): mixed {
    return $raw;
  }

  /**
   * Synthesises an effective FlowDropSchema fragment for an interrupt.
   *
   * Bridges the gap between legacy type IDs (confirmation/choice/text/form)
   * whose schema field may be empty or partial, and the universal
   * schema-driven model. The interrupt's `type` tells us which conventions
   * to apply when synthesising.
   *
   * @param \Drupal\flowdrop_interrupt\Entity\FlowDropInterruptInterface $interrupt
   *   The interrupt being rendered.
   *
   * @return array<string, mixed>
   *   The effective schema.
   */
  private function effectiveSchema(FlowDropInterruptInterface $interrupt): array {
    $rawSchema = $interrupt->getSchema();
    $type = $interrupt->getType();

    return match ($type) {
      InterruptType::Confirmation => $this->confirmationSchema($interrupt, $rawSchema),
      InterruptType::Choice => $this->choiceSchema($interrupt, $rawSchema),
      InterruptType::Text => $this->textSchema($rawSchema),
      InterruptType::Form, InterruptType::SchemaForm => $rawSchema !== [] ? $rawSchema : ['type' => 'object'],
      default => $rawSchema,
    };
  }

  /**
   * Builds the confirmation schema.
   *
   * @param \Drupal\flowdrop_interrupt\Entity\FlowDropInterruptInterface $interrupt
   *   The interrupt being rendered.
   * @param array<string, mixed> $rawSchema
   *   The stored raw schema.
   *
   * @return array<string, mixed>
   *   The effective confirmation schema.
   */
  private function confirmationSchema(FlowDropInterruptInterface $interrupt, array $rawSchema): array {
    $options = $interrupt->getOptions();
    return [
      'type' => 'boolean',
      'presentation' => 'confirmation',
      'confirmLabel' => $rawSchema['confirmLabel'] ?? ($options[0]['label'] ?? 'Confirm'),
      'declineLabel' => $rawSchema['declineLabel'] ?? ($options[1]['label'] ?? 'Decline'),
    ];
  }

  /**
   * Builds the choice schema.
   *
   * @param \Drupal\flowdrop_interrupt\Entity\FlowDropInterruptInterface $interrupt
   *   The interrupt being rendered.
   * @param array<string, mixed> $rawSchema
   *   The stored raw schema.
   *
   * @return array<string, mixed>
   *   The effective choice schema.
   */
  private function choiceSchema(FlowDropInterruptInterface $interrupt, array $rawSchema): array {
    $multiple = (bool) ($rawSchema['multiple'] ?? FALSE);
    $enum = array_values(array_map(
      static fn (array $o): string => (string) $o['value'],
      $interrupt->getOptions(),
    ));
    return $multiple
      ? ['type' => 'array', 'items' => ['type' => 'string', 'enum' => $enum]]
      : ['type' => 'string', 'enum' => $enum];
  }

  /**
   * Builds the text schema.
   *
   * @param array<string, mixed> $rawSchema
   *   The stored raw schema.
   *
   * @return array<string, mixed>
   *   The effective text schema.
   */
  private function textSchema(array $rawSchema): array {
    $schema = ['type' => 'string', 'format' => 'multiline'];
    foreach (['minLength', 'maxLength', 'pattern'] as $constraint) {
      if (isset($rawSchema[$constraint])) {
        $schema[$constraint] = $rawSchema[$constraint];
      }
    }
    return $schema;
  }

  /**
   * Returns TRUE if the schema describes a confirmation presentation.
   *
   * @param array<string, mixed> $schema
   *   The schema to inspect.
   */
  private function isConfirmation(array $schema): bool {
    return ($schema['presentation'] ?? '') === 'confirmation';
  }

  /**
   * Builds the confirmation button pair separately from the schema renderer.
   */
  private function buildConfirmationForm(array $form, FlowDropInterruptInterface $interrupt, array $schema): array {
    $form['actions'] = ['#type' => 'actions'];
    $form['actions']['confirm'] = [
      '#type' => 'submit',
      '#value' => $schema['confirmLabel'] ?? $this->t('Confirm'),
      '#button_type' => 'primary',
      '#name' => 'confirm',
    ];
    $form['actions']['decline'] = [
      '#type' => 'submit',
      '#value' => $schema['declineLabel'] ?? $this->t('Decline'),
      '#name' => 'decline',
    ];
    return $form;
  }

}
