/**
 * Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule FluxStore
 * @flow
 */

'use strict';

import type Dispatcher from 'Dispatcher';

const {EventEmitter} = require('fbemitter');

const invariant = require('invariant');

/**
 * This class represents the most basic functionality for a FluxStore. Do not
 * extend this store directly; instead extend FluxReduceStore when creating a
 * new store.
 */
class FluxStore {
  // private
  _dispatchToken: string;

  // protected, available to subclasses
  __changed: boolean;
  __changeEvent: string;
  __className: any;
  __dispatcher: Dispatcher<any>;
  __emitter: EventEmitter;

  constructor(dispatcher: Dispatcher<any>): void {
    this.__className = this.constructor.name;

    this.__changed = false;
    this.__changeEvent = 'change';
    this.__dispatcher = dispatcher;
    this.__emitter = new EventEmitter();
    this._dispatchToken = dispatcher.register((payload) => {
      this.__invokeOnDispatch(payload);
    });
  }

  addListener(callback: (eventType?: string) => void): {remove: () => void} {
    return this.__emitter.addListener(this.__changeEvent, callback);
  }

  getDispatcher(): Dispatcher<any> {
    return this.__dispatcher;
  }

  /**
   * This exposes a unique string to identify each store's registered callback.
   * This is used with the dispatcher's waitFor method to declaratively depend
   * on other stores updating themselves first.
   */
  getDispatchToken(): string {
    return this._dispatchToken;
  }

  /**
   * Returns whether the store has changed during the most recent dispatch.
   */
  hasChanged(): boolean {
    invariant(
      this.__dispatcher.isDispatching(),
      '%s.hasChanged(): Must be invoked while dispatching.',
      this.__className,
    );
    return this.__changed;
  }

  __emitChange(): void {
    invariant(
      this.__dispatcher.isDispatching(),
      '%s.__emitChange(): Must be invoked while dispatching.',
      this.__className,
    );
    this.__changed = true;
  }

  /**
   * This method encapsulates all logic for invoking __onDispatch. It should
   * be used for things like catching changes and emitting them after the
   * subclass has handled a payload.
   */
  __invokeOnDispatch(payload: Object): void {
    this.__changed = false;
    this.__onDispatch(payload);
    if (this.__changed) {
      this.__emitter.emit(this.__changeEvent);
    }
  }

  /**
   * The callback that will be registered with the dispatcher during
   * instantiation. Subclasses must override this method. This callback is the
   * only way the store receives new data.
   */
  __onDispatch(payload: Object): void {
    invariant(
      false,
      '%s has not overridden FluxStore.__onDispatch(), which is required',
      this.__className,
    );
  }
}

module.exports = FluxStore;
