<?php

declare(strict_types=1);

namespace Drupal\flowdrop_ui_components\Form;

use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\flowdrop_ui_components\Constants\LibraryDefaultSettings;

/**
 * Settings form for the FlowDrop UI Components module.
 *
 * Two independent concerns:
 *  - `default_settings`: open-ended JSON document deep-merged into the FlowDrop
 *    settings store. Edited as a textarea because the underlying schema can
 *    evolve in the library without a Drupal-side migration.
 *  - `exposed_categories`: which categories are shown in the Navbar settings
 *    modal. Bounded set, so rendered as checkboxes.
 *  - `draft_auto_save` / `draft_storage`: whether and where the editor
 *    persists workflow drafts in the browser. Drafts contain the full
 *    workflow JSON including node config values, so this is a security
 *    policy knob, not just a UX preference.
 *
 * @internal This class is not part of the public API and may change without notice.
 */
class UiSettingsForm extends ConfigFormBase {

  /**
   * Settings categories recognised by the bundled FlowDrop library.
   *
   * Keep in sync with `SETTINGS_CATEGORIES` in
   * `lib/flowdrop/src/lib/types/settings.ts`.
   */
  private const CANONICAL_CATEGORIES = ['theme', 'editor', 'ui', 'behavior', 'api'];

  /**
   * Human-readable labels mirroring SETTINGS_CATEGORY_LABELS in the library.
   *
   * @var array<string, string>
   */
  private const CATEGORY_LABELS = [
    'theme' => 'Theme',
    'editor' => 'Editor',
    'ui' => 'UI',
    'behavior' => 'Behavior',
    'api' => 'API',
  ];

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

  /**
   * {@inheritdoc}
   */
  public function getFormId(): string {
    return 'flowdrop_ui_components_ui_settings_form';
  }

