<?php

declare(strict_types = 1);

namespace Fgm\Drupal\Composer;

use Composer\Command\BaseCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;
use Twig\Environment;
use Twig\Extension\DebugExtension;
use Twig\Loader\FilesystemLoader;
use Twig\TemplateWrapper;

/**
 * Provides command primitives useful for most commands, focused on templating.
 *
 * @package Fgm\Drupal\Composer
 */
abstract class BaseBuilderCommand extends BaseCommand {

  /**
   * The name of the argument defining the file to generate.
   */
  const ARG_FILE = 'file';

  /**
   * Provide the path to the project settings directory.
   *
   * @return string
   *   The absolute path to the directory.
   */
  protected function getSettingsPath(): string {
    $settingsPath = getcwd() . "/settings";
    return $settingsPath;
  }

  /**
   * Validate template definition for command, returning its name on success.
   *
   * @param \Symfony\Component\Console\Input\InputInterface $input
   *   Command input.
   * @param \Symfony\Component\Console\Output\OutputInterface $output
   *   Command output.
   *
   * @return array
   *   - string Template name
   *   - int Error
   */
  public function validateTemplateName(InputInterface $input, OutputInterface $output): array {
    $file = $input->getArgument(static::ARG_FILE);
    $conf = $this->getComposer()->getPackage()->getExtra()[Builder::NAME] ?? [];
    $templateName = $conf['templates'][$file] ?? '';
    if (empty($templateName)) {
      $output->writeln(sprintf(
        'Could not build file %s: no such template in composer.json extra section',
        $file
      ));
      return ["", 1];
    }
    return [$templateName, 0];
  }

  /**
   * Perform template rendering.
   *
   * @param \Twig\TemplateWrapper $wrapper
   *   A Twig user-space template wrapper.
   * @param array $context
   *   The data context with which to perform the rendering.
   * @param string $destination
   *   The path where to write the rendering result.
   *
   * @return array
   *   - string message
   *   - int status: 0 on success, other values on errors.
   */
  protected function render(
    TemplateWrapper $wrapper,
    array $context,
    string $destination
  ): array {
    if (file_exists($destination)) {
      $ok = unlink($destination);
      if (!$ok) {
        return [sprintf("Could not remove old %s file", $destination), 1];
      }
    }

    $rendered = $wrapper->render($context);
    $ok = file_put_contents($destination, $rendered, LOCK_EX);
    if (!$ok) {
      return [sprintf('Could not write new %s file', $destination), 2];
    }

    return ["", 0];
  }

  /**
   * Prepare the template and its parameters as obtained from configuration.
   *
   * @param \Symfony\Component\Console\Input\InputInterface $input
   *   Command input.
   * @param \Symfony\Component\Console\Output\OutputInterface $output
   *   Command output.
   *
   * @return array
   *   - TemplateWrapper|NULL: Twig template wrapper
   *   - array: template parameters
   *   - string: error message
   *   - int: error, 0 if OK. If non-zero, only the error message is reliable.
   *
   * @throws \Twig\Error\LoaderError
   * @throws \Twig\Error\RuntimeError
   * @throws \Twig\Error\SyntaxError
   */
  protected function prepare(InputInterface $input, OutputInterface $output): array {
    [$templateName, $err] = $this->validateTemplateName($input, $output);
    if ($err) {
      return [NULL, [], "Could not validate template name", 1];
    };

    $settingsPath = $this->getSettingsPath();
    $templatePath = "${settingsPath}/${templateName}";
    $realTemplatePath = realpath($templatePath);
    if (empty($realTemplatePath)) {
      return [NULL, [],
        sprintf("Could not load template %s: no such file", $templateName),
        2,
      ];
    }

    $paramsPath = "${settingsPath}/merged.params.local.yml";
    $realParamsPath = realpath($paramsPath);
    if (empty($realParamsPath)) {
      return [NULL, [],
        sprintf("Could not load parameters %s: no such file", $paramsPath),
        3,
      ];
    }

    $yaml = new Yaml();
    try {
      $params = $yaml->parseFile($realParamsPath);
    }
    catch (ParseException $e) {
      return [NULL, [],
        sprintf("Could not parse %s: %s", $realParamsPath, $e->getMessage()),
        4,
      ];
    }

    $loader = new FilesystemLoader($settingsPath, $settingsPath);
    $twig = new Environment($loader, [
      'auto_reload' => TRUE,
      'cache' => FALSE,
      'debug' => TRUE,
      'strict_variables' => TRUE,
    ]);
    $twig->addExtension(new DebugExtension());
    $wrapper = $twig->load($templateName);
    return [$wrapper, $params, "", 0];
  }

}