<?php

declare(strict_types=1);

namespace Drupal\flowdrop_node_type\Form;

use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;

/**
 * Configuration form for managing visual types for FlowDrop nodes.
 *
 * Site builders can define the available visual types that determine
 * how nodes appear in the workflow editor.
 */
final class VisualTypesSettingsForm extends ConfigFormBase {

  /**
   * {@inheritdoc}
   */
  public function getFormId(): string {
    return "flowdrop_node_type_visual_types_settings";
  }

  /**
   * {@inheritdoc}
   */
  protected function getEditableConfigNames(): array {
    return ["flowdrop_node_type.settings"];
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state): array {
    $config = $this->config("flowdrop_node_type.settings");
    $visualTypes = $config->get("visual_types") ?? [];

    $form["description"] = [
      "#type" => "markup",
      "#markup" => "<p>" . $this->t("Define the available visual types for FlowDrop nodes. These types determine how nodes appear in the workflow editor. Site builders can select from these types when configuring node types. Empty rows are automatically removed when saving.") . "</p>",
    ];

    $form["visual_types"] = [
      "#type" => "fieldset",
      "#title" => $this->t("Visual Types"),
      "#tree" => TRUE,
      "#prefix" => '<div id="visual-types-wrapper">',
      "#suffix" => "</div>",
    ];

    // Initialize items storage on first load.
    if (!$form_state->has("visual_types_items")) {
      $typesArray = [];
      foreach ($visualTypes as $key => $value) {
        $typesArray[] = [
          "key" => $key,
          "label" => $value["label"] ?? "",
          "description" => $value["description"] ?? "",
        ];
      }
      $form_state->set("visual_types_items", $typesArray);
    }

    // Get the current items from storage.
    $typesArray = $form_state->get("visual_types_items");

    // Always append an empty row for easy additions.
    $typesArray[] = ["key" => "", "label" => "", "description" => ""];

    // Build table header.
    $form["visual_types"]["items"] = [
      "#type" => "table",
      "#header" => [
        $this->t("Machine Name"),
        $this->t("Label"),
        $this->t("Description"),
        $this->t("Operations"),
      ],
      "#empty" => $this->t("No visual types defined yet."),
    ];

    foreach ($typesArray as $i => $typeData) {
      $isEmptyRow = empty($typeData["key"]) && empty($typeData["label"]);

      $form["visual_types"]["items"][$i]["key"] = [
        "#type" => "textfield",
        "#title" => $this->t("Machine Name"),
        "#title_display" => "invisible",
        "#default_value" => $typeData["key"] ?? "",
        "#size" => 15,
        "#pattern" => "[a-z0-9_]+",
        "#placeholder" => $this->t("e.g., custom"),
      ];

      $form["visual_types"]["items"][$i]["label"] = [
        "#type" => "textfield",
        "#title" => $this->t("Label"),
        "#title_display" => "invisible",
        "#default_value" => $typeData["label"] ?? "",
        "#size" => 20,
        "#placeholder" => $this->t("e.g., Custom Type"),
      ];

      $form["visual_types"]["items"][$i]["description"] = [
        "#type" => "textfield",
        "#title" => $this->t("Description"),
        "#title_display" => "invisible",
        "#default_value" => $typeData["description"] ?? "",
        "#size" => 40,
        "#placeholder" => $this->t("Brief description"),
      ];

      // Only show remove button for non-empty rows.
      if (!$isEmptyRow) {
        $form["visual_types"]["items"][$i]["remove"] = [
          "#type" => "submit",
          "#value" => $this->t("Remove"),
          "#name" => "remove_" . $i,
          "#submit" => ["::removeTypeCallback"],
          "#ajax" => [
            "callback" => "::updateVisualTypesCallback",
            "wrapper" => "visual-types-wrapper",
          ],
          "#limit_validation_errors" => [],
        ];
      }
      else {
        // Empty placeholder for the operations column on empty rows.
        $form["visual_types"]["items"][$i]["remove"] = [
          "#markup" => "",
        ];
      }
    }

    return parent::buildForm($form, $form_state);
  }

