Source: data/field.js

import TRANSFORMS from '../registry/transforms';

/**
 * Represents an addressable unit of data from a namespaced datasource, subject to specified value transformations.
 *
 * When used by a data layer, fields will automatically be re-fetched from the appropriate data source whenever the
 *   state of a plot fetches, eg pan or zoom operations that would affect what data is displayed.
 *
 * @private
 * @class
 * @param {String} field A string representing the namespace of the datasource, the name of the desired field to fetch
 *   from that datasource, and arbitrarily many transformations to apply to the value. The namespace and
 *   transformation(s) are optional and information is delimited according to the general syntax
 *   `[namespace:]name[|transformation][|transformation]`. For example, `association:pvalue|neglog10`
 */
class Field {
    constructor(field) {
        // Two scenarios: we are requesting a field by full name, OR there are transforms to apply
        // `fieldname` or `namespace:fieldname` followed by `|filter1|filterN`
        const field_pattern = /^(?:\w+:\w+|^\w+)(?:\|\w+)*$/;
        if (!field_pattern.test(field)) {
            throw new Error(`Invalid field specifier: '${field}'`);
        }

        const [name, ...transforms] = field.split('|');

        this.full_name = field; // fieldname + transforms
        this.field_name = name; // just fieldname
        this.transformations = transforms.map((name) => TRANSFORMS.get(name));
    }

    _applyTransformations(val) {
        this.transformations.forEach(function(transform) {
            val = transform(val);
        });
        return val;
    }

    /**
     * Resolve the field for a given data element.
     *   First look for a full match with transformations already applied by the data requester.
     *   Otherwise prefer a namespace match and fall back to just a name match, applying transformations on the fly.
     * @param {Object} data Returned data/fields into for this element
     * @param {Object} [extra] User-applied annotations for this point (info not provided by the server that we want
     *  to preserve across re-renders). Example usage: "should_show_label"
     * @returns {*}
     */
    resolve(data, extra) {
        // Four resolutions: a) This is cached, b) this can be calculated from a known field, c) this is a known annotation rather than from an API, d) This field doesn't exist and returns as null
        if (typeof data[this.full_name] == 'undefined') { // Check for cached result
            let val = null;
            if (data[this.field_name] !== undefined) { // Fallback: value sans transforms
                val = data[this.field_name];
            } else if (extra && extra[this.field_name] !== undefined) { // Fallback: check annotations
                val = extra[this.field_name];
            } // Don't warn if no value found, because sometimes only certain rows will have a specific field (esp happens with annotations)
            data[this.full_name] = this._applyTransformations(val);
        }
        return data[this.full_name];
    }
}

export {Field as default};