import { Buffer } from '@luma.gl/core';
import { GL } from '@luma.gl/constants';
import { withGLParameters, setGLParameters } from '@luma.gl/webgl';
import { COORDINATE_SYSTEM } from './constants';
import AttributeManager from './attribute/attribute-manager';
import UniformTransitionManager from './uniform-transition-manager';
import { diffProps, validateProps } from '../lifecycle/props';
import { LIFECYCLE } from '../lifecycle/constants';
import { count } from '../utils/count';
import log from '../utils/log';
import debug from '../debug';
import assert from '../utils/assert';
import memoize from '../utils/memoize';
import { mergeShaders } from '../utils/shader';
import { projectPosition, getWorldPosition } from '../shaderlib/project/project-functions';
import typedArrayManager from '../utils/typed-array-manager';
import Component from '../lifecycle/component';
import LayerState from './layer-state';
import { worldToPixels } from '@math.gl/web-mercator';
import { load } from '@loaders.gl/core';
const TRACE_CHANGE_FLAG = 'layer.changeFlag';
const TRACE_INITIALIZE = 'layer.initialize';
const TRACE_UPDATE = 'layer.update';
const TRACE_FINALIZE = 'layer.finalize';
const TRACE_MATCHED = 'layer.matched';
const MAX_PICKING_COLOR_CACHE_SIZE = 2 ** 24 - 1;
const EMPTY_ARRAY = Object.freeze([]);
const areViewportsEqual = memoize(({
  oldViewport,
  viewport
}) => {
  return oldViewport.equals(viewport);
});
let pickingColorCache = new Uint8ClampedArray(0);
const defaultProps = {
  data: {
    type: 'data',
    value: EMPTY_ARRAY,
    async: true
  },
  dataComparator: {
    type: 'function',
    value: null,
    optional: true
  },
  _dataDiff: {
    type: 'function',
    value: data => data && data.__diff,
    optional: true
  },
  dataTransform: {
    type: 'function',
    value: null,
    optional: true
  },
  onDataLoad: {
    type: 'function',
    value: null,
    optional: true
  },
  onError: {
    type: 'function',
    value: null,
    optional: true
  },
  fetch: {
    type: 'function',
    value: (url, {
      propName,
      layer,
      loaders,
      loadOptions,
      signal
    }) => {
      const {
        resourceManager
      } = layer.context;
      loadOptions = loadOptions || layer.getLoadOptions();
      loaders = loaders || layer.props.loaders;

      if (signal) {
        var _loadOptions;

        loadOptions = { ...loadOptions,
          fetch: { ...((_loadOptions = loadOptions) === null || _loadOptions === void 0 ? void 0 : _loadOptions.fetch),
            signal
          }
        };
      }

      let inResourceManager = resourceManager.contains(url);

      if (!inResourceManager && !loadOptions) {
        resourceManager.add({
          resourceId: url,
          data: load(url, loaders),
          persistent: false
        });
        inResourceManager = true;
      }

      if (inResourceManager) {
        return resourceManager.subscribe({
          resourceId: url,
          onChange: data => {
            var _layer$internalState;

            return (_layer$internalState = layer.internalState) === null || _layer$internalState === void 0 ? void 0 : _layer$internalState.reloadAsyncProp(propName, data);
          },
          consumerId: layer.id,
          requestId: propName
        });
      }

      return load(url, loaders, loadOptions);
    }
  },
  updateTriggers: {},
  visible: true,
  pickable: false,
  opacity: {
    type: 'number',
    min: 0,
    max: 1,
    value: 1
  },
  operation: 'draw',
  onHover: {
    type: 'function',
    value: null,
    optional: true
  },
  onClick: {
    type: 'function',
    value: null,
    optional: true
  },
  onDragStart: {
    type: 'function',
    value: null,
    optional: true
  },
  onDrag: {
    type: 'function',
    value: null,
    optional: true
  },
  onDragEnd: {
    type: 'function',
    value: null,
    optional: true
  },
  coordinateSystem: COORDINATE_SYSTEM.DEFAULT,
  coordinateOrigin: {
    type: 'array',
    value: [0, 0, 0],
    compare: true
  },
  modelMatrix: {
    type: 'array',
    value: null,
    compare: true,
    optional: true
  },
  wrapLongitude: false,
  positionFormat: 'XYZ',
  colorFormat: 'RGBA',
  parameters: {
    type: 'object',
    value: {},
    optional: true,
    compare: 2
  },
  loadOptions: {
    type: 'object',
    value: null,
    optional: true,
    ignore: true
  },
  transitions: null,
  extensions: [],
  loaders: {
    type: 'array',
    value: [],
    optional: true,
    ignore: true
  },
  getPolygonOffset: {
    type: 'function',
    value: ({
      layerIndex
    }) => [0, -layerIndex * 100]
  },
  highlightedObjectIndex: null,
  autoHighlight: false,
  highlightColor: {
    type: 'accessor',
    value: [0, 0, 128, 128]
  }
};
export default class Layer extends Component {
  constructor(...args) {
    super(...args);
    this.internalState = null;
    this.lifecycle = LIFECYCLE.NO_STATE;
    this.context = void 0;
    this.state = void 0;
    this.parent = null;
  }

