<?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\flowdrop_orchestration\Plugin\Orchestrator\OrchestratorPluginInterface;
use Drupal\flowdrop_orchestration\Service\Orchestrator\OrchestratorPluginManager;
use Drupal\flowdrop_trigger\Form\Trait\TriggerConditionsFormTrait;
use Drupal\flowdrop_trigger\Form\Trait\TriggerFormOptionsTrait;
use Drupal\flowdrop_trigger\Form\Trait\TriggerRestrictionWarningsTrait;
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.
 *
 * @internal This class is not part of the public API and may change without notice.
 */
class TriggerConfigForm extends EntityForm {

  use TriggerFormOptionsTrait;
  use TriggerConditionsFormTrait;
  use TriggerRestrictionWarningsTrait;

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

  /**
   * The orchestrator plugin manager.
   *
   * @var \Drupal\flowdrop_orchestration\Service\Orchestrator\OrchestratorPluginManager
   */
  protected OrchestratorPluginManager $orchestratorManager;

  // 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->orchestratorManager = $container->get("flowdrop_orchestration.plugin_manager");
    $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;

    // Determine the selected event type.
    // Check form state first (for AJAX updates), then fall back to entity
    // value.
    $selectedEventType = $form_state->getValue("event_type");
    if ($selectedEventType === NULL) {
      $selectedEventType = $entity->getEventType();
    }

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

    // Container for dynamic conditions based on event type.
    $form["conditions"] = [
      "#type" => "details",
      "#title" => $this->t("Conditions"),
      "#open" => TRUE,
      "#tree" => TRUE,
      "#attributes" => [
        "id" => "conditions-wrapper",
      ],
    ];

    $conditions = $entity->getConditions();

    // Build condition form elements based on event type category.
    $this->buildConditionsForm($form["conditions"], $selectedEventType, $conditions);

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

    $settings = $entity->getOrchestratorSettings();

    // Determine the selected orchestrator type.
    // Check form state first (for AJAX updates), then fall back to entity
    // value.
    $selectedType = $form_state->getValue(["orchestrator_settings", "type"]);
    if ($selectedType === NULL) {
      $selectedType = $settings["type"] ?? "default";
    }

    $form["orchestrator_settings"]["type"] = [
      "#type" => "select",
      "#title" => $this->t("Orchestrator Type"),
      "#description" => $this->t("Select how the workflow should be executed. Different orchestrators provide different capabilities."),
      "#options" => $this->getOrchestratorOptions(),
      "#default_value" => $selectedType,
      "#ajax" => [
        "callback" => [$this, "ajaxUpdateOrchestratorSettings"],
        "wrapper" => "orchestrator-settings-wrapper",
        "event" => "change",
      ],
    ];

    // Container for dynamic orchestrator-specific settings.
    $form["orchestrator_settings"]["dynamic_settings"] = [
      "#type" => "container",
      "#attributes" => [
        "id" => "orchestrator-settings-wrapper",
      ],
    ];

    // Build orchestrator-specific form elements.
    $this->buildOrchestratorConfigurationForm(
      $form["orchestrator_settings"]["dynamic_settings"],
      $form_state,
      $selectedType,
      $settings
    );

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

  /**
   * AJAX callback to update orchestrator settings when type changes.
   *
   * @param array<string, mixed> $form
   *   The form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current form state.
   *
   * @return array<string, mixed>
   *   The updated form element to replace the wrapper.
   */
  public function ajaxUpdateOrchestratorSettings(array &$form, FormStateInterface $form_state): array {
    // Return the updated dynamic_settings container.
    // Drupal will use the 'wrapper' setting from #ajax to handle replacement.
    return $form["orchestrator_settings"]["dynamic_settings"];
  }

  /**
   * AJAX callback to update conditions form when event type changes.
   *
   * @param array<string, mixed> $form
   *   The form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current form state.
   *
   * @return array<string, mixed>
   *   The updated form element to replace the wrapper.
   */
  public function ajaxUpdateConditionsForm(array &$form, FormStateInterface $form_state): array {
    // Return the updated conditions container.
    // Drupal will use the 'wrapper' setting from #ajax to handle replacement.
    return $form["conditions"];
  }

