// luma.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import {Matrix4, Vector3} from '@math.gl/core';
import {log} from '@luma.gl/core';
import {ScenegraphNode, ScenegraphNodeProps} from './scenegraph-node';

export type GroupNodeProps = ScenegraphNodeProps & {
  children?: ScenegraphNode[];
};

export class GroupNode extends ScenegraphNode {
  children: ScenegraphNode[];

  constructor(children: ScenegraphNode[]);
  constructor(props?: GroupNodeProps);

  constructor(props: ScenegraphNode[] | GroupNodeProps = {}) {
    props = Array.isArray(props) ? {children: props} : props;
    const {children = []} = props;
    log.assert(
      children.every(child => child instanceof ScenegraphNode),
      'every child must an instance of ScenegraphNode'
    );
    super(props);
    this.children = children;
  }

  override getBounds(): [number[], number[]] | null {
    const result: [number[], number[]] = [
      [Infinity, Infinity, Infinity],
      [-Infinity, -Infinity, -Infinity]
    ];

    this.traverse((node, {worldMatrix}) => {
      const bounds = node.getBounds();
      if (!bounds) {
        return;
      }
      const [min, max] = bounds;
      const center = new Vector3(min).add(max).divide([2, 2, 2]);
      worldMatrix.transformAsPoint(center, center);
      const halfSize = new Vector3(max).subtract(min).divide([2, 2, 2]);
      worldMatrix.transformAsVector(halfSize, halfSize);

      for (let v = 0; v < 8; v++) {
        // Test all 8 corners of the box
        const position = new Vector3(v & 0b001 ? -1 : 1, v & 0b010 ? -1 : 1, v & 0b100 ? -1 : 1)
          .multiply(halfSize)
          .add(center);

        for (let i = 0; i < 3; i++) {
          result[0][i] = Math.min(result[0][i], position[i]);
          result[1][i] = Math.max(result[1][i], position[i]);
        }
      }
    });
    if (!Number.isFinite(result[0][0])) {
      return null;
    }
    return result;
  }

  override destroy(): void {
    this.children.forEach(child => child.destroy());
    this.removeAll();
    super.destroy();
  }

  // Unpacks arrays and nested arrays of children
  add(...children: (ScenegraphNode | ScenegraphNode[])[]): this {
    for (const child of children) {
      if (Array.isArray(child)) {
        this.add(...child);
      } else {
        this.children.push(child);
      }
    }
    return this;
  }

  remove(child: ScenegraphNode): this {
    const children = this.children;
    const indexOf = children.indexOf(child);
    if (indexOf > -1) {
      children.splice(indexOf, 1);
    }
    return this;
  }

  removeAll(): this {
    this.children = [];
    return this;
  }

  traverse(
    visitor: (node: ScenegraphNode, context: {worldMatrix: Matrix4}) => void,
    {worldMatrix = new Matrix4()} = {}
  ) {
    const modelMatrix = new Matrix4(worldMatrix).multiplyRight(this.matrix);

    for (const child of this.children) {
      if (child instanceof GroupNode) {
        child.traverse(visitor, {worldMatrix: modelMatrix});
      } else {
        visitor(child, {worldMatrix: modelMatrix});
      }
    }
  }
}
