<?php

namespace Drupal\eb\Drush\Commands;

use Drupal\eb\Service\DefinitionFactory;
use Drupal\eb\Service\DefinitionGeneratorInterface;
use Drupal\eb\Service\YamlParser;
use Drush\Attributes as CLI;
use Drush\Commands\DrushCommands;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Drush commands for Entity Builder definition-centric export operations.
 *
 * Provides two commands for the definition-centric export workflow:
 * - eb:generate: Creates/updates EbDefinition from Drupal entities.
 * - eb:export: Exports an existing definition to YAML file.
 */
final class EbExportCommands extends DrushCommands {

  /**
   * Constructor.
   *
   * @param \Drupal\eb\Service\DefinitionGeneratorInterface $definitionGenerator
   *   The definition generator service.
   * @param \Drupal\eb\Service\DefinitionFactory $definitionFactory
   *   The definition factory service.
   * @param \Drupal\eb\Service\YamlParser $yamlParser
   *   The YAML parser service.
   */
  public function __construct(
    protected DefinitionGeneratorInterface $definitionGenerator,
    protected DefinitionFactory $definitionFactory,
    protected YamlParser $yamlParser,
  ) {
    parent::__construct();
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container): self {
    return new self(
      $container->get('eb.definition_generator'),
      $container->get('eb.definition_factory'),
      $container->get('eb.yaml_parser'),
    );
  }

