<?php

declare(strict_types=1);

namespace Drupal\flowdrop_trigger\Plugin\FlowDropNodeProcessor;

use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\flowdrop\Attribute\FlowDropNodeProcessor;
use Drupal\flowdrop\DTO\ConfigEdit\ConfigEdit;
use Drupal\flowdrop\DTO\ConfigEdit\ConfigEditApiEndpoints;
use Drupal\flowdrop\DTO\ConfigEdit\DynamicSchemaEndpoint;
use Drupal\flowdrop\DTO\ConfigEdit\ExternalEditLink;
use Drupal\flowdrop\DTO\ParameterBagInterface;
use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\ConfigEditProviderInterface;

/**
 * Unified trigger node processor for all event types.
 *
 * This processor handles all trigger types in the visual workflow editor:
 * - Entity events (insert, update, delete, presave)
 * - User events (login, logout, registration)
 * - Form events (submit, validate)
 * - Cron events (scheduled/periodic execution)
 *
 * When a trigger node is dropped in the editor, a FlowDropTriggerConfig
 * entity is created via the API. The node stores only a reference to the
 * entity via trigger_config_id (derived from workflow_id and node_id).
 *
 * The event type is configured in the FlowDropTriggerConfig entity, and the
 * processor adapts its output structure based on the event category.
 *
 * Global settings (per category) control which events are enabled:
 * - flowdrop_trigger.settings.entity.yml
 * - flowdrop_trigger.settings.user.yml
 * - flowdrop_trigger.settings.form.yml
 * - flowdrop_trigger.settings.cron.yml
 *
 * Entity ID format: {workflow_id}__{node_id}
 *
 * Implements ConfigEditProviderInterface to provide dynamic configuration
 * editing capabilities via REST API and admin forms.
 *
 * @see \Drupal\flowdrop_trigger\Entity\FlowDropTriggerConfig
 * @see \Drupal\flowdrop_trigger\Controller\TriggerConfigApiController
 * @see \Drupal\flowdrop\Plugin\FlowDropNodeProcessor\ConfigEditProviderInterface
 *
 * @internal This class is not part of the public API and may change without notice.
 */
#[FlowDropNodeProcessor(
  id: "trigger",
  label: new TranslatableMarkup("Trigger"),
  description: "Start workflow on events (entity changes, user actions, form submissions, cron, etc.)",
  version: "2.3.0"
)]
class Trigger extends AbstractTrigger implements ConfigEditProviderInterface {

  /**
   * {@inheritdoc}
   *
   * Processes trigger data and structures output based on event type.
   *
   * The output structure adapts based on the event category:
   * - User events: Adds 'user' property with extracted user data
   * - Form events: Adds 'form', 'form_values', 'form_id', 'form_state' props
   * - Entity events: Returns standard data structure
   *
   * @param \Drupal\flowdrop\DTO\ParameterBagInterface $params
   *   The input parameters (typically empty for triggers).
   *
   * @return array<string, mixed>
   *   The processed trigger data with event-specific structure.
   */
  public function process(ParameterBagInterface $params): array {
    $initialData = $this->executionContext->getInitialData();
    $eventType = $initialData["event_type"] ?? "";

    // Handle user events - extract user-specific data.
    if (str_starts_with($eventType, "user.")) {
      return $this->processUserEvent($initialData);
    }

    // Handle form events - extract form-specific data.
    if (str_starts_with($eventType, "form.")) {
      return $this->processFormEvent($initialData);
    }

    // Handle cron events - extract cron-specific data.
    if (str_starts_with($eventType, "cron.")) {
      return $this->processCronEvent($initialData);
    }

    // Default handling for entity events and other event types.
    return [
      "data" => $initialData,
    ];
  }

