// tslint:disable:variable-name Describing an API that's defined elsewhere.
// tslint:disable:no-any describes the API as best we are able today

import {dedupingMixin} from '../utils/mixin.js';

import {microTask} from '../utils/async.js';

export {PropertiesChanged};


/**
 * Element class mixin that provides basic meta-programming for creating one
 * or more property accessors (getter/setter pair) that enqueue an async
 * (batched) `_propertiesChanged` callback.
 *
 * For basic usage of this mixin, call `MyClass.createProperties(props)`
 * once at class definition time to create property accessors for properties
 * named in props, implement `_propertiesChanged` to react as desired to
 * property changes, and implement `static get observedAttributes()` and
 * include lowercase versions of any property names that should be set from
 * attributes. Last, call `this._enableProperties()` in the element's
 * `connectedCallback` to enable the accessors.
 */
declare function PropertiesChanged<T extends new (...args: any[]) => {}>(base: T): T & PropertiesChangedConstructor;

interface PropertiesChangedConstructor {
  new(...args: any[]): PropertiesChanged;

  /**
   * Creates property accessors for the given property names.
   *
   * @param props Object whose keys are names of accessors.
   */
  createProperties(props: object): void;

  /**
   * Returns an attribute name that corresponds to the given property.
   * The attribute name is the lowercased property name. Override to
   * customize this mapping.
   *
   * @param property Property to convert
   * @returns Attribute name corresponding to the given property.
   */
  attributeNameForProperty(property: string): string;

  /**
   * Override point to provide a type to which to deserialize a value to
   * a given property.
   *
   * @param name Name of property
   */
  typeForProperty(name: string): void;
}

export {PropertiesChangedConstructor};

interface PropertiesChanged {

  /**
   * Creates a setter/getter pair for the named property with its own
   * local storage.  The getter returns the value in the local storage,
   * and the setter calls `_setProperty`, which updates the local storage
   * for the property and enqueues a `_propertiesChanged` callback.
   *
   * This method may be called on a prototype or an instance.  Calling
   * this method may overwrite a property value that already exists on
   * the prototype/instance by creating the accessor.
   *
   * @param property Name of the property
   * @param readOnly When true, no setter is created; the
   *   protected `_setProperty` function must be used to set the property
   */
  _createPropertyAccessor(property: string, readOnly?: boolean): void;

  /**
   * Adds the given `property` to a map matching attribute names
   * to property names, using `attributeNameForProperty`. This map is
   * used when deserializing attribute values to properties.
   *
   * @param property Name of the property
   */
  _addPropertyToAttributeMap(property: string): any;

  /**
   * Defines a property accessor for the given property.
   *
   * @param property Name of the property
   * @param readOnly When true, no setter is created
   */
  _definePropertyAccessor(property: string, readOnly?: boolean): void;

  /**
   * Lifecycle callback called when properties are enabled via
   * `_enableProperties`.
   *
   * Users may override this function to implement behavior that is
   * dependent on the element having its property data initialized, e.g.
   * from defaults (initialized from `constructor`, `_initializeProperties`),
   * `attributeChangedCallback`, or values propagated from host e.g. via
   * bindings.  `super.ready()` must be called to ensure the data system
   * becomes enabled.
   */
  ready(): void;

  /**
   * Initializes the local storage for property accessors.
   *
   * Provided as an override point for performing any setup work prior
   * to initializing the property accessor system.
   */
  _initializeProperties(): void;

  /**
   * Called at ready time with bag of instance properties that overwrote
   * accessors when the element upgraded.
   *
   * The default implementation sets these properties back into the
   * setter at ready time.  This method is provided as an override
   * point for customizing or providing more efficient initialization.
   *
   * @param props Bag of property values that were overwritten
   *   when creating property accessors.
   */
  _initializeInstanceProperties(props: object|null): void;

  /**
   * Updates the local storage for a property (via `_setPendingProperty`)
   * and enqueues a `_proeprtiesChanged` callback.
   *
   * @param property Name of the property
   * @param value Value to set
   */
  _setProperty(property: string, value: any): void;

  /**
   * Returns the value for the given property.
   *
   * @param property Name of property
   * @returns Value for the given property
   */
  _getProperty(property: string): any;

  /**
   * Updates the local storage for a property, records the previous value,
   * and adds it to the set of "pending changes" that will be passed to the
   * `_propertiesChanged` callback.  This method does not enqueue the
   * `_propertiesChanged` callback.
   *
   * @param property Name of the property
   * @param value Value to set
   * @param ext Not used here; affordance for closure
   * @returns Returns true if the property changed
   */
  _setPendingProperty(property: string, value: any, ext?: boolean): boolean;

  /**
   * @param property Name of the property
   * @returns Returns true if the property is pending.
   */
  _isPropertyPending(property: string): boolean;

