<?php

declare(strict_types=1);

namespace Drupal\flowdrop_trigger\Form;

use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\flowdrop_trigger\FlowDropTriggerConfigInterface;
use Drupal\flowdrop_trigger\Service\EventTypePluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Form handler for FlowDrop Trigger Configuration entities.
 *
 * Provides an admin form for editing trigger configurations.
 * Note: Triggers are typically created via the workflow editor API,
 * but this form allows admin editing of existing configurations.
 */
class TriggerConfigForm extends EntityForm {

  /**
   * The event type plugin manager.
   *
   * @var \Drupal\flowdrop_trigger\Service\EventTypePluginManager
   */
  protected EventTypePluginManager $eventTypeManager;

  /**
   * The entity type bundle info service.
   *
   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
   */
  protected EntityTypeBundleInfoInterface $bundleInfo;

  // Note: $requestStack is inherited from FormBase without type hint.
  // Note: $configFactory is inherited from FormBase without type hint.

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container): static {
    $instance = parent::create($container);
    $instance->eventTypeManager = $container->get("plugin.manager.flowdrop_event_type");
    $instance->bundleInfo = $container->get("entity_type.bundle.info");
    $instance->requestStack = $container->get("request_stack");
    $instance->configFactory = $container->get("config.factory");
    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  public function form(array $form, FormStateInterface $form_state): array {
    $form = parent::form($form, $form_state);

    /** @var \Drupal\flowdrop_trigger\FlowDropTriggerConfigInterface $entity */
    $entity = $this->entity;

    // Check for restriction warnings and display them.
    $this->addRestrictionWarnings($entity, $form);

    // Get query parameters from URL for autofilling new entities.
    $request = $this->requestStack->getCurrentRequest();
    $workflowParam = $request?->query->get("workflow") ?? "";
    $nodeParam = $request?->query->get("node") ?? "";
    $labelParam = $request?->query->get("label") ?? "";
    $categoryParam = $request?->query->get("category") ?? "";

    // Use query parameters for new entities, otherwise use entity values.
    $hasWorkflowParam = $entity->isNew() && !empty($workflowParam);
    $hasNodeParam = $entity->isNew() && !empty($nodeParam);
    $hasLabelParam = $entity->isNew() && !empty($labelParam);
    $hasUrlParams = $hasWorkflowParam || $hasNodeParam;

    $workflowId = $hasWorkflowParam
      ? $workflowParam
      : $entity->getWorkflowId();
    $nodeId = $hasNodeParam
      ? $nodeParam
      : $entity->getNodeId();

    // Fields should be readonly if entity already exists (has values).
    $isReadonly = !$entity->isNew() && (!empty($workflowId) || !empty($nodeId));

    // Basic information.
    $form["basic"] = [
      "#type" => "details",
      "#title" => $this->t("Basic Information"),
      "#open" => TRUE,
    ];

    // Generate preview ID if both workflow_id and node_id are available.
    $previewId = "";
    $hasBothParams = !empty($workflowId) && !empty($nodeId);
    if ($hasBothParams) {
      $previewId = $entity::generateId($workflowId, $nodeId);
    }

    // Use label from URL parameter if provided, otherwise auto-generate.
    $labelValue = $entity->label();
    if ($hasLabelParam) {
      // Use label from URL parameter.
      $labelValue = $labelParam;
    }
    elseif ($entity->isNew() && $hasBothParams && empty($labelValue)) {
      // Auto-generate label from workflow name and node ID.
      $labelValue = $this->generateLabelFromWorkflowAndNode($workflowId, $nodeId);
    }

    $form["basic"]["label"] = [
      "#type" => "textfield",
      "#title" => $this->t("Label"),
      "#default_value" => $labelValue,
      "#required" => TRUE,
      "#maxlength" => 255,
      "#description" => $hasBothParams && $entity->isNew()
        ? $this->t("Auto-generated from machine name. You can edit if needed.")
        : "",
    ];

    // Machine name should be readonly if both workflow_id and node_id are
    // present.
    $idReadonly = !$entity->isNew() || $hasBothParams;

    $idDescription = $this->t(
      "Format: {workflow_id}__{node_id}. This is automatically generated when creating triggers via the workflow editor."
    );
    if (!empty($previewId)) {
      $idDescription = $this->t(
        "Format: {workflow_id}__{node_id}. Automatically generated as: @id (read-only)",
        ["@id" => $previewId]
      );
    }

    $form["basic"]["id"] = [
      "#type" => "machine_name",
      "#default_value" => $entity->isNew() && !empty($previewId) ? $previewId : $entity->id(),
      "#disabled" => $idReadonly,
      "#machine_name" => [
        "exists" => [$this, "exists"],
        "source" => ["basic", "label"],
      ],
      "#description" => $idDescription,
    ];

    $form["basic"]["description"] = [
      "#type" => "textarea",
      "#title" => $this->t("Description"),
      "#default_value" => $entity->getDescription(),
      "#rows" => 2,
    ];

    $form["basic"]["status"] = [
      "#type" => "checkbox",
      "#title" => $this->t("Enabled"),
      "#default_value" => $entity->status(),
      "#description" => $this->t("Whether this trigger is active."),
    ];

    // Workflow reference.
    $form["workflow"] = [
      "#type" => "details",
      "#title" => $this->t("Workflow Reference"),
      "#open" => TRUE,
    ];

    $workflowDescription = $this->t("The machine name of the workflow this trigger belongs to.");
    if ($hasWorkflowParam) {
      $workflowDescription = $this->t("The machine name of the workflow this trigger belongs to. (Set from URL parameter, cannot be changed)");
    }
    elseif ($isReadonly && !empty($workflowId)) {
      $workflowDescription = $this->t("The machine name of the workflow this trigger belongs to. (Cannot be changed after creation)");
    }

    $form["workflow"]["workflow_id"] = [
      "#type" => "textfield",
      "#title" => $this->t("Workflow ID"),
      "#default_value" => $workflowId,
      "#required" => TRUE,
      "#disabled" => $hasWorkflowParam || ($isReadonly && !empty($workflowId)),
      "#description" => $workflowDescription,
    ];

    $nodeDescription = $this->t("The ID of the trigger node in the workflow canvas.");
    if ($hasNodeParam) {
      $nodeDescription = $this->t("The ID of the trigger node in the workflow canvas. (Set from URL parameter, cannot be changed)");
    }
    elseif ($isReadonly && !empty($nodeId)) {
      $nodeDescription = $this->t("The ID of the trigger node in the workflow canvas. (Cannot be changed after creation)");
    }

    $form["workflow"]["node_id"] = [
      "#type" => "textfield",
      "#title" => $this->t("Node ID"),
      "#default_value" => $nodeId,
      "#required" => TRUE,
      "#disabled" => $hasNodeParam || ($isReadonly && !empty($nodeId)),
      "#description" => $nodeDescription,
    ];

    // Event configuration.
    $form["event"] = [
      "#type" => "details",
      "#title" => $this->t("Event Configuration"),
      "#open" => TRUE,
    ];

    // Get category filter from query parameter if provided.
    $categoryFilter = $entity->isNew() && !empty($categoryParam) ? $categoryParam : NULL;

    $form["event"]["event_type"] = [
      "#type" => "select",
      "#title" => $this->t("Event Type"),
      "#options" => $this->getEventTypeOptions($categoryFilter),
      "#default_value" => $entity->getEventType(),
      "#required" => TRUE,
      "#description" => $this->t("The type of event that triggers workflow execution."),
    ];

    // Conditions.
    $form["conditions"] = [
      "#type" => "details",
      "#title" => $this->t("Conditions"),
      "#open" => TRUE,
      "#tree" => TRUE,
    ];

    $conditions = $entity->getConditions();

    $form["conditions"]["entity_types"] = [
      "#type" => "select",
      "#title" => $this->t("Entity Types"),
      "#options" => $this->getEntityTypeOptions(),
      "#default_value" => $conditions["entity_types"] ?? [],
      "#multiple" => TRUE,
      "#description" => $this->t("Filter by entity types. Leave empty for all."),
    ];

    $form["conditions"]["bundles"] = [
      "#type" => "textfield",
      "#title" => $this->t("Bundles"),
      "#default_value" => implode(", ", $conditions["bundles"] ?? []),
      "#description" => $this->t("Comma-separated list of bundles. Leave empty for all."),
    ];

    $form["conditions"]["roles"] = [
      "#type" => "select",
      "#title" => $this->t("Roles (for user events)"),
      "#options" => $this->getRoleOptions(),
      "#default_value" => $conditions["roles"] ?? [],
      "#multiple" => TRUE,
      "#description" => $this->t("Filter by user roles. Leave empty for all."),
    ];

    // Orchestrator settings.
    $form["orchestrator_settings"] = [
      "#type" => "details",
      "#title" => $this->t("Execution Settings"),
      "#open" => FALSE,
      "#tree" => TRUE,
    ];

    $settings = $entity->getOrchestratorSettings();

    $form["orchestrator_settings"]["type"] = [
      "#type" => "select",
      "#title" => $this->t("Orchestrator Type"),
      "#options" => [
        "default" => $this->t("Default (based on event)"),
        "synchronous" => $this->t("Synchronous (direct execution)"),
        "synchronous_pipeline" => $this->t("Synchronous Pipeline (with job tracking)"),
        "asynchronous" => $this->t("Asynchronous"),
      ],
      "#default_value" => $settings["type"] ?? "default",
    ];

    $form["orchestrator_settings"]["pipeline_mode"] = [
      "#type" => "select",
      "#title" => $this->t("Pipeline Mode"),
      "#options" => [
        "new" => $this->t("New pipeline each time"),
        "reuse" => $this->t("Reuse existing pipeline"),
        "singleton" => $this->t("Singleton (one at a time)"),
      ],
      "#default_value" => $settings["pipeline_mode"] ?? "new",
    ];

    $form["orchestrator_settings"]["priority"] = [
      "#type" => "select",
      "#title" => $this->t("Priority"),
      "#options" => [
        "low" => $this->t("Low"),
        "normal" => $this->t("Normal"),
        "high" => $this->t("High"),
        "critical" => $this->t("Critical"),
      ],
      "#default_value" => $settings["priority"] ?? "normal",
    ];

    $form["orchestrator_settings"]["timeout"] = [
      "#type" => "number",
      "#title" => $this->t("Timeout (seconds)"),
      "#default_value" => $settings["timeout"] ?? 300,
      "#min" => 1,
      "#max" => 86400,
    ];

    // Weight.
    $form["weight"] = [
      "#type" => "number",
      "#title" => $this->t("Weight"),
      "#default_value" => $entity->getWeight(),
      "#description" => $this->t("Lower weights execute first when multiple triggers fire."),
    ];

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state): void {
    parent::validateForm($form, $form_state);

    // Validate workflow exists.
    $workflowId = $form_state->getValue("workflow_id");
    if ($workflowId !== NULL) {
      $workflow = $this->entityTypeManager
        ->getStorage("flowdrop_workflow")
        ->load($workflowId);

      if ($workflow === NULL) {
        $form_state->setErrorByName(
          "workflow_id",
          $this->t("Workflow @id does not exist.", ["@id" => $workflowId])
        );
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function save(array $form, FormStateInterface $form_state): int {
    /** @var \Drupal\flowdrop_trigger\FlowDropTriggerConfigInterface $entity */
    $entity = $this->entity;

    // Set workflow and node IDs from form values.
    $workflowId = $form_state->getValue("workflow_id");
    $nodeId = $form_state->getValue("node_id");

    if ($workflowId !== NULL) {
      $entity->setWorkflowId((string) $workflowId);
    }

    if ($nodeId !== NULL) {
      $entity->setNodeId((string) $nodeId);
    }

    // Generate entity ID for new entities if workflow_id and node_id are set.
    if ($entity->isNew() && !empty($workflowId) && !empty($nodeId)) {
      $generatedId = $entity::generateId((string) $workflowId, (string) $nodeId);
      $entity->set("id", $generatedId);

      // Auto-generate label from workflow name and node type machine name if
      // label is empty.
      $label = $form_state->getValue("label");
      if (empty($label)) {
        $label = $this->generateLabelFromWorkflowAndNode((string) $workflowId, (string) $nodeId);
        if (!empty($label)) {
          $form_state->setValue("label", $label);
        }
      }
    }

    // Set label from form value.
    $label = $form_state->getValue("label");
    if ($label !== NULL) {
      $entity->set("label", (string) $label);
    }

    // Process conditions.
    $conditions = $form_state->getValue("conditions");
    if (is_array($conditions)) {
      // Parse bundles from comma-separated string.
      $bundlesString = $conditions["bundles"] ?? "";
      $bundles = array_filter(array_map("trim", explode(",", $bundlesString)));
      $conditions["bundles"] = $bundles;

      // Ensure arrays.
      $conditions["entity_types"] = array_values($conditions["entity_types"] ?? []);
      $conditions["roles"] = array_values($conditions["roles"] ?? []);

      $entity->setConditions($conditions);
    }

    // Process orchestrator settings.
    $orchestratorSettings = $form_state->getValue("orchestrator_settings");
    if (is_array($orchestratorSettings)) {
      $entity->setOrchestratorSettings($orchestratorSettings);
    }

    $status = $entity->save();

    $this->messenger()->addStatus($this->t(
      "Trigger configuration %label has been saved.",
      ["%label" => $entity->label()]
    ));

    $form_state->setRedirectUrl($entity->toUrl("collection"));

    return $status;
  }

  /**
   * Check if entity with given ID exists.
   *
   * @param string $id
   *   The entity ID.
   *
   * @return bool
   *   TRUE if exists.
   */
  public function exists(string $id): bool {
    $entity = $this->entityTypeManager
      ->getStorage("flowdrop_trigger_config")
      ->load($id);

    return $entity !== NULL;
  }

  /**
   * Get event type options.
   *
   * @param string|null $categoryFilter
   *   Optional category to filter by (e.g., "user").
   *
   * @return array<string, string>|array<string, array<string, string>>
   *   Options array, grouped by category if no filter, or flat array if
   *   filtered.
   */
  protected function getEventTypeOptions(?string $categoryFilter = NULL): array {
    $options = [];
    $definitions = $this->eventTypeManager->getDefinitions();

    foreach ($definitions as $id => $definition) {
      $category = $definition["category"] ?? "other";

      // Filter by category if specified.
      if ($categoryFilter !== NULL && $category !== $categoryFilter) {
        continue;
      }

      $label = (string) ($definition["label"] ?? $id);

      // If filtering by category, return flat array. Otherwise group by
      // category.
      if ($categoryFilter !== NULL) {
        $options[$id] = $label;
      }
      else {
        $options[$category][$id] = $label;
      }
    }

    return $options;
  }

  /**
   * Get entity type options.
   *
   * @return array<string, string>
   *   Options array.
   */
  protected function getEntityTypeOptions(): array {
    $options = [];
    $entityTypes = $this->entityTypeManager->getDefinitions();

    foreach ($entityTypes as $id => $definition) {
      if ($definition->getGroup() === "content") {
        $options[$id] = (string) $definition->getLabel();
      }
    }

    asort($options);
    return $options;
  }

  /**
   * Get role options.
   *
   * @return array<string, string>
   *   Options array.
   */
  protected function getRoleOptions(): array {
    $options = [];

    /** @var \Drupal\user\RoleInterface[] $roles */
    $roles = $this->entityTypeManager->getStorage("user_role")->loadMultiple();

    foreach ($roles as $role) {
      $roleId = $role->id();
      if ($roleId !== NULL) {
        $options[$roleId] = (string) $role->label();
      }
    }

    return $options;
  }

  /**
   * Generate label from workflow name and node ID.
   *
   * Format: "Workflow Name (nodeId)"
   *
   * @param string $workflowId
   *   The workflow entity ID.
   * @param string $nodeId
   *   The node ID in the workflow (e.g., "trigger.2").
   *
   * @return string
   *   The generated label, or empty string if workflow not found.
   */
  protected function generateLabelFromWorkflowAndNode(string $workflowId, string $nodeId): string {
    // Load workflow entity to get its label.
    $workflowStorage = $this->entityTypeManager->getStorage("flowdrop_workflow");
    $workflow = $workflowStorage->load($workflowId);
    if ($workflow === NULL) {
      return "";
    }

    $workflowName = $workflow->label();

    // Format: "Workflow Name (nodeId)".
    return sprintf("%s (%s)", $workflowName, $nodeId);
  }

  /**
   * Add warning messages for restricted event types, entity types, or bundles.
   *
   * Checks the trigger's configuration against site-wide restrictions and
   * displays warning messages if the trigger will not fire due to restrictions.
   *
   * @param \Drupal\flowdrop_trigger\FlowDropTriggerConfigInterface $entity
   *   The trigger configuration entity.
   * @param array<string, mixed> &$form
   *   The form array to add warning elements to.
   */
  protected function addRestrictionWarnings(FlowDropTriggerConfigInterface $entity, array &$form): void {
    // Only show warnings for existing entities with configuration.
    if ($entity->isNew()) {
      return;
    }

    $config = $this->configFactory->get("flowdrop_trigger.settings");
    $allowedEventTypes = $config->get("allowed_event_types") ?? [];
    $allowedEntityTypes = $config->get("allowed_entity_types") ?? [];
    $allowedBundles = $config->get("allowed_bundles") ?? [];

    $warnings = [];
    $settingsUrl = Url::fromRoute("flowdrop.trigger.settings")->toString();

    // Check event type restriction.
    $eventType = $entity->getEventType();
    if (!empty($allowedEventTypes) && !empty($eventType)) {
      if (!in_array($eventType, $allowedEventTypes, TRUE)) {
        $eventTypeLabel = $this->getEventTypeLabel($eventType);
        $warnings[] = $this->t(
          'The event type "<strong>@event_type</strong>" is not enabled in the <a href="@settings_url">site-wide trigger settings</a>. This trigger will not fire until the event type is enabled.',
          [
            "@event_type" => $eventTypeLabel,
            "@settings_url" => $settingsUrl,
          ]
        );
      }
    }

    // Check entity type restrictions (only for entity events).
    $conditions = $entity->getConditions();
    $triggerEntityTypes = $conditions["entity_types"] ?? [];

    if (!empty($allowedEntityTypes) && !empty($triggerEntityTypes)) {
      $disabledEntityTypes = array_diff($triggerEntityTypes, $allowedEntityTypes);
      if (!empty($disabledEntityTypes)) {
        $entityTypeLabels = array_map(
          fn($type) => $this->getEntityTypeLabel($type),
          $disabledEntityTypes
        );
        $warnings[] = $this->t(
          'The following entity types are not enabled in the <a href="@settings_url">site-wide trigger settings</a>: <strong>@entity_types</strong>. Events for these entity types will not trigger this workflow.',
          [
            "@entity_types" => implode(", ", $entityTypeLabels),
            "@settings_url" => $settingsUrl,
          ]
        );
      }
    }

    // Check bundle restrictions.
    $triggerBundles = $conditions["bundles"] ?? [];

    if (!empty($allowedBundles) && !empty($triggerBundles) && !empty($triggerEntityTypes)) {
      // Build the list of bundle keys this trigger uses.
      $triggerBundleKeys = [];
      foreach ($triggerEntityTypes as $entityTypeId) {
        foreach ($triggerBundles as $bundle) {
          $triggerBundleKeys[] = sprintf("%s:%s", $entityTypeId, $bundle);
        }
      }

      $disabledBundles = array_diff($triggerBundleKeys, $allowedBundles);
      if (!empty($disabledBundles)) {
        $warnings[] = $this->t(
          'The following bundles are not enabled in the <a href="@settings_url">site-wide trigger settings</a>: <strong>@bundles</strong>. Events for these bundles will not trigger this workflow.',
          [
            "@bundles" => implode(", ", $disabledBundles),
            "@settings_url" => $settingsUrl,
          ]
        );
      }
    }

    // Display warnings if any.
    if (!empty($warnings)) {
      $form["restriction_warnings"] = [
        "#type" => "container",
        "#weight" => -100,
        "#attributes" => [
          "class" => ["messages", "messages--warning"],
        ],
      ];

      $form["restriction_warnings"]["title"] = [
        "#type" => "html_tag",
        "#tag" => "h3",
        "#value" => $this->t("⚠️ Trigger Restrictions Active"),
        "#attributes" => [
          "style" => "margin-top: 0;",
        ],
      ];

      $form["restriction_warnings"]["description"] = [
        "#type" => "html_tag",
        "#tag" => "p",
        "#value" => $this->t("This trigger may not fire due to site-wide restrictions configured by the administrator:"),
      ];

      $items = [];
      foreach ($warnings as $warning) {
        $items[] = ["#markup" => $warning];
      }

      $form["restriction_warnings"]["list"] = [
        "#theme" => "item_list",
        "#items" => $items,
      ];
    }
  }

  /**
   * Get human-readable label for an event type.
   *
   * @param string $eventTypeId
   *   The event type plugin ID.
   *
   * @return string
   *   The event type label.
   */
  protected function getEventTypeLabel(string $eventTypeId): string {
    $definitions = $this->eventTypeManager->getDefinitions();

    if (isset($definitions[$eventTypeId])) {
      $label = $definitions[$eventTypeId]["label"] ?? $eventTypeId;
      return $label instanceof \Stringable ? (string) $label : $eventTypeId;
    }

    return $eventTypeId;
  }

  /**
   * Get human-readable label for an entity type.
   *
   * @param string $entityTypeId
   *   The entity type ID.
   *
   * @return string
   *   The entity type label.
   */
  protected function getEntityTypeLabel(string $entityTypeId): string {
    $entityType = $this->entityTypeManager->getDefinition($entityTypeId, FALSE);

    if ($entityType !== NULL) {
      $label = $entityType->getLabel();
      return $label instanceof \Stringable ? (string) $label : $entityTypeId;
    }

    return $entityTypeId;
  }

}