  /**
   * Processes user event data and structures output.
   *
   * Extracts user-specific data from the trigger context and provides
   * it as a separate 'user' property for convenient access.
   *
   * @param array<string, mixed> $initialData
   *   The initial data from the execution context.
   *
   * @return array<string, mixed>
   *   The structured output with user data.
   */
  protected function processUserEvent(array $initialData): array {
    // Extract user-specific data from the trigger context.
    $userData = $initialData["entity"] ?? NULL;

    // If entity data is available, ensure it's properly structured.
    if ($userData !== NULL && is_array($userData)) {
      return [
        "data" => $userData,
        "user" => $userData,
      ];
    }

    return [
      "data" => $initialData,
      "user" => $initialData["entity"] ?? NULL,
    ];
  }

  /**
   * Processes form event data and structures output.
   *
   * Extracts form-specific data from the trigger context and provides
   * convenient access to form, form_values, form_id, and form_state.
   *
   * @param array<string, mixed> $initialData
   *   The initial data from the execution context.
   *
   * @return array<string, mixed>
   *   The structured output with form data.
   */
  protected function processFormEvent(array $initialData): array {
    // Extract form-specific data from the trigger context.
    $formData = $initialData["form"] ?? NULL;
    $formValues = $initialData["form_values"] ?? NULL;

    return [
      "data" => $initialData,
      "form" => $formData,
      "form_values" => $formValues,
      "form_id" => $initialData["form_id"] ?? NULL,
      "form_state" => $initialData["form_state"] ?? NULL,
    ];
  }

  /**
   * Processes cron event data and structures output.
   *
   * Extracts cron-specific data from the trigger context and provides
   * convenient access to cron execution information.
   *
   * @param array<string, mixed> $initialData
   *   The initial data from the execution context.
   *
   * @return array<string, mixed>
   *   The structured output with cron data.
   */
  protected function processCronEvent(array $initialData): array {
    return [
      "data" => $initialData,
      "cron" => [
        "timestamp" => $initialData["timestamp"] ?? time(),
        "last_run" => $initialData["last_run"] ?? NULL,
        "cron_key" => $initialData["cron_key"] ?? NULL,
        "operation" => $initialData["operation"] ?? "cron_run",
      ],
    ];
  }

  /**
   * {@inheritdoc}
   *
   * Minimal schema - trigger config is managed via the configure endpoint
   * which accepts workflow and node IDs. No need to store trigger_config_id
   * in the node as it's derived from workflow_id and node_id.
   */
  public function getParameterSchema(): array {
    $baseSchema = parent::getParameterSchema();

    // Terminal node variant - triggers are always "start" nodes.
    $baseSchema["properties"]["variant"] = [
      "type" => "string",
      "title" => "Variant",
      "description" => "Terminal node variant (start for triggers)",
      "enum" => ["start"],
      "default" => "start",
      "format" => "hidden",
    ];
    return $baseSchema;
  }

  /**
   * {@inheritdoc}
   */
  public function getConfigEdit(): ConfigEdit {
    return new ConfigEdit(
      // Dynamic schema from REST endpoint.
      // Schema endpoint returns JSON Schema for trigger configuration.
      dynamicSchema: new DynamicSchemaEndpoint(
        // event_type shapes which condition fields appear; workflow + node
        // identify the existing config so the endpoint can seed current values
        // as defaults (populate on read). When the author picks a different
        // event type the resolved URL changes, driving an automatic refetch.
        url: "/triggers/schema?event_type={event_type}&workflow={workflowId}&node={nodeId}",
        method: "GET",
        // Map URL variables to node data.
        parameterMapping: [
          "event_type" => "config.event_type",
          "workflowId" => "workflowId",
          "nodeId" => "id",
        ],
        // Don't cache - schema varies by event_type and current config.
        cacheSchema: FALSE,
        timeout: 10000,
      ),
      // External edit link - opens admin form in new tab.
      // No category filter - shows all enabled event types.
      externalEditLink: new ExternalEditLink(
        url: "/admin/flowdrop/triggers/configure?workflow={workflowId}&node={nodeId}",
        label: "Configure Trigger",
        icon: "mdi:flash-outline",
        description: "Open trigger configuration form in admin interface",
        // Map URL variables to node data.
        parameterMapping: [
          "workflowId" => "workflowId",
          "nodeId" => "id",
        ],
        openInNewTab: TRUE,
      ),
      // API endpoints for CRUD operations.
      api: new ConfigEditApiEndpoints(
        create: "/api/flowdrop/triggers",
        read: "/api/flowdrop/triggers/{trigger_config_id}",
        update: "/api/flowdrop/triggers/{trigger_config_id}",
        delete: "/api/flowdrop/triggers/{trigger_config_id}",
        custom: [
          "eventTypes" => "/api/flowdrop/triggers/event-types",
        ],
      ),
      preferDynamicSchema: TRUE,
      showRefreshButton: TRUE,
      loadingMessage: "Loading trigger configuration...",
      errorMessage: "Failed to load trigger configuration",
    );
  }

