<?php

declare(strict_types=1);

namespace Drupal\flowdrop\Service;

use Drupal\Core\Form\FormStateInterface;

/**
 * Renders Drupal Form API arrays from FlowDropSchema fragments.
 *
 * FlowDropSchema is a deliberate, versioned subset of JSON Schema (Draft
 * 2020-12 keywords) — not a full JSON Schema implementation. The supported
 * keywords are:
 *
 * - `type` (string, integer, number, boolean, array, object; or a list whose
 *   first non-"null" member wins).
 * - `enum` — rendered as radios (≤10 options) or a select (>10).
 * - `default`, `title`, `description`, `required` (object-level array).
 * - `format` (string only): `multiline`, `date`, `date-time`, `email`, `uri`,
 *   `url`.
 * - `minimum`, `maximum` (number/integer).
 * - `minLength`, `maxLength`, `pattern` (string; patterns >512 chars or
 *   containing unescaped delimiters are treated as opaque).
 * - `properties` (object).
 * - `items` (array — only enum-of-strings arrays render as checkboxes; other
 *   shapes fall back to a JSON textarea).
 * - `presentation: confirmation` — FlowDrop-specific hint for boolean
 *   button-pair rendering, handled by the interrupt SchemaFormHandler.
 *
 * Keywords *not* supported: `oneOf`, `anyOf`, `allOf`, `not`, `$ref`,
 * `additionalProperties`, `patternProperties`, `dependencies`, `if`/`then`/
 * `else`, `const`, `multipleOf`, `uniqueItems`, `contains`. Schemas using
 * these will render the unrecognised subtree as a JSON textarea fallback;
 * the validate path will silently pass for unsupported constraints.
 *
 * See `docs/reference/api/flowdrop-schema.md` for the contract.
 *
 * @api
 */
interface FlowDropSchemaFormBuilderInterface {

  /**
   * Builds a form-element subtree from a FlowDropSchema fragment.
   *
   * Recurses into object `properties` and array `items`. Picks the widget
   * based on `type` + `format` + `enum` + `presentation` hints.
   *
   * @param array<string, mixed> $schema
   *   The FlowDropSchema fragment.
   * @param mixed $defaultValue
   *   The default value to pre-fill (NULL when none).
   * @param array{required?: bool, title?: string|\Stringable, description?: string|\Stringable} $hints
   *   UI hints — title/description override the schema's, and `required`
   *   forces the element to be marked required even if the schema doesn't
   *   declare it (used when the caller knows from outer `required` array).
   *
   * @return array<string, mixed>
   *   A Drupal form element. Top-level callers wrap this in their own form.
   */
  public function buildElement(array $schema, mixed $defaultValue = NULL, array $hints = []): array;

  /**
   * Coerces submitted form values back to the canonical schema shape.
   *
   * Drupal form arrays are flat strings/arrays; this method normalises
   * checkbox group output, casts numeric strings to numbers, parses
   * JSON-textarea values, etc.
   *
   * @param array<string, mixed> $schema
   *   The FlowDropSchema describing the expected shape.
   * @param mixed $rawValue
   *   The raw value from `$form_state->getValue(...)`.
   *
   * @return mixed
   *   The canonicalised value.
   */
  public function extractValue(array $schema, mixed $rawValue): mixed;

  /**
   * Validates a coerced value against the schema's constraints.
   *
   * Populates form-state errors via the supplied parents path. Returns
   * TRUE when valid, FALSE when one or more errors were set.
   *
   * @param array<string, mixed> $schema
   *   The FlowDropSchema describing the expected shape.
   * @param mixed $value
   *   The canonicalised value (already through extractValue()).
   * @param \Drupal\Core\Form\FormStateInterface $formState
   *   The form state where validation errors are recorded.
   * @param list<string> $parents
   *   The form's element path for error reporting.
   *
   * @return bool
   *   TRUE if valid.
   */
  public function validateValue(array $schema, mixed $value, FormStateInterface $formState, array $parents = []): bool;

}
