<?php

declare(strict_types=1);

namespace Drupal\flowdrop_workflow\Service;

use Drupal\flowdrop_workflow\FlowDropWorkflowInterface;

/**
 * Default WorkflowDriftDetector implementation.
 *
 * @internal
 */
final class WorkflowDriftDetector implements WorkflowDriftDetectorInterface {

  /**
   * Per-request cache keyed by entity object identity.
   *
   * Drift detection re-loads source plugins on each call; for hot paths
   * (form load + form rebuild on AJAX) caching avoids redundant work within
   * a single request.
   *
   * Keyed by spl_object_hash, not workflow ID, so that a mutated entity
   * instance (e.g. exposure map changed in form state before save) is not
   * served a stale result from an earlier call with different state.
   *
   * @var array<string, list<array{side: string, name: string, node_id: string, port: string, stored: array|null, live: array|null}>>
   */
  private array $cache = [];

  public function __construct(
    private readonly WorkflowSchemaBuilderInterface $builder,
  ) {}

  /**
   * {@inheritdoc}
   */
  public function detectDrift(FlowDropWorkflowInterface $workflow): array {
    $key = spl_object_hash($workflow);
    if (isset($this->cache[$key])) {
      return $this->cache[$key];
    }
    $drifted = [];

    $live = [
      'input' => $workflow->getInputPorts() === []
        ? NULL
        : $this->buildLive($workflow, $workflow->getInputPorts(), 'input'),
      'output' => $workflow->getOutputPorts() === []
        ? NULL
        : $this->buildLive($workflow, $workflow->getOutputPorts(), 'output'),
    ];

    $stored = [
      'input' => $workflow->getParameterSchema(),
      'output' => $workflow->getOutputSchema(),
    ];

    foreach (['input', 'output'] as $side) {
      $sideExposures = $side === 'input' ? $workflow->getInputPorts() : $workflow->getOutputPorts();
      $liveProps = $live[$side]['properties'] ?? [];
      $storedProps = $stored[$side]['properties'] ?? [];

      foreach ($sideExposures as $entry) {
        $name = $entry['name'] ?? '';
        if ($name === '') {
          continue;
        }
        $liveFragment = $liveProps[$name] ?? NULL;
        $storedFragment = $storedProps[$name] ?? NULL;
        if ($liveFragment !== $storedFragment) {
          $drifted[] = [
            'side' => $side,
            'name' => $name,
            'node_id' => $entry['node_id'] ?? '',
            'port' => $entry['port'] ?? '',
            'stored' => $storedFragment,
            'live' => $liveFragment,
          ];
        }
      }
    }

    return $this->cache[$key] = $drifted;
  }

  /**
   * Builds the live schema for the given ports.
   *
   * @param \Drupal\flowdrop_workflow\FlowDropWorkflowInterface $workflow
   *   The owning workflow.
   * @param list<array{name: string, node_id: string, port: string}> $ports
   *   The ports to build.
   * @param string $direction
   *   The direction (input or output).
   *
   * @return array<string, mixed>
   *   The live schema.
   */
  private function buildLive(FlowDropWorkflowInterface $workflow, array $ports, string $direction): array {
    $visited = [];
    return $this->builder->build($workflow, $ports, $direction, $visited);
  }

}