  static get componentName() {
    return Object.prototype.hasOwnProperty.call(this, 'layerName') ? this.layerName : '';
  }

  get root() {
    let layer = this;

    while (layer.parent) {
      layer = layer.parent;
    }

    return layer;
  }

  toString() {
    const className = this.constructor.layerName || this.constructor.name;
    return `${className}({id: '${this.props.id}'})`;
  }

  project(xyz) {
    assert(this.internalState);
    const viewport = this.internalState.viewport || this.context.viewport;
    const worldPosition = getWorldPosition(xyz, {
      viewport,
      modelMatrix: this.props.modelMatrix,
      coordinateOrigin: this.props.coordinateOrigin,
      coordinateSystem: this.props.coordinateSystem
    });
    const [x, y, z] = worldToPixels(worldPosition, viewport.pixelProjectionMatrix);
    return xyz.length === 2 ? [x, y] : [x, y, z];
  }

  unproject(xy) {
    assert(this.internalState);
    const viewport = this.internalState.viewport || this.context.viewport;
    return viewport.unproject(xy);
  }

  projectPosition(xyz, params) {
    assert(this.internalState);
    const viewport = this.internalState.viewport || this.context.viewport;
    return projectPosition(xyz, {
      viewport,
      modelMatrix: this.props.modelMatrix,
      coordinateOrigin: this.props.coordinateOrigin,
      coordinateSystem: this.props.coordinateSystem,
      ...params
    });
  }

  get isComposite() {
    return false;
  }

  setState(partialState) {
    this.setChangeFlags({
      stateChanged: true
    });
    Object.assign(this.state, partialState);
    this.setNeedsRedraw();
  }

  setNeedsRedraw() {
    if (this.internalState) {
      this.internalState.needsRedraw = true;
    }
  }

  setNeedsUpdate() {
    if (this.internalState) {
      this.context.layerManager.setNeedsUpdate(String(this));
      this.internalState.needsUpdate = true;
    }
  }

  get isLoaded() {
    return this.internalState ? !this.internalState.isAsyncPropLoading() : false;
  }

  get wrapLongitude() {
    return this.props.wrapLongitude;
  }

  isPickable() {
    return this.props.pickable && this.props.visible;
  }

  getModels() {
    const state = this.state;
    return state && (state.models || state.model && [state.model]) || [];
  }

  setModuleParameters(moduleParameters) {
    for (const model of this.getModels()) {
      model.updateModuleSettings(moduleParameters);
    }
  }

  setShaderModuleProps(...props) {
    for (const model of this.getModels()) {
      model.shaderInputs.setProps(...props);
    }
  }

  getAttributeManager() {
    return this.internalState && this.internalState.attributeManager;
  }

  getCurrentLayer() {
    return this.internalState && this.internalState.layer;
  }

  getLoadOptions() {
    return this.props.loadOptions;
  }

  use64bitPositions() {
    const {
      coordinateSystem
    } = this.props;
    return coordinateSystem === COORDINATE_SYSTEM.DEFAULT || coordinateSystem === COORDINATE_SYSTEM.LNGLAT || coordinateSystem === COORDINATE_SYSTEM.CARTESIAN;
  }

  onHover(info, pickingEvent) {
    if (this.props.onHover) {
      return this.props.onHover(info, pickingEvent) || false;
    }

    return false;
  }