  /**
   * Builds orchestrator-specific configuration form elements.
   *
   * Creates form elements based on the selected orchestrator plugin.
   * Each orchestrator plugin provides its own configuration form.
   *
   * @param array<string, mixed> &$form
   *   The form container to add elements to.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current form state.
   * @param string $orchestratorType
   *   The selected orchestrator type.
   * @param array<string, mixed> $settings
   *   Current orchestrator settings.
   */
  protected function buildOrchestratorConfigurationForm(
    array &$form,
    FormStateInterface $form_state,
    string $orchestratorType,
    array $settings,
  ): void {
    // Handle "default" type - show informational message.
    if ($orchestratorType === "default") {
      $form["default_info"] = [
        "#type" => "markup",
        "#markup" => "<p><em>" . $this->t("The orchestrator will be automatically selected based on the event type. No additional configuration is needed.") . "</em></p>",
      ];

      // Still show basic common settings for default mode.
      $form["priority"] = [
        "#type" => "select",
        "#title" => $this->t("Priority"),
        "#description" => $this->t("Execution priority for this trigger."),
        "#options" => [
          "low" => $this->t("Low"),
          "normal" => $this->t("Normal"),
          "high" => $this->t("High"),
          "critical" => $this->t("Critical"),
        ],
        "#default_value" => $settings["priority"] ?? "normal",
      ];

      $form["timeout"] = [
        "#type" => "number",
        "#title" => $this->t("Timeout (seconds)"),
        "#description" => $this->t("Maximum execution time in seconds."),
        "#default_value" => $settings["timeout"] ?? 300,
        "#min" => 0,
        "#max" => 86400,
      ];

      return;
    }

    // Try to load the orchestrator plugin and get its configuration form.
    if (!$this->orchestratorManager->hasDefinition($orchestratorType)) {
      $form["error"] = [
        "#type" => "markup",
        "#markup" => "<p class=\"messages messages--warning\">" .
        $this->t("Unknown orchestrator type: @type. Using default settings.", ["@type" => $orchestratorType]) .
        "</p>",
      ];
      return;
    }

    try {
      /** @var \Drupal\flowdrop_orchestration\Plugin\Orchestrator\OrchestratorPluginInterface $plugin */
      $plugin = $this->orchestratorManager->createInstance($orchestratorType);

      if ($plugin instanceof OrchestratorPluginInterface) {
        // Get the plugin's configuration form elements.
        $pluginForm = $plugin->buildConfigurationForm([], $form_state, $settings);

        // Merge the plugin's form elements into our container.
        foreach ($pluginForm as $key => $element) {
          $form[$key] = $element;
        }
      }
    }
    catch (\Exception $e) {
      $form["error"] = [
        "#type" => "markup",
        "#markup" => "<p class=\"messages messages--error\">" .
        $this->t("Failed to load orchestrator configuration: @message", ["@message" => $e->getMessage()]) .
        "</p>",
      ];
    }
  }

  /**
   * {@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])
        );
      }
    }

    // Validate orchestrator-specific configuration.
    $orchestratorType = $form_state->getValue(["orchestrator_settings", "type"]) ?? "default";

    if ($orchestratorType !== "default" && $this->orchestratorManager->hasDefinition($orchestratorType)) {
      try {
        /** @var \Drupal\flowdrop_orchestration\Plugin\Orchestrator\OrchestratorPluginInterface $plugin */
        $plugin = $this->orchestratorManager->createInstance($orchestratorType);