  /**
   * Generate a definition entity from existing Drupal entities.
   *
   * Creates or updates an EbDefinition entity by reverse-engineering
   * the specified bundles from Drupal. The definition can then be exported to
   * YAML using the eb:export command.
   *
   * @param string $definition_id
   *   The definition ID to create/update.
   * @param array<string, mixed> $options
   *   Command options.
   */
  #[CLI\Command(name: 'eb:generate', aliases: ['ebg'])]
  #[CLI\Argument(name: 'definition_id', description: 'The definition ID to create')]
  #[CLI\Option(name: 'entity-type', description: 'Entity type to include')]
  #[CLI\Option(name: 'bundle', description: 'Bundle to include (can be repeated)')]
  #[CLI\Option(name: 'label', description: 'Definition label')]
  #[CLI\Option(name: 'update', description: 'Update existing definition if it exists')]
  #[CLI\Option(name: 'include-fields', description: 'Include field definitions')]
  #[CLI\Option(name: 'include-displays', description: 'Include display configurations')]
  #[CLI\Option(name: 'include-extensions', description: 'Include extension data (field groups, etc.)')]
  #[CLI\Option(name: 'normalize', description: 'Normalize settings (exclude Drupal defaults)')]
  #[CLI\Usage(
    name: 'eb:generate my_definition --entity-type=node --bundle=article',
    description: 'Generate definition from article content type'
  )]
  #[CLI\Usage(
    name: 'eb:generate my_definition --entity-type=node --bundle=article --bundle=page --label="My Site Definition"',
    description: 'Generate definition from multiple bundles with custom label'
  )]
  public function generate(
    string $definition_id,
    array $options = [
      'entity-type' => NULL,
      'bundle' => [],
      'label' => NULL,
      'update' => FALSE,
      'include-fields' => TRUE,
      'include-displays' => TRUE,
      'include-extensions' => TRUE,
      'normalize' => TRUE,
    ],
  ): int {
    $entity_type = $options['entity-type'];
    $bundles = $options['bundle'];

    // Validate inputs.
    if (!$entity_type) {
      $this->logger()->error(dt('Entity type is required (--entity-type=node)'));
      return self::EXIT_FAILURE;
    }

    if (empty($bundles)) {
      $this->logger()->error(dt('At least one bundle is required (--bundle=article)'));
      return self::EXIT_FAILURE;
    }

    // Ensure bundles is an array.
    if (!is_array($bundles)) {
      $bundles = [$bundles];
    }

    // Check if definition exists and update not allowed.
    if (!$options['update'] && $this->definitionFactory->definitionExists($definition_id)) {
      $this->logger()->error(dt('Definition "@id" already exists. Use --update to overwrite.', [
        '@id' => $definition_id,
      ]));
      return self::EXIT_FAILURE;
    }

    try {
      // Build bundle selection.
      $bundleSelection = [
        $entity_type => $bundles,
      ];

      // Build generation options.
      $genOptions = [
        'include_fields' => (bool) $options['include-fields'],
        'include_displays' => (bool) $options['include-displays'],
        'include_extensions' => (bool) $options['include-extensions'],
        'normalize_settings' => (bool) $options['normalize'],
      ];

      // Generate definition data from Drupal entities.
      $definitionData = $this->definitionGenerator->generate($bundleSelection, $genOptions);

      // Add metadata.
      $definitionData['id'] = $definition_id;
      $definitionData['label'] = $options['label'] ?: ucwords(str_replace('_', ' ', $definition_id));

      // Create or update definition.
      if ($options['update'] && $this->definitionFactory->definitionExists($definition_id)) {
        $definition = $this->definitionFactory->loadDefinition($definition_id);
        $definition = $this->definitionFactory->updateFromYaml($definition, $definitionData);
        $definition->save();

        $this->logger()->success(dt('Updated definition "@id"', ['@id' => $definition_id]));
      }
      else {
        $definition = $this->definitionFactory->createFromYaml($definitionData);
        $definition->save();

        $this->logger()->success(dt('Created definition "@id"', ['@id' => $definition_id]));
      }

      // Show summary.
      $bundleCount = count($definitionData['bundle_definitions'] ?? []);
      $fieldCount = count($definitionData['field_definitions'] ?? []);
      $this->logger()->notice(dt('Definition contains @bundles bundle(s) and @fields field(s)', [
        '@bundles' => $bundleCount,
        '@fields' => $fieldCount,
      ]));

      return self::EXIT_SUCCESS;
    }
    catch (\Exception $e) {
      $this->logger()->error(dt('Generation failed: @message', ['@message' => $e->getMessage()]));
      return self::EXIT_FAILURE;
    }
  }

  /**
   * Export an existing definition entity to a YAML file.
   *
   * Serializes an EbDefinition entity to YAML format suitable for
   * import. The output is compatible with the import workflow, enabling
   * round-trip capability.
   *
   * @param string $definition_id
   *   The definition ID to export.
   * @param string $output
   *   The output YAML file path.
   */
  #[CLI\Command(name: 'eb:export', aliases: ['ebe'])]
  #[CLI\Argument(name: 'definition_id', description: 'The definition ID to export')]
  #[CLI\Argument(name: 'output', description: 'Output YAML file path')]
  #[CLI\Usage(name: 'eb:export my_definition output.yml', description: 'Export definition to YAML file')]
  #[CLI\Usage(name: 'eb:export real_world_blog exports/blog.yml', description: 'Export blog definition')]
  public function export(string $definition_id, string $output): int {
    // Load the definition.
    $definition = $this->definitionFactory->loadDefinition($definition_id);
    if (!$definition) {
      $this->logger()->error(dt('Definition "@id" not found', ['@id' => $definition_id]));
      return self::EXIT_FAILURE;
    }

    try {
      // Get exportable array from definition.
      $data = $definition->toExportArray();

      // Convert to YAML.
      $content = $this->yamlParser->export($data);

      // Ensure directory exists.
      $dir = dirname($output);
      if ($dir && !is_dir($dir)) {
        if (!mkdir($dir, 0755, TRUE)) {
          $this->logger()->error(dt('Failed to create directory: @dir', ['@dir' => $dir]));
          return self::EXIT_FAILURE;
        }
      }

      // Write to file.
      if (file_put_contents($output, $content) === FALSE) {
        $this->logger()->error(dt('Failed to write to file: @file', ['@file' => $output]));
        return self::EXIT_FAILURE;
      }

      $this->logger()->success(dt('Exported "@id" to @file', [
        '@id' => $definition_id,
        '@file' => $output,
      ]));

      return self::EXIT_SUCCESS;
    }
    catch (\Exception $e) {
      $this->logger()->error(dt('Export failed: @message', ['@message' => $e->getMessage()]));
      return self::EXIT_FAILURE;
    }
  }

  /**
   * List all available definition entities.
   *
   * @return int
   *   The command exit code.
   */
  #[CLI\Command(name: 'eb:list', aliases: ['ebl'])]
  #[CLI\Usage(name: 'eb:list', description: 'List all definitions')]
  public function listDefinitions(): int {
    try {
      // Use the definition factory's entity type manager access.
      // @phpstan-ignore globalDrupalDependencyInjection.useDependencyInjection
      $storage = \Drupal::entityTypeManager()->getStorage('eb_definition');
      $definitions = $storage->loadMultiple();

      if (empty($definitions)) {
        $this->logger()->notice(dt('No definitions found'));
        return self::EXIT_SUCCESS;
      }

      $rows = [];
      foreach ($definitions as $definition) {
        /** @var \Drupal\eb\Entity\EbDefinition $definition */
        $rows[] = [
          $definition->id(),
          $definition->label(),
          $definition->getApplicationStatus(),
          $definition->getBundleCount(),
          $definition->getFieldCount(),
        ];
      }

      // Output as table.
      // @phpstan-ignore class.notFound
      $this->io()->table(
        ['ID', 'Label', 'Status', 'Bundles', 'Fields'],
        $rows
      );

      return self::EXIT_SUCCESS;
    }
    catch (\Exception $e) {
      $this->logger()->error(dt('List failed: @message', ['@message' => $e->getMessage()]));
      return self::EXIT_FAILURE;
    }
  }

}