  onClick(info, pickingEvent) {
    if (this.props.onClick) {
      return this.props.onClick(info, pickingEvent) || false;
    }

    return false;
  }

  nullPickingColor() {
    return [0, 0, 0];
  }

  encodePickingColor(i, target = []) {
    target[0] = i + 1 & 255;
    target[1] = i + 1 >> 8 & 255;
    target[2] = i + 1 >> 8 >> 8 & 255;
    return target;
  }

  decodePickingColor(color) {
    assert(color instanceof Uint8Array);
    const [i1, i2, i3] = color;
    const index = i1 + i2 * 256 + i3 * 65536 - 1;
    return index;
  }

  getNumInstances() {
    if (Number.isFinite(this.props.numInstances)) {
      return this.props.numInstances;
    }

    if (this.state && this.state.numInstances !== undefined) {
      return this.state.numInstances;
    }

    return count(this.props.data);
  }

  getStartIndices() {
    if (this.props.startIndices) {
      return this.props.startIndices;
    }

    if (this.state && this.state.startIndices) {
      return this.state.startIndices;
    }

    return null;
  }

  getBounds() {
    var _this$getAttributeMan;

    return (_this$getAttributeMan = this.getAttributeManager()) === null || _this$getAttributeMan === void 0 ? void 0 : _this$getAttributeMan.getBounds(['positions', 'instancePositions']);
  }

  getShaders(shaders) {
    for (const extension of this.props.extensions) {
      shaders = mergeShaders(shaders, extension.getShaders.call(this, extension));
    }

    return shaders;
  }

  shouldUpdateState(params) {
    return params.changeFlags.propsOrDataChanged;
  }

  updateState(params) {
    const attributeManager = this.getAttributeManager();
    const {
      dataChanged
    } = params.changeFlags;

    if (dataChanged && attributeManager) {
      if (Array.isArray(dataChanged)) {
        for (const dataRange of dataChanged) {
          attributeManager.invalidateAll(dataRange);
        }
      } else {
        attributeManager.invalidateAll();
      }
    }

    if (attributeManager) {
      const {
        props
      } = params;
      const hasPickingBuffer = this.internalState.hasPickingBuffer;
      const needsPickingBuffer = Number.isInteger(props.highlightedObjectIndex) || props.pickable || props.extensions.some(extension => extension.getNeedsPickingBuffer.call(this, extension));

      if (hasPickingBuffer !== needsPickingBuffer) {
        this.internalState.hasPickingBuffer = needsPickingBuffer;
        const {
          pickingColors,
          instancePickingColors
        } = attributeManager.attributes;
        const pickingColorsAttribute = pickingColors || instancePickingColors;

        if (pickingColorsAttribute) {
          if (needsPickingBuffer && pickingColorsAttribute.constant) {
            pickingColorsAttribute.constant = false;
            attributeManager.invalidate(pickingColorsAttribute.id);
          }

          if (!pickingColorsAttribute.value && !needsPickingBuffer) {
            pickingColorsAttribute.constant = true;
            pickingColorsAttribute.value = [0, 0, 0];
          }
        }
      }
    }
  }

  finalizeState(context) {
    for (const model of this.getModels()) {
      model.destroy();
    }

    const attributeManager = this.getAttributeManager();

    if (attributeManager) {
      attributeManager.finalize();
    }

    if (this.context) {
      this.context.resourceManager.unsubscribe({
        consumerId: this.id
      });
    }

    if (this.internalState) {
      this.internalState.uniformTransitions.clear();
      this.internalState.finalize();
    }
  }

  draw(opts) {
    for (const model of this.getModels()) {
      model.draw(opts);
    }
  }

  getPickingInfo({
    info,
    mode,
    sourceLayer
  }) {
    const {
      index
    } = info;

    if (index >= 0) {
      if (Array.isArray(this.props.data)) {
        info.object = this.props.data[index];
      }
    }

    return info;
  }

  raiseError(error, message) {
    var _this$props$onError, _this$props;

    if (message) {
      error = new Error(`${message}: ${error.message}`, {
        cause: error
      });
    }

    if (!((_this$props$onError = (_this$props = this.props).onError) !== null && _this$props$onError !== void 0 && _this$props$onError.call(_this$props, error))) {
      var _this$context, _this$context$onError;

      (_this$context = this.context) === null || _this$context === void 0 ? void 0 : (_this$context$onError = _this$context.onError) === null || _this$context$onError === void 0 ? void 0 : _this$context$onError.call(_this$context, error, this);
    }
  }

