<?php

declare(strict_types=1);

namespace Drupal\flowdrop_workflow\Diagnostic;

/**
 * One diagnosed issue on a stored workflow (an immutable value object).
 *
 * A finding is produced by a {@see WorkflowDiagnosticInterface} and carries the
 * remedies the user may choose from. Its identity ({@see self::id()}) is
 * deterministic and built from a *stable* locator (a node id or edge id, never
 * an array index) so a selection made on the review screen still resolves to
 * the same finding when the diagnostic re-runs at apply time.
 */
final class Finding {

  /**
   * Constructs a Finding.
   *
   * @param string $diagnosticId
   *   The id of the diagnostic that produced this finding.
   * @param string $code
   *   A stable machine code. Reuses the validator's CODE_* values where the
   *   finding wraps a validator error (e.g. 'R1_PLUGIN_MISSING').
   * @param \Drupal\flowdrop_workflow\Diagnostic\Severity $severity
   *   The severity.
   * @param string $locator
   *   A STABLE identity for the affected element: 'node.{id}',
   *   'node.{id}.config.{key}', 'edge.{edgeId}', 'exposure.{side}.{name}', or
   *   'workflow'. Never an array index.
   * @param string $summary
   *   A human-readable description of the problem (already interpolated; the
   *   validator builds these with sprintf, so they are plain strings).
   * @param array<string, mixed> $context
   *   Opaque data the owning diagnostic needs to rebuild mutations — e.g.
   *   ['node_id' => '…'], ['edge_id' => '…', 'side' => 'input', 'port' => '…'].
   * @param list<\Drupal\flowdrop_workflow\Diagnostic\RemedyOption> $remedies
   *   The offered remedies. Empty when the finding is informational only.
   */
  public function __construct(
    public readonly string $diagnosticId,
    public readonly string $code,
    public readonly Severity $severity,
    public readonly string $locator,
    public readonly string $summary,
    public readonly array $context = [],
    public readonly array $remedies = [],
  ) {}

  /**
   * The deterministic finding id.
   *
   * Stable across re-diagnosis of the same stored state: a function of the
   * producing diagnostic, the code, and the stable locator only. The client
   * sends this id back with the chosen remedy; the doctor matches it against a
   * freshly-diagnosed finding before rebuilding mutations.
   *
   * @return string
   *   A short hex digest identifying this finding.
   */
  public function id(): string {
    return substr(hash('sha256', $this->diagnosticId . ':' . $this->code . ':' . $this->locator), 0, 20);
  }

  /**
   * Returns the remedy with the given id, or NULL when none matches.
   *
   * @param string $remedyId
   *   The remedy id.
   *
   * @return \Drupal\flowdrop_workflow\Diagnostic\RemedyOption|null
   *   The matching remedy, or NULL.
   */
  public function remedy(string $remedyId): ?RemedyOption {
    foreach ($this->remedies as $remedy) {
      if ($remedy->id === $remedyId) {
        return $remedy;
      }
    }
    return NULL;
  }

}