  /**
   * Callback for removing a visual type.
   *
   * @param array<string, mixed> $form
   *   The form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   */
  public function removeTypeCallback(array &$form, FormStateInterface $form_state): void {
    $triggeringElement = $form_state->getTriggeringElement();
    $name = $triggeringElement["#name"] ?? "";

    if (preg_match('/remove_(\d+)/', $name, $matches)) {
      $indexToRemove = (int) $matches[1];

      // Get current items from user input (filtering out empty rows).
      $items = $this->getItemsFromUserInput($form_state, TRUE);

      // Remove the item at the specified index.
      unset($items[$indexToRemove]);
      $items = array_values($items);

      // Update storage with the modified items.
      // An empty row will be automatically added in buildForm().
      $form_state->set("visual_types_items", $items);
    }

    $form_state->setRebuild();
  }

  /**
   * Extract items data from user input.
   *
   * Since we use #limit_validation_errors => [], getValue() doesn't work.
   * We need to read directly from user input.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param bool $filterEmpty
   *   Whether to filter out empty rows (rows with no key and no label).
   *
   * @return array<int, array<string, string>>
   *   Array of item data.
   */
  protected function getItemsFromUserInput(FormStateInterface $form_state, bool $filterEmpty = FALSE): array {
    $userInput = $form_state->getUserInput();
    $inputItems = $userInput["visual_types"]["items"] ?? [];
    $items = [];

    foreach ($inputItems as $index => $item) {
      if (is_array($item)) {
        $key = trim($item["key"] ?? "");
        $label = trim($item["label"] ?? "");
        $description = trim($item["description"] ?? "");

        // Skip empty rows if filtering is enabled.
        if ($filterEmpty && empty($key) && empty($label)) {
          continue;
        }

        $items[] = [
          "key" => $key,
          "label" => $label,
          "description" => $description,
        ];
      }
    }

    // Fall back to stored items if no user input yet.
    if (empty($items) && !$filterEmpty) {
      $items = $form_state->get("visual_types_items") ?? [];
    }

    return $items;
  }

  /**
   * AJAX callback to update the visual types section.
   *
   * @param array<string, mixed> $form
   *   The form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @return array<string, mixed>
   *   The updated form element.
   */
  public function updateVisualTypesCallback(array &$form, FormStateInterface $form_state): array {
    return $form["visual_types"];
  }

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

    $items = $form_state->getValue(["visual_types", "items"]) ?? [];
    $keys = [];

    foreach ($items as $index => $item) {
      $key = trim($item["key"] ?? "");
      $label = trim($item["label"] ?? "");

      // Skip empty rows.
      if (empty($key) && empty($label)) {
        continue;
      }

      // Validate that both key and label are provided.
      if (!empty($key) && empty($label)) {
        $form_state->setError(
          $form["visual_types"]["items"][$index]["label"],
          $this->t("Label is required when machine name is provided.")
        );
      }

      if (empty($key) && !empty($label)) {
        $form_state->setError(
          $form["visual_types"]["items"][$index]["key"],
          $this->t("Machine name is required when label is provided.")
        );
      }

      // Validate machine name format.
      if (!empty($key) && !preg_match('/^[a-z0-9_]+$/', $key)) {
        $form_state->setError(
          $form["visual_types"]["items"][$index]["key"],
          $this->t("Machine name must contain only lowercase letters, numbers, and underscores.")
        );
      }

      // Check for duplicate keys.
      if (!empty($key)) {
        if (in_array($key, $keys, TRUE)) {
          $form_state->setError(
            $form["visual_types"]["items"][$index]["key"],
            $this->t("Duplicate machine name: @key", ["@key" => $key])
          );
        }
        $keys[] = $key;
      }
    }

    // Ensure at least one visual type is defined.
    if (empty($keys)) {
      $form_state->setErrorByName(
        "visual_types",
        $this->t("At least one visual type must be defined.")
      );
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state): void {
    $items = $form_state->getValue(["visual_types", "items"]) ?? [];
    $visualTypes = [];

    foreach ($items as $item) {
      $key = trim($item["key"] ?? "");
      $label = trim($item["label"] ?? "");
      $description = trim($item["description"] ?? "");

      if (!empty($key) && !empty($label)) {
        $visualTypes[$key] = [
          "label" => $label,
          "description" => $description,
        ];
      }
    }

    $this->config("flowdrop_node_type.settings")
      ->set("visual_types", $visualTypes)
      ->save();

    parent::submitForm($form, $form_state);
  }

}