  getNeedsRedraw(opts = {
    clearRedrawFlags: false
  }) {
    return this._getNeedsRedraw(opts);
  }

  needsUpdate() {
    if (!this.internalState) {
      return false;
    }

    return this.internalState.needsUpdate || this.hasUniformTransition() || this.shouldUpdateState(this._getUpdateParams());
  }

  hasUniformTransition() {
    var _this$internalState;

    return ((_this$internalState = this.internalState) === null || _this$internalState === void 0 ? void 0 : _this$internalState.uniformTransitions.active) || false;
  }

  activateViewport(viewport) {
    if (!this.internalState) {
      return;
    }

    const oldViewport = this.internalState.viewport;
    this.internalState.viewport = viewport;

    if (!oldViewport || !areViewportsEqual({
      oldViewport,
      viewport
    })) {
      this.setChangeFlags({
        viewportChanged: true
      });

      if (this.isComposite) {
        if (this.needsUpdate()) {
          this.setNeedsUpdate();
        }
      } else {
        this._update();
      }
    }
  }

  invalidateAttribute(name = 'all') {
    const attributeManager = this.getAttributeManager();

    if (!attributeManager) {
      return;
    }

    if (name === 'all') {
      attributeManager.invalidateAll();
    } else {
      attributeManager.invalidate(name);
    }
  }

  updateAttributes(changedAttributes) {
    let bufferLayoutChanged = false;

    for (const id in changedAttributes) {
      if (changedAttributes[id].layoutChanged()) {
        bufferLayoutChanged = true;
      }
    }

    for (const model of this.getModels()) {
      this._setModelAttributes(model, changedAttributes, bufferLayoutChanged);
    }
  }

  _updateAttributes() {
    const attributeManager = this.getAttributeManager();

    if (!attributeManager) {
      return;
    }

    const props = this.props;
    const numInstances = this.getNumInstances();
    const startIndices = this.getStartIndices();
    attributeManager.update({
      data: props.data,
      numInstances,
      startIndices,
      props,
      transitions: props.transitions,
      buffers: props.data.attributes,
      context: this
    });
    const changedAttributes = attributeManager.getChangedAttributes({
      clearChangedFlags: true
    });
    this.updateAttributes(changedAttributes);
  }

  _updateAttributeTransition() {
    const attributeManager = this.getAttributeManager();

    if (attributeManager) {
      attributeManager.updateTransition();
    }
  }

  _updateUniformTransition() {
    const {
      uniformTransitions
    } = this.internalState;

    if (uniformTransitions.active) {
      const propsInTransition = uniformTransitions.update();
      const props = Object.create(this.props);

      for (const key in propsInTransition) {
        Object.defineProperty(props, key, {
          value: propsInTransition[key]
        });
      }

      return props;
    }

    return this.props;
  }

  calculateInstancePickingColors(attribute, {
    numInstances
  }) {
    if (attribute.constant) {
      return;
    }

    const cacheSize = Math.floor(pickingColorCache.length / 4);
    this.internalState.usesPickingColorCache = true;

    if (cacheSize < numInstances) {
      if (numInstances > MAX_PICKING_COLOR_CACHE_SIZE) {
        log.warn('Layer has too many data objects. Picking might not be able to distinguish all objects.')();
      }

      pickingColorCache = typedArrayManager.allocate(pickingColorCache, numInstances, {
        size: 4,
        copy: true,
        maxCount: Math.max(numInstances, MAX_PICKING_COLOR_CACHE_SIZE)
      });
      const newCacheSize = Math.floor(pickingColorCache.length / 4);
      const pickingColor = [];

      for (let i = cacheSize; i < newCacheSize; i++) {
        this.encodePickingColor(i, pickingColor);
        pickingColorCache[i * 4 + 0] = pickingColor[0];
        pickingColorCache[i * 4 + 1] = pickingColor[1];
        pickingColorCache[i * 4 + 2] = pickingColor[2];
      }
    }

    attribute.value = pickingColorCache.subarray(0, numInstances * 4);
  }

