<?php

/**
 * @file
 * Post-update hooks for the flowdrop_pipeline module.
 */

declare(strict_types=1);

use Drupal\flowdrop_pipeline\Entity\FlowDropPipelineInterface;
use Drupal\flowdrop_session\Entity\FlowDropSessionInterface;

/**
 * Backfill session_id and pending-interrupt slots onto FlowDropPipeline.
 *
 * Lifts per-pipeline state off session metadata onto typed pipeline
 * columns. Walks every session that has either:
 *  - playground execution context that references a pipeline (sets
 *    session_id on those pipelines), or
 *  - legacy pending_interrupt_* keys in session metadata (copies them onto
 *    the matching pipeline and clears the metadata).
 *
 * Idempotent. Batched 50 sessions per pass so large installs don't OOM.
 *
 * @param-out array $sandbox
 */
function flowdrop_pipeline_post_update_backfill_session_and_pending_interrupt(?array &$sandbox = NULL): void {
  // Nothing to backfill when flowdrop_session isn't enabled — both the
  // source data (session metadata) and the destination column (session_id,
  // provided by flowdrop_session) are absent on a pipeline-only install.
  // Hook stays in flowdrop_pipeline intentionally: moving it would rename
  // the function, which Drupal would re-run on sites that already
  // completed it under the original name.
  if (!\Drupal::moduleHandler()->moduleExists('flowdrop_session')) {
    return;
  }
  $sessionStorage = \Drupal::entityTypeManager()->getStorage('flowdrop_session');
  $pipelineStorage = \Drupal::entityTypeManager()->getStorage('flowdrop_pipeline');

  if (!isset($sandbox['ids'])) {
    $sandbox['ids'] = $sessionStorage->getQuery()->accessCheck(FALSE)->execute();
    $sandbox['total'] = count($sandbox['ids']);
    $sandbox['progress'] = 0;
    $sandbox['stamped_session'] = 0;
    $sandbox['moved_pending'] = 0;
  }

  $batch = array_splice($sandbox['ids'], 0, 50);
  foreach ($batch as $sessionId) {
    $sandbox['progress']++;
    $session = $sessionStorage->load($sessionId);
    if (!$session instanceof FlowDropSessionInterface) {
      continue;
    }

    $metadata = $session->getMetadata();
    $pendingId = $metadata['pending_interrupt_id'] ?? NULL;
    $pendingType = $metadata['pending_interrupt_type'] ?? NULL;
    $pendingPipelineId = $metadata['pending_execution_id'] ?? NULL;

    // Move pending state from session metadata onto the pipeline it
    // belongs to. The pending_execution_id is the pipeline id.
    if ($pendingPipelineId !== NULL && $pendingPipelineId !== '') {
      $pipeline = $pipelineStorage->load($pendingPipelineId);
      if ($pipeline instanceof FlowDropPipelineInterface) {
        $dirty = FALSE;
        if ($pipeline->getSessionId() === NULL) {
          $pipeline->setSessionId((string) $sessionId);
          $sandbox['stamped_session']++;
          $dirty = TRUE;
        }
        if (
          $pipeline->getPendingInterruptId() === NULL
          && is_string($pendingId) && $pendingId !== ''
        ) {
          $pipeline->setPendingInterruptId($pendingId);
          $pipeline->setPendingInterruptType(is_string($pendingType) ? $pendingType : NULL);
          $sandbox['moved_pending']++;
          $dirty = TRUE;
        }
        if ($dirty) {
          $pipeline->save();
        }
      }
    }

    // Stamp session_id on any other pipelines that reference this session
    // in their execution_context.playground.session_id but don't yet have
    // the typed column populated.
    $candidateIds = $pipelineStorage->getQuery()
      ->condition('execution_context', '%"session_id":"' . $sessionId . '"%', 'LIKE')
      ->accessCheck(FALSE)
      ->execute();
    foreach ($pipelineStorage->loadMultiple($candidateIds) as $pipeline) {
      if ($pipeline instanceof FlowDropPipelineInterface && $pipeline->getSessionId() === NULL) {
        $pipeline->setSessionId((string) $sessionId);
        $pipeline->save();
        $sandbox['stamped_session']++;
      }
    }

    // Drop the legacy keys from session metadata regardless of whether
    // we found a matching pipeline — they're no longer authoritative.
    if (
      array_key_exists('pending_interrupt_id', $metadata)
      || array_key_exists('pending_interrupt_type', $metadata)
      || array_key_exists('pending_execution_id', $metadata)
    ) {
      unset(
        $metadata['pending_interrupt_id'],
        $metadata['pending_interrupt_type'],
        $metadata['pending_execution_id'],
      );
      $session->setMetadata($metadata);
      $session->save();
    }
  }

  $sandbox['#finished'] = $sandbox['total'] === 0
    ? 1
    : ($sandbox['progress'] / $sandbox['total']);

  if ($sandbox['#finished'] >= 1) {
    \Drupal::messenger()->addStatus(t(
      'Backfilled pipelines: @stamped session_id stamps, @moved pending-interrupt moves.',
      [
        '@stamped' => $sandbox['stamped_session'],
        '@moved' => $sandbox['moved_pending'],
      ]
    ));
  }
}