  /**
   * {@inheritdoc}
   *
   * @return array<string, mixed>
   *   The form array.
   */
  public function buildForm(array $form, FormStateInterface $form_state): array {
    $config = $this->config('flowdrop_ui_components.settings');
    $default_settings = (string) ($config->get('default_settings') ?? '');
    $exposed = $config->get('exposed_categories');
    $exposed = is_array($exposed) ? array_values($exposed) : [];

    // Defaults group — JSON textarea + "Load library defaults" helper.
    $form['defaults'] = [
      '#type' => 'details',
      '#title' => $this->t('Default settings'),
      '#description' => $this->t('Values deep-merged into the FlowDrop settings store before mount. JSON object keyed by category (@categories).', [
        '@categories' => implode(', ', self::CANONICAL_CATEGORIES),
      ]),
      '#open' => TRUE,
    ];

    $form['defaults']['default_settings'] = [
      '#type' => 'textarea',
      '#title' => $this->t('Default settings (JSON)'),
      '#rows' => 18,
      '#default_value' => $default_settings,
      '#attributes' => [
        'spellcheck' => 'false',
        'style' => 'font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.85rem;',
      ],
    ];

    $form['defaults']['load_defaults'] = [
      '#type' => 'submit',
      '#value' => $this->t('Load library defaults'),
      '#submit' => ['::loadLibraryDefaults'],
      '#limit_validation_errors' => [],
      '#button_type' => 'secondary',
    ];

    // Exposed group — checkboxes for each canonical category.
    $form['exposed'] = [
      '#type' => 'details',
      '#title' => $this->t('Exposed in settings modal'),
      '#description' => $this->t('Choose which categories the user can see in the Navbar settings modal. Unchecking everything hides the gear icon entirely.'),
      '#open' => TRUE,
    ];

    $form['exposed']['exposed_categories'] = [
      '#type' => 'checkboxes',
      '#title' => $this->t('Categories'),
      '#title_display' => 'invisible',
      '#options' => self::CATEGORY_LABELS,
      '#default_value' => $exposed,
    ];

    // Drafts group — browser-side draft persistence policy.
    $form['drafts'] = [
      '#type' => 'details',
      '#title' => $this->t('Workflow drafts'),
      '#description' => $this->t('The editor periodically saves a draft of the workflow being edited to browser storage so unsaved changes survive a crash or accidental reload. Drafts contain the complete workflow JSON, including node configuration values.'),
      '#open' => TRUE,
    ];

    $form['drafts']['draft_auto_save'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Auto-save drafts to browser storage'),
      '#default_value' => $config->get('draft_auto_save') ?? TRUE,
      '#description' => $this->t('Uncheck to disable draft persistence entirely. Users can also opt out individually via the "Store Drafts in Browser" toggle in the editor settings.'),
    ];

    $form['drafts']['draft_storage'] = [
      '#type' => 'radios',
      '#title' => $this->t('Draft storage backend'),
      '#options' => [
        'session' => $this->t('Session storage (recommended) — drafts are scoped to the browser tab and removed when it closes. They do not survive closing the tab, including after a crash.'),
        'local' => $this->t('Local storage — drafts persist on the device across tabs and browser restarts. They remain readable after logout until explicitly cleared, so avoid this on shared devices.'),
      ],
      '#default_value' => in_array($config->get('draft_storage'), ['local', 'session'], TRUE) ? $config->get('draft_storage') : 'session',
      '#states' => [
        'visible' => [
          ':input[name="draft_auto_save"]' => ['checked' => TRUE],
        ],
      ],
    ];

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

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

    $raw = trim((string) $form_state->getValue('default_settings'));
    if ($raw === '') {
      return;
    }

    try {
      $decoded = json_decode($raw, TRUE, 512, JSON_THROW_ON_ERROR);
    }
    catch (\JsonException $e) {
      $form_state->setErrorByName('default_settings', $this->t('Invalid JSON: @message', ['@message' => $e->getMessage()]));
      return;
    }

    if (!is_array($decoded)) {
      $form_state->setErrorByName('default_settings', $this->t('Top-level JSON value must be an object keyed by category.'));
      return;
    }

    $unknown = array_diff(array_keys($decoded), self::CANONICAL_CATEGORIES);
    if ($unknown !== []) {
      $form_state->setErrorByName('default_settings', $this->t('Unknown categories: @keys. Allowed: @allowed.', [
        '@keys' => implode(', ', $unknown),
        '@allowed' => implode(', ', self::CANONICAL_CATEGORIES),
      ]));
    }

    foreach ($decoded as $category => $values) {
      if (!is_array($values)) {
        $form_state->setErrorByName('default_settings', $this->t('Category "@cat" must be an object of key/value pairs.', ['@cat' => $category]));
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state): void {
    $raw = trim((string) $form_state->getValue('default_settings'));
    $checkboxes = (array) $form_state->getValue('exposed_categories');
    // `checkboxes` returns ['theme' => 'theme', 'ui' => 0, ...]. Keep only the
    // truthy entries; persist as an ordered, indexed array matching the
    // canonical order so config diffs are stable.
    $exposed = array_values(array_intersect(self::CANONICAL_CATEGORIES, array_filter($checkboxes)));

    $draft_storage = (string) $form_state->getValue('draft_storage');

    $this->config('flowdrop_ui_components.settings')
      ->set('default_settings', $raw)
      ->set('exposed_categories', $exposed)
      ->set('draft_auto_save', (bool) $form_state->getValue('draft_auto_save'))
      ->set('draft_storage', in_array($draft_storage, ['local', 'session'], TRUE) ? $draft_storage : 'session')
      ->save();

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

  /**
   * Submit handler for the "Load library defaults" button.
   *
   * Populates the textarea with the bundled library's DEFAULT_SETTINGS
   * snapshot. Does not save — the site builder still has to click
   * "Save configuration" to persist.
   */
  public function loadLibraryDefaults(array &$form, FormStateInterface $form_state): void {
    $json = json_encode(LibraryDefaultSettings::all(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);

    // The form renderer reads textarea contents from userInput on rebuild, not
    // from `#default_value`. Update both for safety.
    $input = $form_state->getUserInput();
    $input['default_settings'] = $json;
    $form_state->setUserInput($input);
    $form_state->setValue('default_settings', $json);
    $form_state->setRebuild(TRUE);

    $this->messenger()->addStatus($this->t('Loaded library defaults into the textarea. Click "Save configuration" to apply.'));
  }

}
