<?php

declare(strict_types=1);

namespace Drupal\flowdrop_pipeline\Service;

use Drupal\Core\Database\Connection;

/**
 * Resolves pipeline lineage (root / parent) for a set of execution ids.
 *
 * Used by the playground to attach the root pipeline id to messages without
 * walking the pipeline chain or denormalising lineage onto every message.
 *
 * Lookups are numeric-filtered, existence-checked and session-scoped: an
 * execution id only resolves when it is a real pipeline row in the given
 * session. This makes the generic, possibly-external message execution_id
 * field safe to resolve against without changing its contract — non-pipeline
 * ids simply do not match and carry no lineage.
 *
 * @internal
 *   This class is not part of the public API and may change without notice.
 */
final class PipelineLineageResolver {

  public function __construct(
    private readonly Connection $database,
  ) {}

  /**
   * Resolves lineage for execution ids belonging to a session.
   *
   * @param array<int, int|string|null> $executionIds
   *   Candidate execution ids (message execution_id values). Non-numeric ids
   *   are dropped — only a numeric pipeline id can match a pipeline row.
   * @param string|int $sessionId
   *   The session the pipelines must belong to. Scopes the lookup so an
   *   external id that happens to collide with an unrelated pipeline's id
   *   cannot match. Assumes the session_id column is present, i.e. that
   *   flowdrop_session is installed — true whenever this is called from the
   *   playground/session read path, the only caller.
   *
   * @return array<string, array{root: string, parent: string|null}>
   *   Map keyed by pipeline id, each with the resolved root pipeline id and
   *   parent pipeline id (NULL for a top-level run). Ids that are not a
   *   pipeline in this session are absent from the map.
   */
  public function resolveForSession(array $executionIds, string|int $sessionId): array {
    $ids = [];
    foreach ($executionIds as $id) {
      if ($id !== NULL && $id !== '' && ctype_digit((string) $id)) {
        $ids[(int) $id] = (int) $id;
      }
    }
    if ($ids === []) {
      return [];
    }

    $rows = $this->database->select('flowdrop_pipeline', 'p')
      ->fields('p', ['id', 'root_pipeline_id', 'parent_pipeline_id'])
      ->condition('p.id', $ids, 'IN')
      ->condition('p.session_id', (int) $sessionId)
      ->execute();

    $map = [];
    foreach ($rows as $row) {
      $map[(string) $row->id] = [
        // root_pipeline_id is always populated on a saved pipeline (self-root
        // on insert, inherited for nested). The ?? id is defensive only — it
        // never fires for a row a reader can reach.
        'root' => (string) ($row->root_pipeline_id ?? $row->id),
        'parent' => $row->parent_pipeline_id !== NULL ? (string) $row->parent_pipeline_id : NULL,
      ];
    }
    return $map;
  }

}
