<?php

namespace Drupal\pagedesigner_debug\Commands;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\State\StateInterface;
use Drupal\pagedesigner_debug\Service\StructureDebugger;
use Drush\Commands\DrushCommands;

/**
 * A drush command file.
 *
 * @package Drupal\pagedesigner_debug\Commands
 */
class Debug extends DrushCommands {

  /**
   * The print mask.
   *
   * @var string
   */
  protected $mask = "| %-10.10s | %-30.30s | %-60.60s |\n";

  /**
   * Creates a new Debug instance.
   *
   * @param \Drupal\pagedesigner_debug\Service\StructureDebugger $debugger
   *   The debugger.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Drupal\Core\State\StateInterface $state
   *   The state service.
   */
  public function __construct(
    protected StructureDebugger $debugger,
    protected EntityTypeManagerInterface $entityTypeManager,
    protected StateInterface $state,
  ) {}

  /**
   * Drush command that displays the given text.
   *
   * @command pagedesigner_debug:check
   * @aliases pd_debug:check
   * @option strategy
   *   The strategy to employ: 'preserve' or 'cleanup'. Defaults to preserve.
   * @usage pagedesigner_debug:correct --strategy=preserve
   */
  public function check($options = ['strategy' => 'preserve']) {
    $this->debugger->setStrategy($options['strategy']);
    if (!$this->getConsent()) {
      return FALSE;
    }
    $this->doCheck();
    $this->io()->comment('Debugging stopped.');
  }

  /**
   * Drush command that displays the given text.
   *
   * @command pagedesigner_debug:correct
   * @aliases pd_debug:correct
   * @option strategy
   *   The strategy to employ: 'preserve' or 'cleanup'. Defaults to preserve.
   * @usage pagedesigner_debug:correct --strategy=preserve
   */
  public function correct($options = ['strategy' => 'preserve']) {
    $this->debugger->setStrategy($options['strategy']);
    if (!$this->getConsent()) {
      return FALSE;
    }
    $this->state->set('pagedesigner_debug_running', TRUE);
    do {
      $hasCorrections = $this->doCorrect();
      if (empty($hasCorrections) && $hasCorrections !== FALSE) {
        $this->io()->title('Clearing up special elements');
        $this->debugger->clearSpecialElements();
        $this->io()->title('Removing orphans');
        if (($orphanCount = $this->debugger->removeOrphans(TRUE)) !== FALSE) {
          $this->io()->success('Removed ' . $orphanCount . ' orphans.');
          $hasCorrections = $this->doCorrect();
        }
        else {
          $this->io()->warning('Removing orphans failed.');
        }
      }
    } while ($hasCorrections);
    $this->state->delete('pagedesigner_debug_running');
    $this->io()->comment('Debugging stopped.');
  }

  /**
   * Executes check runs.
   */
  protected function doCheck() {
    $this->io()->title('Collecting corrections');
    $fields = $this->entityTypeManager->getStorage('field_storage_config')->loadByProperties([
      'entity_type' => 'node',
      'type' => 'pagedesigner_item',
    ]);
    $bundles = [];
    /** @var \Drupal\field\FieldStorageConfigInterface $field */
    foreach ($fields as $field) {
      if (!empty($field)) {
        $bundles = [...$bundles, ...array_keys($field->getBundles())];
      }
    }

    $nids = $this->entityTypeManager->getStorage('node')
      ->getQuery()
      ->condition('type', $bundles, 'IN')
      ->accessCheck(FALSE)
      ->execute();
    $this->debugger->checkStructure($nids);
    $errors = $this->debugger->getErrors();
    $corrections = $this->debugger->getCorrections();
    if (!empty($errors)) {
      $this->printErrors($errors);
      if (!$this->io()->confirm('Do you want to continue despite errors?', FALSE)) {
        return FALSE;
      }
    }
    if (!empty($corrections)) {
      $this->printCorrections($corrections);
    }
    else {
      $this->io()->success('No more corrections found.');
    }
    $orphans = $this->debugger->findOrphans();
    if (!empty($orphans)) {
      $this->io()->note('Found ' . count($orphans) . ' orphans.');
    }
    else {
      $this->io()->success('No orphans found.');
    }
    return $corrections;
  }

  /**
   * Helper method to apply corrections.
   *
   * @return array
   *   The corrections found.
   */
  protected function doCorrect() {
    while (($corrections = $this->doCheck())) {
      if ($this->io()->confirm('Do you want to apply the listed corrections?')) {
        $this->io()->text('Applying corrections (this might take a while)');
        $this->debugger->applyCorrections();
      }
      else {
        return FALSE;
      }
    }
    return $corrections;
  }

  /**
   * Print the found corrections.
   */
  protected function printErrors($errors) {
    $this->io()->warning('There are errors which need to be corrected by hand.');
    $tableData = [];
    foreach ($errors as $element => $data) {
      foreach ($data as $type => $value) {
        $text = (is_array($value)) ? implode(',', $value) : $value;
        $tableData[] = [$element, $type, $text];
      }
    }
    $this->io->table(['Element', 'Error', 'Values'], $tableData);
  }

  /**
   * Print the found corrections.
   */
  protected function printCorrections(array $corrections) {
    $tableData = [];
    foreach ($corrections as $element => $data) {
      foreach ($data as $type => $value) {
        $text = (is_array($value)) ? implode(',', $value) : $value;
        $tableData[] = [$element, $type, $text];
      }
    }
    $this->io->table(['Element', 'Correction', 'Values'], $tableData);
  }

  /**
   * Get consent from user to continue.
   *
   * @return bool
   *   True, if the user gives consent.
   */
  protected function getConsent() {
    $this->io()->warning('The debugger is experimental!');
    return $this->io()->confirm('Do you want to continue?');
  }

}