        if ($plugin instanceof OrchestratorPluginInterface) {
          $plugin->validateConfigurationForm($form, $form_state);
        }
      }
      catch (\Exception $e) {
        // Log error but don't block form submission.
        $this->logger("flowdrop_trigger")->warning(
          "Failed to validate orchestrator configuration: @message",
          ["@message" => $e->getMessage()]
        );
      }
    }
  }

  /**
   * {@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 based on event type category.
    $conditions = $form_state->getValue("conditions");
    if (is_array($conditions)) {
      // Parse bundles from comma-separated string (for entity events).
      if (isset($conditions["bundles"])) {
        $bundlesString = $conditions["bundles"] ?? "";
        $bundles = array_filter(array_map("trim", explode(",", $bundlesString)));
        $conditions["bundles"] = $bundles;
      }

      // Parse form_ids from newline-separated string (for form events).
      if (isset($conditions["form_ids"])) {
        $formIdsString = $conditions["form_ids"] ?? "";
        $formIds = array_filter(array_map("trim", explode("\n", $formIdsString)));
        $conditions["form_ids"] = $formIds;
      }

      // Ensure arrays for entity_types and roles if present.
      if (isset($conditions["entity_types"])) {
        $conditions["entity_types"] = array_values($conditions["entity_types"]);
      }
      if (isset($conditions["roles"])) {
        $conditions["roles"] = array_values($conditions["roles"]);
      }

      // Process cron_schedule from nested form structure.
      if (isset($conditions["cron_schedule"])) {
        $cronSchedule = $conditions["cron_schedule"];
        $processedCronSchedule = [
          "mode" => $cronSchedule["mode"] ?? "simple",
          "schedule_type" => $cronSchedule["schedule_type"] ?? "recurring",
        ];

        // Process simple settings.
        $simpleSettings = $cronSchedule["simple_settings"] ?? [];
        $processedCronSchedule["interval"] = $simpleSettings["interval"] ?? "every_hour";
        $processedCronSchedule["custom_interval"] = (int) ($simpleSettings["custom_interval"] ?? 3600);
        $processedCronSchedule["time_window_start"] = $simpleSettings["time_window_start"] ?? "";
        $processedCronSchedule["time_window_end"] = $simpleSettings["time_window_end"] ?? "";

        // Process days_of_week checkboxes (filter unchecked values).
        $daysOfWeek = $simpleSettings["days_of_week"] ?? [];
        $processedCronSchedule["days_of_week"] = array_values(array_filter(
          array_map("intval", array_keys(array_filter($daysOfWeek)))
        ));

        // Process advanced settings.
        $advancedSettings = $cronSchedule["advanced_settings"] ?? [];
        $processedCronSchedule["cron_expression"] = $advancedSettings["cron_expression"] ?? "0 * * * *";

        // Process common settings.
        $commonSettings = $cronSchedule["common_settings"] ?? [];
        $processedCronSchedule["max_executions"] = (int) ($commonSettings["max_executions"] ?? 0);
        $processedCronSchedule["timezone"] = $commonSettings["timezone"] ?? "";

        $conditions["cron_schedule"] = $processedCronSchedule;
      }

      $entity->setConditions($conditions);
    }

    // Process orchestrator settings using the plugin's submit handler.
    $orchestratorSettings = $this->processOrchestratorSettings($form, $form_state);
    $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;
  }

  /**
   * Process orchestrator settings from form submission.
   *
   * Uses the orchestrator plugin's submitConfigurationForm() method to
   * extract and process configuration values.
   *
   * @param array<string, mixed> $form
   *   The form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current form state.
   *
   * @return array<string, mixed>
   *   The processed orchestrator settings.
   */
  protected function processOrchestratorSettings(array $form, FormStateInterface $form_state): array {
    $rawSettings = $form_state->getValue("orchestrator_settings") ?? [];
    $orchestratorType = $rawSettings["type"] ?? "default";

    // For "default" type, just store basic settings.
    if ($orchestratorType === "default") {
      $dynamicSettings = $rawSettings["dynamic_settings"] ?? [];
      return [
        "type" => "default",
        "priority" => $dynamicSettings["priority"] ?? "normal",
        "timeout" => (int) ($dynamicSettings["timeout"] ?? 300),
      ];
    }

    // Try to use the plugin's submit handler.
    if ($this->orchestratorManager->hasDefinition($orchestratorType)) {
      try {
        /** @var \Drupal\flowdrop_orchestration\Plugin\Orchestrator\OrchestratorPluginInterface $plugin */
        $plugin = $this->orchestratorManager->createInstance($orchestratorType);

        if ($plugin instanceof OrchestratorPluginInterface) {
          // The plugin's submitConfigurationForm expects values nested under
          // orchestrator_settings, but we need to handle the dynamic_settings
          // container. Flatten the structure for the plugin.
          $dynamicSettings = $rawSettings["dynamic_settings"] ?? [];

          // Create a temporary form state with the flattened values.
          $flattenedValues = ["orchestrator_settings" => $dynamicSettings];
          $tempFormState = clone $form_state;
          $tempFormState->setValues($flattenedValues);

          return $plugin->submitConfigurationForm($form, $tempFormState);
        }
      }
      catch (\Exception $e) {
        // Log error and fall back to raw settings.
        $this->logger("flowdrop_trigger")->warning(
          "Failed to process orchestrator settings: @message",
          ["@message" => $e->getMessage()]
        );
      }
    }

    // Fallback: return raw settings with type preserved.
    $dynamicSettings = $rawSettings["dynamic_settings"] ?? [];
    $dynamicSettings["type"] = $orchestratorType;
    return $dynamicSettings;
  }

}
