/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

'use strict';

// Mock of the Native Hooks

const roots = new Map();
const allocatedTags = new Set();

function dumpSubtree(info, indent) {
  let out = '';
  out += ' '.repeat(indent) + info.viewName + ' ' + JSON.stringify(info.props);
  // eslint-disable-next-line no-for-of-loops/no-for-of-loops
  for (const child of info.children) {
    out += '\n' + dumpSubtree(child, indent + 2);
  }
  return out;
}

const RCTFabricUIManager = {
  __dumpChildSetForJestTestsOnly: function (childSet) {
    const result = [];
    // eslint-disable-next-line no-for-of-loops/no-for-of-loops
    for (const child of childSet) {
      result.push(dumpSubtree(child, 0));
    }
    return result.join('\n');
  },
  __dumpHierarchyForJestTestsOnly: function () {
    const result = [];
    // eslint-disable-next-line no-for-of-loops/no-for-of-loops
    for (const [rootTag, childSet] of roots) {
      result.push(rootTag);
      // eslint-disable-next-line no-for-of-loops/no-for-of-loops
      for (const child of childSet) {
        result.push(dumpSubtree(child, 1));
      }
    }
    return result.join('\n');
  },
  createNode: jest.fn(
    function createNode(reactTag, viewName, rootTag, props, eventTarget) {
      if (allocatedTags.has(reactTag)) {
        throw new Error(`Created two native views with tag ${reactTag}`);
      }

      allocatedTags.add(reactTag);
      return {
        reactTag: reactTag,
        viewName: viewName,
        props: props,
        children: [],
      };
    },
  ),
  cloneNode: jest.fn(function cloneNode(node) {
    return {
      reactTag: node.reactTag,
      viewName: node.viewName,
      props: node.props,
      children: node.children,
    };
  }),
  cloneNodeWithNewChildren: jest.fn(
    function cloneNodeWithNewChildren(node, children) {
      return {
        reactTag: node.reactTag,
        viewName: node.viewName,
        props: node.props,
        children: children ?? [],
      };
    },
  ),
  cloneNodeWithNewProps: jest.fn(
    function cloneNodeWithNewProps(node, newPropsDiff) {
      return {
        reactTag: node.reactTag,
        viewName: node.viewName,
        props: {...node.props, ...newPropsDiff},
        children: node.children,
      };
    },
  ),
  cloneNodeWithNewChildrenAndProps: jest.fn(
    function cloneNodeWithNewChildrenAndProps(node, newPropsDiff) {
      let children = [];
      if (arguments.length === 3) {
        children = newPropsDiff;
        newPropsDiff = arguments[2];
      }

      return {
        reactTag: node.reactTag,
        viewName: node.viewName,
        props: {...node.props, ...newPropsDiff},
        children,
      };
    },
  ),
  appendChild: jest.fn(function appendChild(parentNode, childNode) {
    parentNode.children.push(childNode);
  }),

  createChildSet: jest.fn(function createChildSet() {
    return [];
  }),

  appendChildToSet: jest.fn(function appendChildToSet(childSet, childNode) {
    childSet.push(childNode);
  }),

  completeRoot: jest.fn(function completeRoot(rootTag, newChildSet) {
    roots.set(rootTag, newChildSet);
  }),

  dispatchCommand: jest.fn(),

  setNativeProps: jest.fn(),

  sendAccessibilityEvent: jest.fn(),

  registerEventHandler: jest.fn(function registerEventHandler(callback) {}),

  measure: jest.fn(function measure(node, callback) {
    if (typeof node !== 'object') {
      throw new Error(
        `Expected node to be an object, was passed "${typeof node}"`,
      );
    }

    if (typeof node.viewName !== 'string') {
      throw new Error('Expected node to be a host node.');
    }

    callback(10, 10, 100, 100, 0, 0);
  }),
  measureInWindow: jest.fn(function measureInWindow(node, callback) {
    if (typeof node !== 'object') {
      throw new Error(
        `Expected node to be an object, was passed "${typeof node}"`,
      );
    }

    if (typeof node.viewName !== 'string') {
      throw new Error('Expected node to be a host node.');
    }

    callback(10, 10, 100, 100);
  }),
  getBoundingClientRect: jest.fn(function getBoundingClientRect(node) {
    if (typeof node !== 'object') {
      throw new Error(
        `Expected node to be an object, was passed "${typeof node}"`,
      );
    }

    if (typeof node.viewName !== 'string') {
      throw new Error('Expected node to be a host node.');
    }

    return [10, 10, 100, 100];
  }),
  measureLayout: jest.fn(
    function measureLayout(node, relativeNode, fail, success) {
      if (typeof node !== 'object') {
        throw new Error(
          `Expected node to be an object, was passed "${typeof node}"`,
        );
      }

      if (typeof node.viewName !== 'string') {
        throw new Error('Expected node to be a host node.');
      }

      if (typeof relativeNode !== 'object') {
        throw new Error(
          `Expected relative node to be an object, was passed "${typeof relativeNode}"`,
        );
      }

      if (typeof relativeNode.viewName !== 'string') {
        throw new Error('Expected relative node to be a host node.');
      }

      success(1, 1, 100, 100);
    },
  ),
  setIsJSResponder: jest.fn(),
};

global.nativeFabricUIManager = RCTFabricUIManager;

// DOMRect isn't provided by jsdom, but it's used by `ReactFabricHostComponent`.
// This is a basic implementation for testing.
global.DOMRect = class DOMRect {
  constructor(x, y, width, height) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
  }

  toJSON() {
    const {x, y, width, height} = this;
    return {x, y, width, height};
  }
};