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

import {
  sharedTestSetup,
  sharedTestTeardown,
} from './test_helpers/setup_teardown.js';
import {
  defineRowBlock,
  defineRowToStackBlock,
  defineStackBlock,
} from './test_helpers/block_definitions.js';

suite('Insertion marker manager', function () {
  setup(function () {
    sharedTestSetup.call(this);
    defineRowBlock();
    defineStackBlock();
    defineRowToStackBlock();
    this.workspace = Blockly.inject('blocklyDiv');
  });
  teardown(function () {
    sharedTestTeardown.call(this);
  });

  suite('Creating markers', function () {
    function createBlocksAndManager(workspace, state) {
      Blockly.serialization.workspaces.load(state, workspace);
      const block = workspace.getBlockById('first');
      const manager = new Blockly.InsertionMarkerManager(block);
      return manager;
    }

    test('One stack block creates one marker', function () {
      const state = {
        'blocks': {
          'blocks': [
            {
              'type': 'stack_block',
              'id': 'first',
            },
          ],
        },
      };
      const manager = createBlocksAndManager(this.workspace, state);
      const markers = manager.getInsertionMarkers();
      chai.assert.equal(markers.length, 1);
    });

    test('Two stack blocks create two markers', function () {
      const state = {
        'blocks': {
          'blocks': [
            {
              'type': 'stack_block',
              'id': 'first',
              'next': {
                'block': {
                  'type': 'stack_block',
                  'id': 'second',
                },
              },
            },
          ],
        },
      };
      const manager = createBlocksAndManager(this.workspace, state);
      const markers = manager.getInsertionMarkers();
      chai.assert.equal(markers.length, 2);
    });

    test('Three stack blocks create two markers', function () {
      const state = {
        'blocks': {
          'blocks': [
            {
              'type': 'stack_block',
              'id': 'first',
              'next': {
                'block': {
                  'type': 'stack_block',
                  'id': 'second',
                  'next': {
                    'block': {
                      'type': 'stack_block',
                      'id': 'third',
                    },
                  },
                },
              },
            },
          ],
        },
      };
      const manager = createBlocksAndManager(this.workspace, state);
      const markers = manager.getInsertionMarkers();
      chai.assert.equal(markers.length, 2);
    });

    test('One value block creates one marker', function () {
      const state = {
        'blocks': {
          'blocks': [
            {
              'type': 'row_block',
              'id': 'first',
            },
          ],
        },
      };
      const manager = createBlocksAndManager(this.workspace, state);
      const markers = manager.getInsertionMarkers();
      chai.assert.equal(markers.length, 1);
    });

    test('Two value blocks create one marker', function () {
      const state = {
        'blocks': {
          'blocks': [
            {
              'type': 'row_block',
              'id': 'first',
              'inputs': {
                'INPUT': {
                  'block': {
                    'type': 'row_block',
                    'id': 'second',
                  },
                },
              },
            },
          ],
        },
      };
      const manager = createBlocksAndManager(this.workspace, state);
      const markers = manager.getInsertionMarkers();
      chai.assert.equal(markers.length, 1);
    });

    test('One row to stack block creates one marker', function () {
      const state = {
        'blocks': {
          'blocks': [
            {
              'type': 'row_to_stack_block',
              'id': 'first',
            },
          ],
        },
      };
      const manager = createBlocksAndManager(this.workspace, state);
      const markers = manager.getInsertionMarkers();
      chai.assert.equal(markers.length, 1);
    });

    test('Row to stack block with child creates two markers', function () {
      const state = {
        'blocks': {
          'blocks': [
            {
              'type': 'row_to_stack_block',
              'id': 'first',
              'next': {
                'block': {
                  'type': 'stack_block',
                  'id': 'second',
                },
              },
            },
          ],
        },
      };
      const manager = createBlocksAndManager(this.workspace, state);
      const markers = manager.getInsertionMarkers();
      chai.assert.equal(markers.length, 2);
    });

    suite('children being set as insertion markers', function () {
      setup(function () {
        Blockly.Blocks['shadows_in_init'] = {
          init: function () {
            this.appendValueInput('test').connection.setShadowState({
              'type': 'math_number',
            });
            this.setPreviousStatement(true);
          },
        };

        Blockly.Blocks['shadows_in_load'] = {
          init: function () {
            this.appendValueInput('test');
            this.setPreviousStatement(true);
          },

          loadExtraState: function () {
            this.getInput('test').connection.setShadowState({
              'type': 'math_number',
            });
          },

          saveExtraState: function () {
            return true;
          },
        };
      });

      teardown(function () {
        delete Blockly.Blocks['shadows_in_init'];
        delete Blockly.Blocks['shadows_in_load'];
      });

      test('Shadows added in init are set as insertion markers', function () {
        const state = {
          'blocks': {
            'blocks': [
              {
                'id': 'first',
                'type': 'shadows_in_init',
              },
            ],
          },
        };
        const manager = createBlocksAndManager(this.workspace, state);
        const markers = manager.getInsertionMarkers();
        chai.assert.isTrue(
          markers[0].getChildren()[0].isInsertionMarker(),
          'Expected the shadow block to be an insertion maker',
        );
      });

      test('Shadows added in `loadExtraState` are set as insertion markers', function () {
        const state = {
          'blocks': {
            'blocks': [
              {
                'id': 'first',
                'type': 'shadows_in_load',
              },
            ],
          },
        };
        const manager = createBlocksAndManager(this.workspace, state);
        const markers = manager.getInsertionMarkers();
        chai.assert.isTrue(
          markers[0].getChildren()[0].isInsertionMarker(),
          'Expected the shadow block to be an insertion maker',
        );
      });
    });
  });

  suite('Would delete block', function () {
    setup(function () {
      const state = {
        'blocks': {
          'blocks': [
            {
              'type': 'stack_block',
              'id': 'first',
            },
          ],
        },
      };
      Blockly.serialization.workspaces.load(state, this.workspace);
      this.block = this.workspace.getBlockById('first');
      this.manager = new Blockly.InsertionMarkerManager(this.block);

      const componentManager = this.workspace.getComponentManager();
      this.stub = sinon.stub(componentManager, 'hasCapability');
      this.dxy = new Blockly.utils.Coordinate(0, 0);
    });

    test('Over delete area and accepted would delete', function () {
      this.stub
        .withArgs(
          'fakeDragTarget',
          Blockly.ComponentManager.Capability.DELETE_AREA,
        )
        .returns(true);
      const fakeDragTarget = {
        wouldDelete: sinon.fake.returns(true),
        id: 'fakeDragTarget',
      };
      this.manager.update(this.dxy, fakeDragTarget);
      chai.assert.isTrue(this.manager.wouldDeleteBlock);
    });

    test('Over delete area and rejected would not delete', function () {
      this.stub
        .withArgs(
          'fakeDragTarget',
          Blockly.ComponentManager.Capability.DELETE_AREA,
        )
        .returns(true);
      const fakeDragTarget = {
        wouldDelete: sinon.fake.returns(false),
        id: 'fakeDragTarget',
      };
      this.manager.update(this.dxy, fakeDragTarget);
      chai.assert.isFalse(this.manager.wouldDeleteBlock);
    });

    test('Drag target is not a delete area would not delete', function () {
      this.stub
        .withArgs(
          'fakeDragTarget',
          Blockly.ComponentManager.Capability.DELETE_AREA,
        )
        .returns(false);
      const fakeDragTarget = {
        wouldDelete: sinon.fake.returns(false),
        id: 'fakeDragTarget',
      };
      this.manager.update(this.dxy, fakeDragTarget);
      chai.assert.isFalse(this.manager.wouldDeleteBlock);
    });

    test('Not over drag target would not delete', function () {
      this.manager.update(this.dxy, null);
      chai.assert.isFalse(this.manager.wouldDeleteBlock);
    });
  });

  suite('Would connect stack blocks', function () {
    setup(function () {
      const state = {
        'blocks': {
          'blocks': [
            {
              'type': 'stack_block',
              'id': 'first',
              'x': 0,
              'y': 0,
            },
            {
              'type': 'stack_block',
              'id': 'other',
              'x': 200,
              'y': 200,
            },
          ],
        },
      };
      Blockly.serialization.workspaces.load(state, this.workspace);
      this.block = this.workspace.getBlockById('first');
      this.block.setDragging(true);
      this.manager = new Blockly.InsertionMarkerManager(this.block);
    });

    test('No other blocks nearby would not connect', function () {
      this.manager.update(new Blockly.utils.Coordinate(0, 0), null);
      chai.assert.isFalse(this.manager.wouldConnectBlock());
    });

    test('Near other block and above would connect before', function () {
      this.manager.update(new Blockly.utils.Coordinate(200, 190), null);
      chai.assert.isTrue(this.manager.wouldConnectBlock());
      const markers = this.manager.getInsertionMarkers();
      chai.assert.equal(markers.length, 1);
      const marker = markers[0];
      chai.assert.isTrue(marker.nextConnection.isConnected());
    });

    test('Near other block and below would connect after', function () {
      this.manager.update(new Blockly.utils.Coordinate(200, 210), null);
      chai.assert.isTrue(this.manager.wouldConnectBlock());
      const markers = this.manager.getInsertionMarkers();
      chai.assert.equal(markers.length, 1);
      const marker = markers[0];
      chai.assert.isTrue(marker.previousConnection.isConnected());
    });

    test('Near other block and left would connect', function () {
      this.manager.update(new Blockly.utils.Coordinate(190, 200), null);
      chai.assert.isTrue(this.manager.wouldConnectBlock());
    });

    test('Near other block and right would connect', function () {
      this.manager.update(new Blockly.utils.Coordinate(210, 200), null);
      chai.assert.isTrue(this.manager.wouldConnectBlock());
    });
  });

  suite('Would connect row blocks', function () {
    setup(function () {
      const state = {
        'blocks': {
          'blocks': [
            {
              'type': 'row_block',
              'id': 'first',
              'x': 0,
              'y': 0,
            },
            {
              'type': 'row_block',
              'id': 'other',
              'x': 200,
              'y': 200,
            },
          ],
        },
      };
      Blockly.serialization.workspaces.load(state, this.workspace);
      this.block = this.workspace.getBlockById('first');
      this.block.setDragging(true);
      this.manager = new Blockly.InsertionMarkerManager(this.block);
    });

    test('No other blocks nearby would not connect', function () {
      this.manager.update(new Blockly.utils.Coordinate(0, 0), null);
      chai.assert.isFalse(this.manager.wouldConnectBlock());
    });

    test('Near other block and above would connect', function () {
      this.manager.update(new Blockly.utils.Coordinate(200, 190), null);
      chai.assert.isTrue(this.manager.wouldConnectBlock());
    });

    test('Near other block and below would connect', function () {
      this.manager.update(new Blockly.utils.Coordinate(200, 210), null);
      chai.assert.isTrue(this.manager.wouldConnectBlock());
    });

    test('Near other block and left would connect before', function () {
      this.manager.update(new Blockly.utils.Coordinate(190, 200), null);
      chai.assert.isTrue(this.manager.wouldConnectBlock());
      const markers = this.manager.getInsertionMarkers();
      chai.assert.isTrue(markers[0].getInput('INPUT').connection.isConnected());
    });

    test('Near other block and right would connect after', function () {
      this.manager.update(new Blockly.utils.Coordinate(210, 200), null);
      chai.assert.isTrue(this.manager.wouldConnectBlock());
      const markers = this.manager.getInsertionMarkers();
      chai.assert.isTrue(markers[0].outputConnection.isConnected());
    });
  });
});