  /**
   * Marks the properties as invalid, and enqueues an async
   * `_propertiesChanged` callback.
   */
  _invalidateProperties(): void;

  /**
   * Call to enable property accessor processing. Before this method is
   * called accessor values will be set but side effects are
   * queued. When called, any pending side effects occur immediately.
   * For elements, generally `connectedCallback` is a normal spot to do so.
   * It is safe to call this method multiple times as it only turns on
   * property accessors once.
   */
  _enableProperties(): void;

  /**
   * Calls the `_propertiesChanged` callback with the current set of
   * pending changes (and old values recorded when pending changes were
   * set), and resets the pending set of changes. Generally, this method
   * should not be called in user code.
   */
  _flushProperties(): void;

  /**
   * Called in `_flushProperties` to determine if `_propertiesChanged`
   * should be called. The default implementation returns true if
   * properties are pending. Override to customize when
   * `_propertiesChanged` is called.
   *
   * @param currentProps Bag of all current accessor values
   * @param changedProps Bag of properties changed since the last
   *   call to `_propertiesChanged`
   * @param oldProps Bag of previous values for each property
   *   in `changedProps`
   * @returns true if changedProps is truthy
   */
  _shouldPropertiesChange(currentProps: object, changedProps: object|null, oldProps: object|null): boolean;

  /**
   * Callback called when any properties with accessors created via
   * `_createPropertyAccessor` have been set.
   *
   * @param currentProps Bag of all current accessor values
   * @param changedProps Bag of properties changed since the last
   *   call to `_propertiesChanged`
   * @param oldProps Bag of previous values for each property
   *   in `changedProps`
   */
  _propertiesChanged(currentProps: object, changedProps: object|null, oldProps: object|null): void;

  /**
   * Method called to determine whether a property value should be
   * considered as a change and cause the `_propertiesChanged` callback
   * to be enqueued.
   *
   * The default implementation returns `true` if a strict equality
   * check fails. The method always returns false for `NaN`.
   *
   * Override this method to e.g. provide stricter checking for
   * Objects/Arrays when using immutable patterns.
   *
   * @param property Property name
   * @param value New property value
   * @param old Previous property value
   * @returns Whether the property should be considered a change
   *   and enqueue a `_proeprtiesChanged` callback
   */
  _shouldPropertyChange(property: string, value: any, old: any): boolean;

  /**
   * Implements native Custom Elements `attributeChangedCallback` to
   * set an attribute value to a property via `_attributeToProperty`.
   *
   * @param name Name of attribute that changed
   * @param old Old attribute value
   * @param value New attribute value
   * @param namespace Attribute namespace.
   */
  attributeChangedCallback(name: string, old: string|null, value: string|null, namespace: string|null): void;

  /**
   * Deserializes an attribute to its associated property.
   *
   * This method calls the `_deserializeValue` method to convert the string to
   * a typed value.
   *
   * @param attribute Name of attribute to deserialize.
   * @param value of the attribute.
   * @param type type to deserialize to, defaults to the value
   * returned from `typeForProperty`
   */
  _attributeToProperty(attribute: string, value: string|null, type?: any): void;

  /**
   * Serializes a property to its associated attribute.
   *
   * @param property Property name to reflect.
   * @param attribute Attribute name to reflect to.
   * @param value Property value to refect.
   */
  _propertyToAttribute(property: string, attribute?: string, value?: any): void;

  /**
   * Sets a typed value to an HTML attribute on a node.
   *
   * This method calls the `_serializeValue` method to convert the typed
   * value to a string.  If the `_serializeValue` method returns `undefined`,
   * the attribute will be removed (this is the default for boolean
   * type `false`).
   *
   * @param node Element to set attribute to.
   * @param value Value to serialize.
   * @param attribute Attribute name to serialize to.
   */
  _valueToNodeAttribute(node: Element|null, value: any, attribute: string): void;

  /**
   * Converts a typed JavaScript value to a string.
   *
   * This method is called when setting JS property values to
   * HTML attributes.  Users may override this method to provide
   * serialization for custom types.
   *
   * @param value Property value to serialize.
   * @returns String serialized from the provided
   * property  value.
   */
  _serializeValue(value: any): string|undefined;

  /**
   * Converts a string to a typed JavaScript value.
   *
   * This method is called when reading HTML attribute values to
   * JS properties.  Users may override this method to provide
   * deserialization for custom `type`s. Types for `Boolean`, `String`,
   * and `Number` convert attributes to the expected types.
   *
   * @param value Value to deserialize.
   * @param type Type to deserialize the string to.
   * @returns Typed value deserialized from the provided string.
   */
  _deserializeValue(value: string|null, type?: any): any;
}