  /**
   * {@inheritdoc}
   *
   * Simplified output schema with common properties only.
   *
   * The actual output structure varies based on the event type configured
   * in the trigger. Event-specific properties are available via the `data`
   * object and category-specific convenience outputs (user, form, cron).
   */
  public function getOutputSchema(): array {
    return [
      "type" => "object",
      "title" => "Trigger Data",
      "description" => "Event data from the trigger. Structure varies by event type.",
      "properties" => [
        // Primary data output - always present, always exposed. Carries the
        // full event payload regardless of event type, so it is the one port
        // that makes sense for every trigger.
        "data" => [
          "type" => "object",
          "title" => "Event Data",
          "description" => "Complete trigger data. Contains event_type, timestamp, and event-specific properties.",
          "additionalProperties" => TRUE,
          "properties" => [
            "event_type" => [
              "type" => "string",
              "description" => "The event type (e.g., entity.insert, user.login, form.submit, cron.run)",
            ],
            "timestamp" => [
              "type" => "integer",
              "description" => "Unix timestamp when the event occurred",
            ],
            "trigger_config_id" => [
              "type" => "string",
              "description" => "The trigger configuration ID",
            ],
            "trigger_node_id" => [
              "type" => "string",
              "description" => "The trigger node ID in the workflow",
            ],
          ],
        ],
        // Event-specific convenience outputs. Each only carries a value for its
        // own event family (a cron trigger's `form_id` is always null), so they
        // ship NOT exposed (`x-exposed-by-default: false`): hidden on the
        // canvas and unwireable until the author reveals the ones relevant to
        // their event type via the Ports fieldset. Keeps a cron trigger from
        // dangling five always-null form/user ports.
        "user" => [
          "type" => ["object", "null"],
          "title" => "User Data",
          "description" => "User entity data (user.* events only)",
          "additionalProperties" => TRUE,
          "x-exposed-by-default" => FALSE,
        ],
        "form" => [
          "type" => ["object", "null"],
          "title" => "Form",
          "description" => "Form structure (form.* events only)",
          "additionalProperties" => TRUE,
          "x-exposed-by-default" => FALSE,
        ],
        "form_values" => [
          "type" => ["object", "null"],
          "title" => "Form Values",
          "description" => "Submitted form values (form.* events only)",
          "additionalProperties" => TRUE,
          "x-exposed-by-default" => FALSE,
        ],
        "form_id" => [
          "type" => ["string", "null"],
          "title" => "Form ID",
          "description" => "The form ID (form.* events only)",
          "x-exposed-by-default" => FALSE,
        ],
        "form_state" => [
          "type" => ["object", "null"],
          "title" => "Form State",
          "description" => "Form state (form.* events only)",
          "additionalProperties" => TRUE,
          "x-exposed-by-default" => FALSE,
        ],
        "cron" => [
          "type" => ["object", "null"],
          "title" => "Cron Data",
          "description" => "Cron execution info (cron.* events only)",
          "additionalProperties" => TRUE,
          "x-exposed-by-default" => FALSE,
        ],
      ],
    ];
  }

}