  _setModelAttributes(model, changedAttributes, bufferLayoutChanged = false) {
    var _model$userData;

    if (!Object.keys(changedAttributes).length) {
      return;
    }

    if (bufferLayoutChanged) {
      const attributeManager = this.getAttributeManager();
      model.setBufferLayout(attributeManager.getBufferLayouts());
      changedAttributes = attributeManager.getAttributes();
    }

    const excludeAttributes = ((_model$userData = model.userData) === null || _model$userData === void 0 ? void 0 : _model$userData.excludeAttributes) || {};
    const attributeBuffers = {};
    const constantAttributes = {};

    for (const name in changedAttributes) {
      if (excludeAttributes[name]) {
        continue;
      }

      const values = changedAttributes[name].getValue();

      for (const attributeName in values) {
        const value = values[attributeName];

        if (value instanceof Buffer) {
          if (changedAttributes[name].settings.isIndexed) {
            model.setIndexBuffer(value);
          } else {
            attributeBuffers[attributeName] = value;
          }
        } else if (value) {
          constantAttributes[attributeName] = value;
        }
      }
    }

    model.setAttributes(attributeBuffers);
    model.setConstantAttributes(constantAttributes);
  }

  disablePickingIndex(objectIndex) {
    const data = this.props.data;

    if (!('attributes' in data)) {
      this._disablePickingIndex(objectIndex);

      return;
    }

    const {
      pickingColors,
      instancePickingColors
    } = this.getAttributeManager().attributes;
    const colors = pickingColors || instancePickingColors;
    const externalColorAttribute = colors && data.attributes && data.attributes[colors.id];

    if (externalColorAttribute && externalColorAttribute.value) {
      const values = externalColorAttribute.value;
      const objectColor = this.encodePickingColor(objectIndex);

      for (let index = 0; index < data.length; index++) {
        const i = colors.getVertexOffset(index);

        if (values[i] === objectColor[0] && values[i + 1] === objectColor[1] && values[i + 2] === objectColor[2]) {
          this._disablePickingIndex(index);
        }
      }
    } else {
      this._disablePickingIndex(objectIndex);
    }
  }

  _disablePickingIndex(objectIndex) {
    const {
      pickingColors,
      instancePickingColors
    } = this.getAttributeManager().attributes;
    const colors = pickingColors || instancePickingColors;

    if (!colors) {
      return;
    }

    const start = colors.getVertexOffset(objectIndex);
    const end = colors.getVertexOffset(objectIndex + 1);
    colors.buffer.write(new Uint8Array(end - start), start);
  }

  restorePickingColors() {
    const {
      pickingColors,
      instancePickingColors
    } = this.getAttributeManager().attributes;
    const colors = pickingColors || instancePickingColors;

    if (!colors) {
      return;
    }

    if (this.internalState.usesPickingColorCache && colors.value.buffer !== pickingColorCache.buffer) {
      colors.value = pickingColorCache.subarray(0, colors.value.length);
    }

    colors.updateSubBuffer({
      startOffset: 0
    });
  }

  _initialize() {
    assert(!this.internalState);
    assert(Number.isFinite(this.props.coordinateSystem));
    debug(TRACE_INITIALIZE, this);

    const attributeManager = this._getAttributeManager();

    if (attributeManager) {
      attributeManager.addInstanced({
        instancePickingColors: {
          type: 5121,
          size: 4,
          noAlloc: true,
          update: this.calculateInstancePickingColors
        }
      });
    }

    this.internalState = new LayerState({
      attributeManager,
      layer: this
    });

    this._clearChangeFlags();

    this.state = {};
    Object.defineProperty(this.state, 'attributeManager', {
      get: () => {
        log.deprecated('layer.state.attributeManager', 'layer.getAttributeManager()')();
        return attributeManager;
      }
    });
    this.internalState.uniformTransitions = new UniformTransitionManager(this.context.timeline);
    this.internalState.onAsyncPropUpdated = this._onAsyncPropUpdated.bind(this);
    this.internalState.setAsyncProps(this.props);
    this.initializeState(this.context);

    for (const extension of this.props.extensions) {
      extension.initializeState.call(this, this.context, extension);
    }

    this.setChangeFlags({
      dataChanged: 'init',
      propsChanged: 'init',
      viewportChanged: true,
      extensionsChanged: true
    });

    this._update();
  }

