<?php

namespace Drupal\eb\Service;

use Drupal\Core\Render\RendererInterface;
use Drupal\eb\PluginInterfaces\PreviewableOperationInterface;
use Drupal\eb\Result\PreviewResult;
use Drupal\eb\Service\Dependency\DependencyResolver;

/**
 * Service for generating operation previews.
 */
class PreviewGenerator {

  /**
   * Constructor.
   *
   * @param \Drupal\Core\Render\RendererInterface $renderer
   *   The renderer service.
   * @param \Drupal\eb\Service\Dependency\DependencyResolver|null $dependencyResolver
   *   The dependency resolver (optional for backward compatibility).
   */
  public function __construct(
    protected RendererInterface $renderer,
    protected ?DependencyResolver $dependencyResolver = NULL,
  ) {}

  /**
   * Generate preview for a single operation.
   *
   * @param \Drupal\eb\PluginInterfaces\PreviewableOperationInterface $operation
   *   The operation to preview.
   *
   * @return \Drupal\eb\Result\PreviewResult
   *   The preview result.
   */
  public function generatePreview(PreviewableOperationInterface $operation): PreviewResult {
    // Call the operation's preview method.
    // Operations are responsible for their own preview generation.
    return $operation->preview();
  }

  /**
   * Generate previews for multiple operations.
   *
   * @param array<\Drupal\eb\PluginInterfaces\PreviewableOperationInterface> $operations
   *   Array of operations to preview.
   *
   * @return array<\Drupal\eb\Result\PreviewResult>
   *   Array of preview results, keyed by operation index.
   */
  public function generateBatchPreview(array $operations): array {
    $previews = [];

    foreach ($operations as $index => $operation) {
      $previews[$index] = $this->generatePreview($operation);
    }

    return $previews;
  }

  /**
   * Format preview as text table.
   *
   * @param array<\Drupal\eb\Result\PreviewResult> $previews
   *   Array of preview results.
   *
   * @return string
   *   Formatted text table.
   */
  public function formatAsText(array $previews): string {
    $rows = [];
    $rows[] = str_pad('#', 4) . str_pad('Operation', 20) . str_pad('Target', 30) . 'Description';
    $rows[] = str_repeat('-', 80);

    foreach ($previews as $index => $preview) {
      $created = $preview->getCreatedEntities();
      $modified = $preview->getModifiedEntities();
      $deleted = $preview->getDeletedEntities();

      $entities = array_merge($created, $modified, $deleted);

      foreach ($entities as $entity_info) {
        $operation_type = $entity_info['operation'] ?? 'unknown';
        $entity_type = $entity_info['entity_type'] ?? '';
        $entity_id = $entity_info['entity_id'] ?? '';
        $description = $entity_info['description'] ?? '';

        $rows[] = str_pad((string) ($index + 1), 4) .
          str_pad($operation_type, 20) .
          str_pad("{$entity_type}:{$entity_id}", 30) .
          $description;
      }
    }

    return implode("\n", $rows);
  }

  /**
   * Format preview as HTML.
   *
   * @param array<\Drupal\eb\Result\PreviewResult> $previews
   *   Array of preview results.
   *
   * @return array
   *   Render array.
   */
  public function formatAsHtml(array $previews): array {
    $build = [
      '#theme' => 'item_list',
      '#items' => [],
      '#list_type' => 'ol',
    ];

    foreach ($previews as $preview) {
      $created = $preview->getCreatedEntities();
      $modified = $preview->getModifiedEntities();
      $deleted = $preview->getDeletedEntities();

      foreach ($created as $entity_info) {
        $build['#items'][] = [
          '#markup' => '<strong>Create:</strong> ' . ($entity_info['description'] ?? ''),
        ];
      }

      foreach ($modified as $entity_info) {
        $build['#items'][] = [
          '#markup' => '<strong>Update:</strong> ' . ($entity_info['description'] ?? ''),
        ];
      }

      foreach ($deleted as $entity_info) {
        $build['#items'][] = [
          '#markup' => '<strong class="color-error">Delete:</strong> ' . ($entity_info['description'] ?? ''),
        ];
      }

      // Add warnings if present.
      if ($preview->hasWarnings()) {
        foreach ($preview->getWarnings() as $warning) {
          $build['#items'][] = [
            '#markup' => '<em class="warning">' . $warning . '</em>',
          ];
        }
      }
    }

    return $build;
  }

  /**
   * Format preview as JSON.
   *
   * @param array<\Drupal\eb\Result\PreviewResult> $previews
   *   Array of preview results.
   *
   * @return array<int, array<string, mixed>>
   *   JSON-serializable array.
   */
  public function formatAsJson(array $previews): array {
    $result = [];

    foreach ($previews as $index => $preview) {
      $result[] = [
        'index' => $index,
        'created' => $preview->getCreatedEntities(),
        'modified' => $preview->getModifiedEntities(),
        'deleted' => $preview->getDeletedEntities(),
        'warnings' => $preview->getWarnings(),
        'description' => $preview->getDescription(),
      ];
    }

    return $result;
  }

  /**
   * Generate format header for preview.
   *
   * @param array<string, mixed> $formatInfo
   *   Format information from ValidationManager::getFormatInfo().
   *
   * @return string
   *   Formatted header string.
   */
  public function generateFormatHeader(array $formatInfo): string {
    $lines = [];

    $description = $formatInfo['description'] ?? 'Unknown format';
    $mode = $formatInfo['mode'] ?? 'sync';

    $lines[] = "Format: {$description}";

    // Only show mode for sync-capable formats.
    if (isset($formatInfo['mode'])) {
      $lines[] = "Mode: {$mode}";
    }

    return implode("\n", $lines);
  }

