// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:typed_data';

import 'package:ui/ui.dart' as ui;

import '../dom.dart';
import '../util.dart';
import '../vector_math.dart';
import 'surface.dart';

/// A surface that transforms its children using CSS transform.
class PersistedTransform extends PersistedContainerSurface
    implements ui.TransformEngineLayer {
  PersistedTransform(PersistedTransform? super.oldLayer, this._matrixStorage);

  /// The storage representing the transform of this surface.
  final Float32List _matrixStorage;

  /// The matrix representing the transform of this surface.
  Matrix4 get matrix4 => _matrix4 ??= Matrix4.fromFloat32List(_matrixStorage);
  Matrix4? _matrix4;

  @override
  void recomputeTransformAndClip() {
    transform = parent!.transform!.multiplied(matrix4);
    projectedClip = null;
  }

  /// Cached inverse of transform on this node. Unlike [transform], this
  /// Matrix only contains local transform (not chain multiplied since root).
  Matrix4? _localTransformInverse;

  @override
  Matrix4? get localTransformInverse {
    _localTransformInverse ??= Matrix4.tryInvert(matrix4);
    return _localTransformInverse;
  }

  @override
  DomElement createElement() {
    final DomElement element = domDocument.createElement('flt-transform');
    setElementStyle(element, 'position', 'absolute');
    setElementStyle(element, 'transform-origin', '0 0 0');
    return element;
  }

  @override
  void apply() {
    rootElement!.style.transform = float64ListToCssTransform(_matrixStorage);
  }

  @override
  void update(PersistedTransform oldSurface) {
    super.update(oldSurface);

    if (identical(oldSurface._matrixStorage, _matrixStorage)) {
      // The matrix storage is identical, so we can copy the matrices from the
      // old surface to avoid recomputing them.
      _matrix4 = oldSurface._matrix4;
      _localTransformInverse = oldSurface._localTransformInverse;
      return;
    }

    bool matrixChanged = false;
    for (int i = 0; i < _matrixStorage.length; i++) {
      if (_matrixStorage[i] != oldSurface._matrixStorage[i]) {
        matrixChanged = true;
        break;
      }
    }

    if (matrixChanged) {
      apply();
    } else {
      // The matrix storage hasn't changed, so we can copy the matrices from the
      // old surface to avoid recomputing them.
      _matrix4 = oldSurface._matrix4;
      _localTransformInverse = oldSurface._localTransformInverse;
    }
  }
}