  _transferState(oldLayer) {
    debug(TRACE_MATCHED, this, this === oldLayer);
    const {
      state,
      internalState
    } = oldLayer;

    if (this === oldLayer) {
      return;
    }

    this.internalState = internalState;
    this.state = state;
    this.internalState.setAsyncProps(this.props);

    this._diffProps(this.props, this.internalState.getOldProps());
  }

  _update() {
    const stateNeedsUpdate = this.needsUpdate();
    debug(TRACE_UPDATE, this, stateNeedsUpdate);

    if (!stateNeedsUpdate) {
      return;
    }

    const currentProps = this.props;
    const context = this.context;
    const internalState = this.internalState;
    const currentViewport = context.viewport;

    const propsInTransition = this._updateUniformTransition();

    internalState.propsInTransition = propsInTransition;
    context.viewport = internalState.viewport || currentViewport;
    this.props = propsInTransition;

    try {
      const updateParams = this._getUpdateParams();

      const oldModels = this.getModels();

      if (context.device) {
        this.updateState(updateParams);
      } else {
        try {
          this.updateState(updateParams);
        } catch (error) {}
      }

      for (const extension of this.props.extensions) {
        extension.updateState.call(this, updateParams, extension);
      }

      const modelChanged = this.getModels()[0] !== oldModels[0];

      this._postUpdate(updateParams, modelChanged);
    } finally {
      context.viewport = currentViewport;
      this.props = currentProps;

      this._clearChangeFlags();

      internalState.needsUpdate = false;
      internalState.resetOldProps();
    }
  }

  _finalize() {
    debug(TRACE_FINALIZE, this);
    this.finalizeState(this.context);

    for (const extension of this.props.extensions) {
      extension.finalizeState.call(this, this.context, extension);
    }
  }

  _drawLayer({
    renderPass,
    moduleParameters = null,
    uniforms = {},
    parameters = {}
  }) {
    this._updateAttributeTransition();

    const currentProps = this.props;
    const context = this.context;
    this.props = this.internalState.propsInTransition || currentProps;
    const opacity = this.props.opacity;
    uniforms.opacity = Math.pow(opacity, 1 / 2.2);

    try {
      if (moduleParameters) {
        const {
          isActive,
          isAttribute
        } = moduleParameters.picking;
        this.setModuleParameters(moduleParameters);
        this.setShaderModuleProps({
          picking: {
            isActive,
            isAttribute
          }
        });
      }

      const {
        getPolygonOffset
      } = this.props;
      const offsets = getPolygonOffset && getPolygonOffset(uniforms) || [0, 0];
      setGLParameters(context.device, {
        polygonOffset: offsets
      });
      withGLParameters(context.gl, parameters, () => {
        const opts = {
          renderPass,
          moduleParameters,
          uniforms,
          parameters,
          context
        };

        for (const extension of this.props.extensions) {
          extension.draw.call(this, opts, extension);
        }

        this.draw(opts);
      });
    } finally {
      this.props = currentProps;
    }
  }

  getChangeFlags() {
    var _this$internalState2;

    return (_this$internalState2 = this.internalState) === null || _this$internalState2 === void 0 ? void 0 : _this$internalState2.changeFlags;
  }

  setChangeFlags(flags) {
    if (!this.internalState) {
      return;
    }

    const {
      changeFlags
    } = this.internalState;

    for (const key in flags) {
      if (flags[key]) {
        let flagChanged = false;

        switch (key) {
          case 'dataChanged':
            const dataChangedReason = flags[key];
            const prevDataChangedReason = changeFlags[key];

            if (dataChangedReason && Array.isArray(prevDataChangedReason)) {
              changeFlags.dataChanged = Array.isArray(dataChangedReason) ? prevDataChangedReason.concat(dataChangedReason) : dataChangedReason;
              flagChanged = true;
            }

          default:
            if (!changeFlags[key]) {
              changeFlags[key] = flags[key];
              flagChanged = true;
            }

        }

        if (flagChanged) {
          debug(TRACE_CHANGE_FLAG, this, key, flags);
        }
      }
    }

    const propsOrDataChanged = Boolean(changeFlags.dataChanged || changeFlags.updateTriggersChanged || changeFlags.propsChanged || changeFlags.extensionsChanged);
    changeFlags.propsOrDataChanged = propsOrDataChanged;
    changeFlags.somethingChanged = propsOrDataChanged || changeFlags.viewportChanged || changeFlags.stateChanged;
  }

