// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

const {session, contextGroup, Protocol} =
    InspectorTest.start('RemoteObject.CustomPreview');

(async function test() {
  contextGroup.addScript(`
    var a = {name: 'a'};
    var b = {name: 'b'};
    var c = {name: 'c'};
    a.formattableBy1 = true;
    b.formattableBy2 = true;
    c.formattableBy1 = true;
    c.formattableBy2 = true;
    var formatter1 = {
      header: (x) => x.formattableBy1 ? ['span', {}, 'Header formatted by 1 ', x.name] : null,
      hasBody: () => true,
      body: (x) => ['span', {}, 'Body formatted by 1 ', x.name, ['object', {object: {}}]]
    };
    var formatter2 = {
      header: (x) => x.formattableBy2 ? ['span', {}, 'Header formatted by 2 ', x.name] : null,
      hasBody: (x) => true,
      body: (x) => ['span', {}, 'Body formatted by 2 ', x.name]
    };
    var configTest = {};
    var formatterWithConfig1 = {
      header: function(x, config) {
        if (x !== configTest || config)
          return null;
        return ['span', {}, 'Formatter with config ', ['object', {'object': x, 'config': {'info': 'additional info'}}]];
      },
      hasBody: (x) => false,
      body: (x) => { throw 'Unreachable'; }
    }
    var formatterWithConfig2 = {
      header: function(x, config) {
        if (x !== configTest || !config)
          return null;
        return ['span', {}, 'Header ', 'info: ', config.info];
      },
      hasBody: (x, config) => config && config.info,
      body: (x, config) => ['span', {}, 'body', 'info: ', config.info]
    }
    this.devtoolsFormatters = [formatter1, formatter2, formatterWithConfig1, formatterWithConfig2];
  `);

  Protocol.Runtime.enable();
  Protocol.Runtime.setCustomObjectFormatterEnabled({enabled: true});

  Protocol.Runtime.onConsoleAPICalled(m => InspectorTest.logMessage(m));
  InspectorTest.log('Dump custom previews..');
  await dumpCustomPreviewForEvaluate(await Protocol.Runtime.evaluate({expression: 'a'}));
  await dumpCustomPreviewForEvaluate(await Protocol.Runtime.evaluate({expression: 'b'}));
  await dumpCustomPreviewForEvaluate(await Protocol.Runtime.evaluate({expression: 'c'}));
  await dumpCustomPreviewForEvaluate(await Protocol.Runtime.evaluate({expression: 'configTest'}));
  InspectorTest.log('Change formatters order and dump again..');
  await Protocol.Runtime.evaluate({
    expression: 'this.devtoolsFormatters = [formatter2, formatter1, formatterWithConfig1, formatterWithConfig2]'
  });
  await dumpCustomPreviewForEvaluate(await Protocol.Runtime.evaluate({expression: 'a'}));
  await dumpCustomPreviewForEvaluate(await Protocol.Runtime.evaluate({expression: 'b'}));
  await dumpCustomPreviewForEvaluate(await Protocol.Runtime.evaluate({expression: 'c'}));
  await dumpCustomPreviewForEvaluate(await Protocol.Runtime.evaluate({expression: 'configTest'}));

  InspectorTest.log('Test Runtime.getProperties');
  const {result:{result:{objectId}}} = await Protocol.Runtime.evaluate({expression: '({a})'});
  const {result:{result}} = await Protocol.Runtime.getProperties({
    objectId, ownProperties: true, generatePreview: true});
  await dumpCustomPreview(result.find(value => value.name === 'a').value);

  InspectorTest.log('Try to break custom preview..');
  await Protocol.Runtime.evaluate({
    expression: `Object.defineProperty(this, 'devtoolsFormatters', {
      get: () => { throw 1; },
      configurable: true
    })`
  });
  Protocol.Runtime.evaluate({ expression: '({})', generatePreview: true });
  InspectorTest.logMessage(await Protocol.Runtime.onceConsoleAPICalled());

  await Protocol.Runtime.evaluate({
    expression: `Object.defineProperty(this, 'devtoolsFormatters', {
      get: () => {
        const arr = [1];
        Object.defineProperty(arr, 0, { get: () => { throw 2; }});
        return arr;
      },
      configurable: true
    })`
  });
  Protocol.Runtime.evaluate({ expression: '({})', generatePreview: true });
  InspectorTest.logMessage(await Protocol.Runtime.onceConsoleAPICalled());

  await Protocol.Runtime.evaluate({
    expression: `Object.defineProperty(this, 'devtoolsFormatters', {
      get: () => [{get header() { throw 3; }}],
      configurable: true
    })`
  });
  Protocol.Runtime.evaluate({ expression: '({})', generatePreview: true });
  InspectorTest.logMessage(await Protocol.Runtime.onceConsoleAPICalled());

  await Protocol.Runtime.evaluate({
    expression: `Object.defineProperty(this, 'devtoolsFormatters', {
      get: () => [{header: () => { throw 4; }}],
      configurable: true
    })`
  });
  Protocol.Runtime.evaluate({ expression: '({})', generatePreview: true });
  InspectorTest.logMessage(await Protocol.Runtime.onceConsoleAPICalled());

  InspectorTest.completeTest();
})()

function dumpCustomPreviewForEvaluate(result) {
  return dumpCustomPreview(result.result.result);
}

async function dumpCustomPreview(result) {
  const { objectId, customPreview } = result;
  InspectorTest.logMessage(customPreview);
  if (customPreview.bodyGetterId) {
    const body = await Protocol.Runtime.callFunctionOn({
      objectId,
      functionDeclaration: 'function(bodyGetter) { return bodyGetter.call(this); }',
      arguments: [ { objectId: customPreview.bodyGetterId } ],
      returnByValue: true
    });
    InspectorTest.logMessage(body);
  }
}