<?php

class MbComponentOptions {
  public function __construct() {
    $this->supports = new MbComponentSupports();
  }

  /** @var bool
   * Flag if the block is enabled.
   */
  public $enabled = true;

  /** @var string
   * The block title. It's used to display the block when you click the Block Inserter in Gutenberg.
   */
  public $title;

  /** @var string
   * The block ID. Must be unique.
   */
  public $id;

  /** @var string
   * The block description. Optional.
   */
  public $description;

  /** @var string
   * The block icon. Can be any of WordPress' Dashicons (without the prefix dashicons-), or a custom svg element (string) or FontAwesome 5 free icons (added in version 1.2.0).
   * docs: [https://docs.metabox.io/extensions/mb-blocks/#icon](https://docs.metabox.io/extensions/mb-blocks/#icon)
   * block icons: [https://developer.wordpress.org/resource/dashicons/#block-default](https://developer.wordpress.org/resource/dashicons/#block-default)
   */
  public $icon = "block-default";

  /** @var string
   * Specify the block category, which is used to help users browse and discover them. Available values: `text, media, design, widgets, theme (default), embed`. If a theme or a plugin registers a custom category, you can use it, too.
   */
  public $category = "theme";

  /** @var string
   * Where to show the block settings. If set to `side`, the block settings are displayed on the right sidebar when you select the block. If omitted (default), the block settings are displayed when you click the Edit icon in the block toolbar.
   */
  public $context = "side";

  /** @var string
   * The default mode of the block: `edit` to make it shows the edit fields when loaded, `preview` (default) to show the rendered HTML when loaded.
   */
  public $mode;

  /**
   * Sets the storage for the block fields. Default, it's `attributes`, which means saving block fields in the attributes.
   *
   * If you want to save the block fields into custom fields, set it to `post_meta`. Saving block fields in the custom fields make the block acts as a wrapper of custom fields. In this case, to prevent bugs, you should set `multiple` to `false` to prevent inserting the same block multiple times (see supports parameter above).
   */
  public $storage_type;

  /** @var string */
  public $render_template;

  /** @var callable */
  public $render_callback;

  /** @var MbComponentSupports */
  public $supports;

  /** @var array */
  public $fields = [];

  /**
   * @var array
   * This attribute allows you to set preview data for the block, which will show when you click on the plus icon (+) on the toolbar:
   * ```['title' => 'This is title']```
   */
  public $preview = [];
}

class MbComponentSupports {
  /** @var array
   * Add supports for the block alignment. Note that your theme must add styling for the Gutenberg alignment.
   * false or ```["left", "center", "right", "wide", "full"]```
   */
  public $align = false;

  /** @var bool
   * This property adds a field to define a custom CSS class name for the block's wrapper. It's useful when you want to add custom styling for a specific instance of the block.
   */
  public $customClassName = true;

  /** @var bool
   * Anchors let you link directly to a specific block on a page. This property adds a field to define an id for the block.
   */
  public $anchor = true;

  /** @var bool
   * If you want to have a block that can be inserted into each post one time only (like a hero area block), then set this parameter to false. A non-multiple block's icon is automatically dimmed (unclickable) to prevent multiple instances.
   */
  public $multiple = true;

  /** @var bool
   * A block may want to disable the ability to be converted into a reusable block. By default, all blocks can be converted to reusable blocks. If supports reusable is set to false, the option to convert the block into a reusable block will not appear.
   */
  public $reusable = true;
}

abstract class MBComponentBase {
  protected $options;
  protected $supports;

  public function __construct($class_name = "") {
    $this->options = new MbComponentOptions();
    $this->supports = new MbComponentSupports();
  }

  /**
   * @param MbComponentOptions $options
   * @param MbComponentSupports $supports
   * @param array $fields
   */
  abstract protected function init_options($options);

  public function get_mb() {
    $this->init_options($this->options);
    $this->merge_additional_mb();
    if (!$this->options->enabled) {
      return false;
    }
    $def = (array) $this->options;
    $def["type"] = "block";
    $def["supports"] = (array) $this->options->supports;
    return $def;
  }

  private function merge_additional_mb() {
    $id = $this->options->id;

    $additional_mb = apply_filters("component_add_mb_{$id}", []) ?: [];

    if (count($additional_mb)) {
      $this->options->fields = $this->merge_mb_fields(
        $this->options->fields,
        $additional_mb,
      );
    }

    $common_mb = MbComponentBlocks::instance()->get_common_fields();
    if (count($common_mb)) {
      $this->options->fields = array_merge($this->options->fields, $common_mb);
    }
  }

  private function merge_mb_fields(&$orig, $add) {
    foreach ($add as $mb) {
      $id = $mb["id"] ?? false;
      if ($id) {
        $idx = $this->find_mb_index_by_id($orig, $id);
        if ($idx) {
          $this->merge_mb($orig[$idx], $mb);
        } else {
          $this->push_mb_to_postition($orig, $mb);
        }
      }
    }
    return $orig;
  }

  private function merge_mb(&$orig, $new) {
    foreach ($new as $key => $value) {
      if ($key == "fields") {
        $orig[$key] = $this->merge_mb_fields($orig["fields"], $value);
      } else {
        $orig[$key] = $value;
      }
    }
  }

  private function push_mb_to_postition(&$orig, $new) {
    if (isset($new["after_id"])) {
      $after_id = $new["after_id"] ?? null;
      $position = $this->find_mb_index_by_id($orig, $after_id);

      $position++;
      while ($orig[$position]["after_id"] ?? null == $after_id) {
        $position++;
      }
      array_splice($orig, $position, 0, [$new]);
    } else {
      array_push($orig, $new);
    }
  }

  private function find_mb_index_by_id($mbs, $id) {
    for ($i = 0; $i < count($mbs); $i++) {
      if (($mbs[$i]["id"] ?? "") == $id) {
        return $i;
      }
    }
    return null;
  }
}