  _clearChangeFlags() {
    this.internalState.changeFlags = {
      dataChanged: false,
      propsChanged: false,
      updateTriggersChanged: false,
      viewportChanged: false,
      stateChanged: false,
      extensionsChanged: false,
      propsOrDataChanged: false,
      somethingChanged: false
    };
  }

  _diffProps(newProps, oldProps) {
    const changeFlags = diffProps(newProps, oldProps);

    if (changeFlags.updateTriggersChanged) {
      for (const key in changeFlags.updateTriggersChanged) {
        if (changeFlags.updateTriggersChanged[key]) {
          this.invalidateAttribute(key);
        }
      }
    }

    if (changeFlags.transitionsChanged) {
      for (const key in changeFlags.transitionsChanged) {
        var _newProps$transitions;

        this.internalState.uniformTransitions.add(key, oldProps[key], newProps[key], (_newProps$transitions = newProps.transitions) === null || _newProps$transitions === void 0 ? void 0 : _newProps$transitions[key]);
      }
    }

    return this.setChangeFlags(changeFlags);
  }

  validateProps() {
    validateProps(this.props);
  }

  updateAutoHighlight(info) {
    if (this.props.autoHighlight && !Number.isInteger(this.props.highlightedObjectIndex)) {
      this._updateAutoHighlight(info);
    }
  }

  _updateAutoHighlight(info) {
    const picking = {
      highlightedObjectColor: info.picked ? info.color : null
    };
    const {
      highlightColor
    } = this.props;

    if (info.picked && typeof highlightColor === 'function') {
      picking.highlightColor = highlightColor(info);
    }

    this.setShaderModuleProps({
      picking
    });
    this.setNeedsRedraw();
  }

  _getAttributeManager() {
    const context = this.context;
    return new AttributeManager(context.device, {
      id: this.props.id,
      stats: context.stats,
      timeline: context.timeline
    });
  }

  _postUpdate(updateParams, forceUpdate) {
    const {
      props,
      oldProps
    } = updateParams;
    this.setNeedsRedraw();

    this._updateAttributes();

    const model = this.state.model;
    model === null || model === void 0 ? void 0 : model.setInstanceCount(this.getNumInstances());
    const {
      autoHighlight,
      highlightedObjectIndex,
      highlightColor
    } = props;

    if (forceUpdate || oldProps.autoHighlight !== autoHighlight || oldProps.highlightedObjectIndex !== highlightedObjectIndex || oldProps.highlightColor !== highlightColor) {
      const picking = {};

      if (!autoHighlight) {
        picking.highlightedObjectColor = null;
      }

      if (Array.isArray(highlightColor)) {
        picking.highlightColor = highlightColor;
      }

      if (forceUpdate || highlightedObjectIndex !== oldProps.highlightedObjectIndex) {
        picking.highlightedObjectColor = Number.isFinite(highlightedObjectIndex) && highlightedObjectIndex >= 0 ? this.encodePickingColor(highlightedObjectIndex) : null;
      }

      this.setShaderModuleProps({
        picking
      });
    }
  }

  _getUpdateParams() {
    return {
      props: this.props,
      oldProps: this.internalState.getOldProps(),
      context: this.context,
      changeFlags: this.internalState.changeFlags
    };
  }

  _getNeedsRedraw(opts) {
    if (!this.internalState) {
      return false;
    }

    let redraw = false;
    redraw = redraw || this.internalState.needsRedraw && this.id;
    const attributeManager = this.getAttributeManager();
    const attributeManagerNeedsRedraw = attributeManager ? attributeManager.getNeedsRedraw(opts) : false;
    redraw = redraw || attributeManagerNeedsRedraw;

    if (redraw) {
      for (const extension of this.props.extensions) {
        extension.onNeedsRedraw.call(this, extension);
      }
    }

    this.internalState.needsRedraw = this.internalState.needsRedraw && !opts.clearRedrawFlags;
    return redraw;
  }

  _onAsyncPropUpdated() {
    this._diffProps(this.props, this.internalState.getOldProps());

    this.setNeedsUpdate();
  }

}
Layer.defaultProps = defaultProps;
Layer.layerName = 'Layer';
//# sourceMappingURL=layer.js.map