<?php

declare(strict_types=1);

namespace Drupal\flowdrop_workflow\Diagnostic;

/**
 * The aggregated outcome of diagnosing a workflow.
 *
 * Holds every {@see Finding} produced this run, with helpers to group by
 * severity and look up a finding by its stable id (the doctor uses the latter
 * to match a user's selection back to a freshly-diagnosed finding at apply
 * time).
 */
final class DoctorReport {

  /**
   * Findings indexed by their stable id.
   *
   * @var array<string, \Drupal\flowdrop_workflow\Diagnostic\Finding>
   */
  private array $byId = [];

  /**
   * Constructs a DoctorReport.
   *
   * @param list<\Drupal\flowdrop_workflow\Diagnostic\Finding> $findings
   *   The findings, in the order their diagnostics produced them.
   */
  public function __construct(
    private readonly array $findings,
  ) {
    foreach ($findings as $finding) {
      // A later finding with a colliding id would shadow an earlier one; ids
      // are deterministic per (diagnostic, code, locator) so a collision means
      // two diagnostics genuinely describe the same element — keep the first.
      $this->byId += [$finding->id() => $finding];
    }
  }

  /**
   * All findings, in production order.
   *
   * @return list<\Drupal\flowdrop_workflow\Diagnostic\Finding>
   *   The findings.
   */
  public function findings(): array {
    return $this->findings;
  }

  /**
   * Whether the workflow is free of findings.
   *
   * @return bool
   *   TRUE when there are no findings at all.
   */
  public function isHealthy(): bool {
    return $this->findings === [];
  }

  /**
   * Looks up a finding by its stable id.
   *
   * @param string $id
   *   The finding id.
   *
   * @return \Drupal\flowdrop_workflow\Diagnostic\Finding|null
   *   The finding, or NULL when no finding with that id is present.
   */
  public function findingById(string $id): ?Finding {
    return $this->byId[$id] ?? NULL;
  }

  /**
   * Findings of a given severity.
   *
   * @param \Drupal\flowdrop_workflow\Diagnostic\Severity $severity
   *   The severity to filter by.
   *
   * @return list<\Drupal\flowdrop_workflow\Diagnostic\Finding>
   *   The matching findings.
   */
  public function bySeverity(Severity $severity): array {
    return array_values(array_filter(
      $this->findings,
      static fn (Finding $finding): bool => $finding->severity === $severity,
    ));
  }

  /**
   * Finding counts keyed by severity value.
   *
   * @return array<string, int>
   *   A map of severity value (e.g. 'error') to count. Severities with no
   *   findings are omitted.
   */
  public function counts(): array {
    $counts = [];
    foreach ($this->findings as $finding) {
      $key = $finding->severity->value;
      $counts[$key] = ($counts[$key] ?? 0) + 1;
    }
    return $counts;
  }

}