  /**
   * Generate dependency graph visualization.
   *
   * @param array<array<string, mixed>> $operationData
   *   Array of operation data arrays.
   *
   * @return string
   *   Formatted dependency tree string.
   */
  public function generateDependencyTree(array $operationData): string {
    if (!$this->dependencyResolver) {
      return '';
    }

    $lines = [];
    $lines[] = "\nDependency Analysis:";

    try {
      // Build dependency graph.
      $graph = $this->buildGraphFromData($operationData);

      if (empty($graph)) {
        $lines[] = "  No dependencies detected";
        return implode("\n", $lines);
      }

      // Show dependencies for each operation.
      foreach ($operationData as $index => $operation) {
        $key = $this->makeEntityKeyFromData($operation, $index);
        $dependencies = $graph[$key] ?? [];

        if (!empty($dependencies)) {
          $operationType = $operation['operation'] ?? 'unknown';
          $entityInfo = $this->getEntityInfoFromOperation($operation);

          $lines[] = "  [{$operationType}] {$entityInfo}";
          $lines[] = "    depends on:";

          foreach ($dependencies as $dep) {
            $lines[] = "      - {$dep}";
          }
        }
      }
    }
    catch (\Exception $e) {
      $lines[] = "  Error analyzing dependencies: " . $e->getMessage();
    }

    return implode("\n", $lines);
  }

  /**
   * Build dependency graph from operation data.
   *
   * @param array<array<string, mixed>> $operationData
   *   Operation data arrays.
   *
   * @return array<string, array<string>>
   *   Dependency graph.
   */
  protected function buildGraphFromData(array $operationData): array {
    $graph = [];

    foreach ($operationData as $index => $operation) {
      $key = $this->makeEntityKeyFromData($operation, $index);
      $dependencies = $this->findDependenciesFromData($operation, $operationData);

      if (!empty($dependencies)) {
        $graph[$key] = $dependencies;
      }
    }

    return $graph;
  }

  /**
   * Find dependencies from operation data.
   *
   * @param array<string, mixed> $operation
   *   Operation data.
   * @param array<array<string, mixed>> $allOperations
   *   All operations.
   *
   * @return array<string>
   *   Array of dependency keys.
   */
  protected function findDependenciesFromData(array $operation, array $allOperations): array {
    $dependencies = [];
    $operationType = $operation['operation'] ?? '';

    // Field operations depend on bundle.
    if (in_array($operationType, ['create_field', 'update_field'])) {
      $entityType = $operation['entity_type'] ?? '';
      $bundle = $operation['bundle'] ?? '';

      if ($entityType && $bundle) {
        $dependencies[] = "bundle:{$entityType}:{$bundle}";
      }

      // Entity reference fields depend on target bundles.
      if (($operation['field_type'] ?? '') === 'entity_reference') {
        $targetType = $operation['field_storage_settings']['target_type'] ?? NULL;
        $handlerSettings = $operation['field_config_settings']['handler_settings'] ?? [];
        $targetBundleIds = $handlerSettings['target_bundles'] ?? [];

        foreach ($targetBundleIds as $bundleId) {
          $dependencies[] = "bundle:{$targetType}:{$bundleId}";
        }
      }
    }

    // Display operations depend on fields.
    if (in_array($operationType, ['configure_form_mode', 'configure_view_mode'])) {
      $entityType = $operation['entity_type'] ?? '';
      $bundle = $operation['bundle'] ?? '';
      $fieldName = $operation['field_name'] ?? '';

      if ($entityType && $bundle && $fieldName) {
        $dependencies[] = "field:{$entityType}:{$bundle}:{$fieldName}";
      }
    }

    return array_unique($dependencies);
  }

  /**
   * Make entity key from operation data.
   *
   * @param array<string, mixed> $operation
   *   Operation data.
   * @param int $index
   *   Operation index.
   *
   * @return string
   *   Entity key.
   */
  protected function makeEntityKeyFromData(array $operation, int $index): string {
    $operationType = $operation['operation'] ?? '';

    if (in_array($operationType, ['create_bundle', 'update_bundle'])) {
      $entityType = $operation['entity_type'] ?? '';
      $bundleId = $operation['bundle_id'] ?? '';
      return "bundle:{$entityType}:{$bundleId}";
    }

    if (in_array($operationType, ['create_field', 'update_field'])) {
      $entityType = $operation['entity_type'] ?? '';
      $bundle = $operation['bundle'] ?? '';
      $fieldName = $operation['field_name'] ?? '';
      return "field:{$entityType}:{$bundle}:{$fieldName}";
    }

    return "operation:{$operationType}:{$index}";
  }

  /**
   * Get entity info string from operation.
   *
   * @param array<string, mixed> $operation
   *   Operation data.
   *
   * @return string
   *   Entity info string.
   */
  protected function getEntityInfoFromOperation(array $operation): string {
    $operationType = $operation['operation'] ?? '';

    if (str_contains($operationType, 'bundle')) {
      $entityType = $operation['entity_type'] ?? '';
      $bundleId = $operation['bundle_id'] ?? '';
      return "{$entityType}.{$bundleId}";
    }

    if (str_contains($operationType, 'field')) {
      $entityType = $operation['entity_type'] ?? '';
      $bundle = $operation['bundle'] ?? '';
      $fieldName = $operation['field_name'] ?? '';
      return "{$entityType}.{$bundle}.{$fieldName}";
    }

    return $operationType;
  }

}
