/**
 * @license
 * Copyright 2018 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Class for a block delete event.
 */
'use strict';

/**
 * Class for a block delete event.
 * @class
 */
goog.module('Blockly.Events.BlockDelete');

const Xml = goog.require('Blockly.Xml');
const blocks = goog.require('Blockly.serialization.blocks');
const eventUtils = goog.require('Blockly.Events.utils');
const object = goog.require('Blockly.utils.object');
const registry = goog.require('Blockly.registry');
const {BlockBase} = goog.require('Blockly.Events.BlockBase');
/* eslint-disable-next-line no-unused-vars */
const {Block} = goog.requireType('Blockly.Block');


/**
 * Class for a block deletion event.
 * @param {!Block=} opt_block The deleted block.  Undefined for a blank
 *     event.
 * @extends {BlockBase}
 * @constructor
 * @alias Blockly.Events.BlockDelete
 */
const BlockDelete = function(opt_block) {
  BlockDelete.superClass_.constructor.call(this, opt_block);
  if (!opt_block) {
    return;  // Blank event to be populated by fromJson.
  }
  if (opt_block.getParent()) {
    throw Error('Connected blocks cannot be deleted.');
  }
  if (opt_block.isShadow()) {
    // Respawning shadow blocks is handled via disconnection.
    this.recordUndo = false;
  }

  this.oldXml = Xml.blockToDomWithXY(opt_block);
  this.ids = eventUtils.getDescendantIds(opt_block);

  /**
   * Was the block that was just deleted a shadow?
   * @type {boolean}
   */
  this.wasShadow = opt_block.isShadow();

  /**
   * JSON representation of the block that was just deleted.
   * @type {!blocks.State}
   */
  this.oldJson = /** @type {!blocks.State} */ (
      blocks.save(opt_block, {addCoordinates: true}));
};
object.inherits(BlockDelete, BlockBase);

/**
 * Type of this event.
 * @type {string}
 */
BlockDelete.prototype.type = eventUtils.BLOCK_DELETE;

/**
 * Encode the event as JSON.
 * @return {!Object} JSON representation.
 */
BlockDelete.prototype.toJson = function() {
  const json = BlockDelete.superClass_.toJson.call(this);
  json['oldXml'] = Xml.domToText(this.oldXml);
  json['ids'] = this.ids;
  json['wasShadow'] = this.wasShadow;
  json['oldJson'] = this.oldJson;
  if (!this.recordUndo) {
    json['recordUndo'] = this.recordUndo;
  }
  return json;
};

/**
 * Decode the JSON event.
 * @param {!Object} json JSON representation.
 */
BlockDelete.prototype.fromJson = function(json) {
  BlockDelete.superClass_.fromJson.call(this, json);
  this.oldXml = Xml.textToDom(json['oldXml']);
  this.ids = json['ids'];
  this.wasShadow =
      json['wasShadow'] || this.oldXml.tagName.toLowerCase() === 'shadow';
  this.oldJson = /** @type {!blocks.State} */ (json['oldJson']);
  if (json['recordUndo'] !== undefined) {
    this.recordUndo = json['recordUndo'];
  }
};

/**
 * Run a deletion event.
 * @param {boolean} forward True if run forward, false if run backward (undo).
 */
BlockDelete.prototype.run = function(forward) {
  const workspace = this.getEventWorkspace_();
  if (forward) {
    for (let i = 0; i < this.ids.length; i++) {
      const id = this.ids[i];
      const block = workspace.getBlockById(id);
      if (block) {
        block.dispose(false);
      } else if (id === this.blockId) {
        // Only complain about root-level block.
        console.warn('Can\'t delete non-existent block: ' + id);
      }
    }
  } else {
    blocks.append(this.oldJson, workspace);
  }
};

registry.register(registry.Type.EVENT, eventUtils.DELETE, BlockDelete);

exports.BlockDelete = BlockDelete;
