#!/usr/bin/env node
import require$$0 from 'fs';

function getDefaultExportFromCjs (x) {
	return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}

var minimist$1;
var hasRequiredMinimist;

function requireMinimist () {
	if (hasRequiredMinimist) return minimist$1;
	hasRequiredMinimist = 1;

	function hasKey(obj, keys) {
		var o = obj;
		keys.slice(0, -1).forEach(function (key) {
			o = o[key] || {};
		});

		var key = keys[keys.length - 1];
		return key in o;
	}

	function isNumber(x) {
		if (typeof x === 'number') { return true; }
		if ((/^0x[0-9a-f]+$/i).test(x)) { return true; }
		return (/^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/).test(x);
	}

	function isConstructorOrProto(obj, key) {
		return (key === 'constructor' && typeof obj[key] === 'function') || key === '__proto__';
	}

	minimist$1 = function (args, opts) {
		if (!opts) { opts = {}; }

		var flags = {
			bools: {},
			strings: {},
			unknownFn: null,
		};

		if (typeof opts.unknown === 'function') {
			flags.unknownFn = opts.unknown;
		}

		if (typeof opts.boolean === 'boolean' && opts.boolean) {
			flags.allBools = true;
		} else {
			[].concat(opts.boolean).filter(Boolean).forEach(function (key) {
				flags.bools[key] = true;
			});
		}

		var aliases = {};

		function aliasIsBoolean(key) {
			return aliases[key].some(function (x) {
				return flags.bools[x];
			});
		}

		Object.keys(opts.alias || {}).forEach(function (key) {
			aliases[key] = [].concat(opts.alias[key]);
			aliases[key].forEach(function (x) {
				aliases[x] = [key].concat(aliases[key].filter(function (y) {
					return x !== y;
				}));
			});
		});

		[].concat(opts.string).filter(Boolean).forEach(function (key) {
			flags.strings[key] = true;
			if (aliases[key]) {
				[].concat(aliases[key]).forEach(function (k) {
					flags.strings[k] = true;
				});
			}
		});

		var defaults = opts.default || {};

		var argv = { _: [] };

		function argDefined(key, arg) {
			return (flags.allBools && (/^--[^=]+$/).test(arg))
				|| flags.strings[key]
				|| flags.bools[key]
				|| aliases[key];
		}

		function setKey(obj, keys, value) {
			var o = obj;
			for (var i = 0; i < keys.length - 1; i++) {
				var key = keys[i];
				if (isConstructorOrProto(o, key)) { return; }
				if (o[key] === undefined) { o[key] = {}; }
				if (
					o[key] === Object.prototype
					|| o[key] === Number.prototype
					|| o[key] === String.prototype
				) {
					o[key] = {};
				}
				if (o[key] === Array.prototype) { o[key] = []; }
				o = o[key];
			}

			var lastKey = keys[keys.length - 1];
			if (isConstructorOrProto(o, lastKey)) { return; }
			if (
				o === Object.prototype
				|| o === Number.prototype
				|| o === String.prototype
			) {
				o = {};
			}
			if (o === Array.prototype) { o = []; }
			if (o[lastKey] === undefined || flags.bools[lastKey] || typeof o[lastKey] === 'boolean') {
				o[lastKey] = value;
			} else if (Array.isArray(o[lastKey])) {
				o[lastKey].push(value);
			} else {
				o[lastKey] = [o[lastKey], value];
			}
		}

		function setArg(key, val, arg) {
			if (arg && flags.unknownFn && !argDefined(key, arg)) {
				if (flags.unknownFn(arg) === false) { return; }
			}

			var value = !flags.strings[key] && isNumber(val)
				? Number(val)
				: val;
			setKey(argv, key.split('.'), value);

			(aliases[key] || []).forEach(function (x) {
				setKey(argv, x.split('.'), value);
			});
		}

		Object.keys(flags.bools).forEach(function (key) {
			setArg(key, defaults[key] === undefined ? false : defaults[key]);
		});

		var notFlags = [];

		if (args.indexOf('--') !== -1) {
			notFlags = args.slice(args.indexOf('--') + 1);
			args = args.slice(0, args.indexOf('--'));
		}

		for (var i = 0; i < args.length; i++) {
			var arg = args[i];
			var key;
			var next;

			if ((/^--.+=/).test(arg)) {
				// Using [\s\S] instead of . because js doesn't support the
				// 'dotall' regex modifier. See:
				// http://stackoverflow.com/a/1068308/13216
				var m = arg.match(/^--([^=]+)=([\s\S]*)$/);
				key = m[1];
				var value = m[2];
				if (flags.bools[key]) {
					value = value !== 'false';
				}
				setArg(key, value, arg);
			} else if ((/^--no-.+/).test(arg)) {
				key = arg.match(/^--no-(.+)/)[1];
				setArg(key, false, arg);
			} else if ((/^--.+/).test(arg)) {
				key = arg.match(/^--(.+)/)[1];
				next = args[i + 1];
				if (
					next !== undefined
					&& !(/^(-|--)[^-]/).test(next)
					&& !flags.bools[key]
					&& !flags.allBools
					&& (aliases[key] ? !aliasIsBoolean(key) : true)
				) {
					setArg(key, next, arg);
					i += 1;
				} else if ((/^(true|false)$/).test(next)) {
					setArg(key, next === 'true', arg);
					i += 1;
				} else {
					setArg(key, flags.strings[key] ? '' : true, arg);
				}
			} else if ((/^-[^-]+/).test(arg)) {
				var letters = arg.slice(1, -1).split('');

				var broken = false;
				for (var j = 0; j < letters.length; j++) {
					next = arg.slice(j + 2);

					if (next === '-') {
						setArg(letters[j], next, arg);
						continue;
					}

					if ((/[A-Za-z]/).test(letters[j]) && next[0] === '=') {
						setArg(letters[j], next.slice(1), arg);
						broken = true;
						break;
					}

					if (
						(/[A-Za-z]/).test(letters[j])
						&& (/-?\d+(\.\d*)?(e-?\d+)?$/).test(next)
					) {
						setArg(letters[j], next, arg);
						broken = true;
						break;
					}

					if (letters[j + 1] && letters[j + 1].match(/\W/)) {
						setArg(letters[j], arg.slice(j + 2), arg);
						broken = true;
						break;
					} else {
						setArg(letters[j], flags.strings[letters[j]] ? '' : true, arg);
					}
				}

				key = arg.slice(-1)[0];
				if (!broken && key !== '-') {
					if (
						args[i + 1]
						&& !(/^(-|--)[^-]/).test(args[i + 1])
						&& !flags.bools[key]
						&& (aliases[key] ? !aliasIsBoolean(key) : true)
					) {
						setArg(key, args[i + 1], arg);
						i += 1;
					} else if (args[i + 1] && (/^(true|false)$/).test(args[i + 1])) {
						setArg(key, args[i + 1] === 'true', arg);
						i += 1;
					} else {
						setArg(key, flags.strings[key] ? '' : true, arg);
					}
				}
			} else {
				if (!flags.unknownFn || flags.unknownFn(arg) !== false) {
					argv._.push(flags.strings._ || !isNumber(arg) ? arg : Number(arg));
				}
				if (opts.stopEarly) {
					argv._.push.apply(argv._, args.slice(i + 1));
					break;
				}
			}
		}

		Object.keys(defaults).forEach(function (k) {
			if (!hasKey(argv, k.split('.'))) {
				setKey(argv, k.split('.'), defaults[k]);

				(aliases[k] || []).forEach(function (x) {
					setKey(argv, x.split('.'), defaults[k]);
				});
			}
		});

		if (opts['--']) {
			argv['--'] = notFlags.slice();
		} else {
			notFlags.forEach(function (k) {
				argv._.push(k);
			});
		}

		return argv;
	};
	return minimist$1;
}

var minimistExports = requireMinimist();
var minimist = /*@__PURE__*/getDefaultExportFromCjs(minimistExports);

var rw$1 = {};

var dash = {};

var decode;
var hasRequiredDecode;

function requireDecode () {
	if (hasRequiredDecode) return decode;
	hasRequiredDecode = 1;
	decode = function(options) {
	  if (options) {
	    if (typeof options === "string") return encoding(options);
	    if (options.encoding !== null) return encoding(options.encoding);
	  }
	  return identity();
	};

	function identity() {
	  var chunks = [];
	  return {
	    push: function(chunk) { chunks.push(chunk); },
	    value: function() { return Buffer.concat(chunks); }
	  };
	}

	function encoding(encoding) {
	  var chunks = [];
	  return {
	    push: function(chunk) { chunks.push(chunk); },
	    value: function() { return Buffer.concat(chunks).toString(encoding); }
	  };
	}
	return decode;
}

var readFile;
var hasRequiredReadFile;

function requireReadFile () {
	if (hasRequiredReadFile) return readFile;
	hasRequiredReadFile = 1;
	var fs = require$$0,
	    decode = requireDecode();

	readFile = function(path, options, callback) {
	  if (arguments.length < 3) callback = options, options = null;

	  switch (path) {
	    case "/dev/stdin": return readStream(process.stdin, options, callback);
	  }

	  fs.stat(path, function(error, stat) {
	    if (error) return callback(error);
	    if (stat.isFile()) return fs.readFile(path, options, callback);
	    readStream(fs.createReadStream(path, options ? {flags: options.flag || "r"} : {}), options, callback); // N.B. flag / flags
	  });
	};

	function readStream(stream, options, callback) {
	  var decoder = decode(options);
	  stream.on("error", callback);
	  stream.on("data", function(d) { decoder.push(d); });
	  stream.on("end", function() { callback(null, decoder.value()); });
	}
	return readFile;
}

var readFileSync;
var hasRequiredReadFileSync;

function requireReadFileSync () {
	if (hasRequiredReadFileSync) return readFileSync;
	hasRequiredReadFileSync = 1;
	var fs = require$$0,
	    decode = requireDecode();

	readFileSync = function(filename, options) {
	  if (fs.statSync(filename).isFile()) {
	    return fs.readFileSync(filename, options);
	  } else {
	    var fd = fs.openSync(filename, options && options.flag || "r"),
	        decoder = decode(options);

	    while (true) { // eslint-disable-line no-constant-condition
	      try {
	        var buffer = new Buffer(bufferSize),
	            bytesRead = fs.readSync(fd, buffer, 0, bufferSize);
	      } catch (e) {
	        if (e.code === "EOF") break;
	        fs.closeSync(fd);
	        throw e;
	      }
	      if (bytesRead === 0) break;
	      decoder.push(buffer.slice(0, bytesRead));
	    }

	    fs.closeSync(fd);
	    return decoder.value();
	  }
	};

	var bufferSize = 1 << 16;
	return readFileSync;
}

var encode;
var hasRequiredEncode;

function requireEncode () {
	if (hasRequiredEncode) return encode;
	hasRequiredEncode = 1;
	encode = function(data, options) {
	  return typeof data === "string"
	      ? new Buffer(data, typeof options === "string" ? options
	          : options && options.encoding !== null ? options.encoding
	          : "utf8")
	      : data;
	};
	return encode;
}

var writeFile;
var hasRequiredWriteFile;

function requireWriteFile () {
	if (hasRequiredWriteFile) return writeFile;
	hasRequiredWriteFile = 1;
	var fs = require$$0,
	    encode = requireEncode();

	writeFile = function(path, data, options, callback) {
	  if (arguments.length < 4) callback = options, options = null;

	  switch (path) {
	    case "/dev/stdout": return writeStream(process.stdout, "write", data, options, callback);
	    case "/dev/stderr": return writeStream(process.stderr, "write", data, options, callback);
	  }

	  fs.stat(path, function(error, stat) {
	    if (error && error.code !== "ENOENT") return callback(error);
	    if (stat && stat.isFile()) return fs.writeFile(path, data, options, callback);
	    writeStream(fs.createWriteStream(path, options ? {flags: options.flag || "w"} : {}), "end", data, options, callback); // N.B. flag / flags
	  });
	};

	function writeStream(stream, send, data, options, callback) {
	  stream.on("error", function(error) { callback(error.code === "EPIPE" ? null : error); }); // ignore broken pipe, e.g., | head
	  stream[send](encode(data, options), function(error) { callback(error && error.code === "EPIPE" ? null : error); });
	}
	return writeFile;
}

var writeFileSync;
var hasRequiredWriteFileSync;

function requireWriteFileSync () {
	if (hasRequiredWriteFileSync) return writeFileSync;
	hasRequiredWriteFileSync = 1;
	var fs = require$$0,
	    encode = requireEncode();

	writeFileSync = function(filename, data, options) {
	  var stat;

	  try {
	    stat = fs.statSync(filename);
	  } catch (error) {
	    if (error.code !== "ENOENT") throw error;
	  }

	  if (!stat || stat.isFile()) {
	    fs.writeFileSync(filename, data, options);
	  } else {
	    var fd = fs.openSync(filename, options && options.flag || "w"),
	        bytesWritten = 0,
	        bytesTotal = (data = encode(data, options)).length;

	    while (bytesWritten < bytesTotal) {
	      try {
	        bytesWritten += fs.writeSync(fd, data, bytesWritten, bytesTotal - bytesWritten, null);
	      } catch (error) {
	        if (error.code === "EPIPE") break; // ignore broken pipe, e.g., | head
	        fs.closeSync(fd);
	        throw error;
	      }
	    }

	    fs.closeSync(fd);
	  }
	};
	return writeFileSync;
}

var hasRequiredDash;

function requireDash () {
	if (hasRequiredDash) return dash;
	hasRequiredDash = 1;
	var slice = Array.prototype.slice;

	function dashify(method, file) {
	  return function(path) {
	    var argv = arguments;
	    if (path == "-") (argv = slice.call(argv)).splice(0, 1, file);
	    return method.apply(null, argv);
	  };
	}

	dash.readFile = dashify(requireReadFile(), "/dev/stdin");
	dash.readFileSync = dashify(requireReadFileSync(), "/dev/stdin");
	dash.writeFile = dashify(requireWriteFile(), "/dev/stdout");
	dash.writeFileSync = dashify(requireWriteFileSync(), "/dev/stdout");
	return dash;
}

var hasRequiredRw;

function requireRw () {
	if (hasRequiredRw) return rw$1;
	hasRequiredRw = 1;
	rw$1.dash = requireDash();
	rw$1.readFile = requireReadFile();
	rw$1.readFileSync = requireReadFileSync();
	rw$1.writeFile = requireWriteFile();
	rw$1.writeFileSync = requireWriteFileSync();
	return rw$1;
}

var rwExports = requireRw();
var rw = /*@__PURE__*/getDefaultExportFromCjs(rwExports);

// Note: Do not inherit from Error. It breaks when transpiling to ES5.
class ValidationError {
    constructor(key, value, message, identifier) {
        this.message = (key ? `${key}: ` : '') + message;
        if (identifier)
            this.identifier = identifier;
        if (value !== null && value !== undefined && value.__line__) {
            this.line = value.__line__;
        }
    }
}

function validateConstants(options) {
    const key = options.key;
    const constants = options.value;
    if (constants) {
        return [new ValidationError(key, constants, 'constants have been deprecated as of v8')];
    }
    else {
        return [];
    }
}

function extendBy(output, ...inputs) {
    for (const input of inputs) {
        for (const k in input) {
            output[k] = input[k];
        }
    }
    return output;
}

// Turn jsonlint-lines-primitives objects into primitive objects
function unbundle(value) {
    if (value instanceof Number || value instanceof String || value instanceof Boolean) {
        return value.valueOf();
    }
    else {
        return value;
    }
}
function deepUnbundle(value) {
    if (Array.isArray(value)) {
        return value.map(deepUnbundle);
    }
    else if (value instanceof Object && !(value instanceof Number || value instanceof String || value instanceof Boolean)) {
        const unbundledValue = {};
        for (const key in value) {
            unbundledValue[key] = deepUnbundle(value[key]);
        }
        return unbundledValue;
    }
    return unbundle(value);
}

class ExpressionParsingError extends Error {
    constructor(key, message) {
        super(message);
        this.message = message;
        this.key = key;
    }
}

/**
 * Tracks `let` bindings during expression parsing.
 * @private
 */
class Scope {
    constructor(parent, bindings = []) {
        this.parent = parent;
        this.bindings = {};
        for (const [name, expression] of bindings) {
            this.bindings[name] = expression;
        }
    }
    concat(bindings) {
        return new Scope(this, bindings);
    }
    get(name) {
        if (this.bindings[name]) {
            return this.bindings[name];
        }
        if (this.parent) {
            return this.parent.get(name);
        }
        throw new Error(`${name} not found in scope.`);
    }
    has(name) {
        if (this.bindings[name])
            return true;
        return this.parent ? this.parent.has(name) : false;
    }
}

const NullType = { kind: 'null' };
const NumberType = { kind: 'number' };
const StringType = { kind: 'string' };
const BooleanType = { kind: 'boolean' };
const ColorType = { kind: 'color' };
const ProjectionDefinitionType = { kind: 'projectionDefinition' };
const ObjectType = { kind: 'object' };
const ValueType = { kind: 'value' };
const ErrorType = { kind: 'error' };
const CollatorType = { kind: 'collator' };
const FormattedType = { kind: 'formatted' };
const PaddingType = { kind: 'padding' };
const ResolvedImageType = { kind: 'resolvedImage' };
const VariableAnchorOffsetCollectionType = { kind: 'variableAnchorOffsetCollection' };
function array(itemType, N) {
    return {
        kind: 'array',
        itemType,
        N
    };
}
function typeToString(type) {
    if (type.kind === 'array') {
        const itemType = typeToString(type.itemType);
        return typeof type.N === 'number' ?
            `array<${itemType}, ${type.N}>` :
            type.itemType.kind === 'value' ? 'array' : `array<${itemType}>`;
    }
    else {
        return type.kind;
    }
}
const valueMemberTypes = [
    NullType,
    NumberType,
    StringType,
    BooleanType,
    ColorType,
    ProjectionDefinitionType,
    FormattedType,
    ObjectType,
    array(ValueType),
    PaddingType,
    ResolvedImageType,
    VariableAnchorOffsetCollectionType
];
/**
 * Returns null if `t` is a subtype of `expected`; otherwise returns an
 * error message.
 * @private
 */
function checkSubtype(expected, t) {
    if (t.kind === 'error') {
        // Error is a subtype of every type
        return null;
    }
    else if (expected.kind === 'array') {
        if (t.kind === 'array' &&
            ((t.N === 0 && t.itemType.kind === 'value') || !checkSubtype(expected.itemType, t.itemType)) &&
            (typeof expected.N !== 'number' || expected.N === t.N)) {
            return null;
        }
    }
    else if (expected.kind === t.kind) {
        return null;
    }
    else if (expected.kind === 'value') {
        for (const memberType of valueMemberTypes) {
            if (!checkSubtype(memberType, t)) {
                return null;
            }
        }
    }
    return `Expected ${typeToString(expected)} but found ${typeToString(t)} instead.`;
}
function isValidType(provided, allowedTypes) {
    return allowedTypes.some(t => t.kind === provided.kind);
}
function isValidNativeType(provided, allowedTypes) {
    return allowedTypes.some(t => {
        if (t === 'null') {
            return provided === null;
        }
        else if (t === 'array') {
            return Array.isArray(provided);
        }
        else if (t === 'object') {
            return provided && !Array.isArray(provided) && typeof provided === 'object';
        }
        else {
            return t === typeof provided;
        }
    });
}
/**
 * Verify whether the specified type is of the same type as the specified sample.
 *
 * @param provided Type to verify
 * @param sample Sample type to reference
 * @returns `true` if both objects are of the same type, `false` otherwise
 * @example basic types
 * if (verifyType(outputType, ValueType)) {
 *     // type narrowed to:
 *     outputType.kind; // 'value'
 * }
 * @example array types
 * if (verifyType(outputType, array(NumberType))) {
 *     // type narrowed to:
 *     outputType.kind; // 'array'
 *     outputType.itemType; // NumberTypeT
 *     outputType.itemType.kind; // 'number'
 * }
 */
function verifyType(provided, sample) {
    if (provided.kind === 'array' && sample.kind === 'array') {
        return provided.itemType.kind === sample.itemType.kind && typeof provided.N === 'number';
    }
    return provided.kind === sample.kind;
}

// See https://observablehq.com/@mbostock/lab-and-rgb
const Xn = 0.96422, Yn = 1, Zn = 0.82521, t0 = 4 / 29, t1 = 6 / 29, t2 = 3 * t1 * t1, t3 = t1 * t1 * t1, deg2rad = Math.PI / 180, rad2deg = 180 / Math.PI;
function constrainAngle(angle) {
    angle = angle % 360;
    if (angle < 0) {
        angle += 360;
    }
    return angle;
}
function rgbToLab([r, g, b, alpha]) {
    r = rgb2xyz(r);
    g = rgb2xyz(g);
    b = rgb2xyz(b);
    let x, z;
    const y = xyz2lab((0.2225045 * r + 0.7168786 * g + 0.0606169 * b) / Yn);
    if (r === g && g === b) {
        x = z = y;
    }
    else {
        x = xyz2lab((0.4360747 * r + 0.3850649 * g + 0.1430804 * b) / Xn);
        z = xyz2lab((0.0139322 * r + 0.0971045 * g + 0.7141733 * b) / Zn);
    }
    const l = 116 * y - 16;
    return [(l < 0) ? 0 : l, 500 * (x - y), 200 * (y - z), alpha];
}
function rgb2xyz(x) {
    return (x <= 0.04045) ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4);
}
function xyz2lab(t) {
    return (t > t3) ? Math.pow(t, 1 / 3) : t / t2 + t0;
}
function labToRgb([l, a, b, alpha]) {
    let y = (l + 16) / 116, x = isNaN(a) ? y : y + a / 500, z = isNaN(b) ? y : y - b / 200;
    y = Yn * lab2xyz(y);
    x = Xn * lab2xyz(x);
    z = Zn * lab2xyz(z);
    return [
        xyz2rgb(3.1338561 * x - 1.6168667 * y - 0.4906146 * z), // D50 -> sRGB
        xyz2rgb(-0.9787684 * x + 1.9161415 * y + 0.0334540 * z),
        xyz2rgb(0.0719453 * x - 0.2289914 * y + 1.4052427 * z),
        alpha,
    ];
}
function xyz2rgb(x) {
    x = (x <= 0.00304) ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055;
    return (x < 0) ? 0 : (x > 1) ? 1 : x; // clip to 0..1 range
}
function lab2xyz(t) {
    return (t > t1) ? t * t * t : t2 * (t - t0);
}
function rgbToHcl(rgbColor) {
    const [l, a, b, alpha] = rgbToLab(rgbColor);
    const c = Math.sqrt(a * a + b * b);
    const h = Math.round(c * 10000) ? constrainAngle(Math.atan2(b, a) * rad2deg) : NaN;
    return [h, c, l, alpha];
}
function hclToRgb([h, c, l, alpha]) {
    h = isNaN(h) ? 0 : h * deg2rad;
    return labToRgb([l, Math.cos(h) * c, Math.sin(h) * c, alpha]);
}
// https://drafts.csswg.org/css-color-4/#hsl-to-rgb
function hslToRgb([h, s, l, alpha]) {
    h = constrainAngle(h);
    s /= 100;
    l /= 100;
    function f(n) {
        const k = (n + h / 30) % 12;
        const a = s * Math.min(l, 1 - l);
        return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
    }
    return [f(0), f(8), f(4), alpha];
}

/**
 * CSS color parser compliant with CSS Color 4 Specification.
 * Supports: named colors, `transparent` keyword, all rgb hex notations,
 * rgb(), rgba(), hsl() and hsla() functions.
 * Does not round the parsed values to integers from the range 0..255.
 *
 * Syntax:
 *
 * <alpha-value> = <number> | <percentage>
 *         <hue> = <number> | <angle>
 *
 *         rgb() = rgb( <percentage>{3} [ / <alpha-value> ]? ) | rgb( <number>{3} [ / <alpha-value> ]? )
 *         rgb() = rgb( <percentage>#{3} , <alpha-value>? )    | rgb( <number>#{3} , <alpha-value>? )
 *
 *         hsl() = hsl( <hue> <percentage> <percentage> [ / <alpha-value> ]? )
 *         hsl() = hsl( <hue>, <percentage>, <percentage>, <alpha-value>? )
 *
 * Caveats:
 *   - <angle> - <number> with optional `deg` suffix; `grad`, `rad`, `turn` are not supported
 *   - `none` keyword is not supported
 *   - comments inside rgb()/hsl() are not supported
 *   - legacy color syntax rgba() is supported with an identical grammar and behavior to rgb()
 *   - legacy color syntax hsla() is supported with an identical grammar and behavior to hsl()
 *
 * @param input CSS color string to parse.
 * @returns Color in sRGB color space, with `red`, `green`, `blue`
 * and `alpha` channels normalized to the range 0..1,
 * or `undefined` if the input is not a valid color string.
 */
function parseCssColor(input) {
    input = input.toLowerCase().trim();
    if (input === 'transparent') {
        return [0, 0, 0, 0];
    }
    // 'white', 'black', 'blue'
    const namedColorsMatch = namedColors[input];
    if (namedColorsMatch) {
        const [r, g, b] = namedColorsMatch;
        return [r / 255, g / 255, b / 255, 1];
    }
    // #f0c, #f0cf, #ff00cc, #ff00ccff
    if (input.startsWith('#')) {
        const hexRegexp = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/;
        if (hexRegexp.test(input)) {
            const step = input.length < 6 ? 1 : 2;
            let i = 1;
            return [
                parseHex(input.slice(i, i += step)),
                parseHex(input.slice(i, i += step)),
                parseHex(input.slice(i, i += step)),
                parseHex(input.slice(i, i + step) || 'ff'),
            ];
        }
    }
    // rgb(128 0 0), rgb(50% 0% 0%), rgba(255,0,255,0.6), rgb(255 0 255 / 60%), rgb(100% 0% 100% /.6)
    if (input.startsWith('rgb')) {
        const rgbRegExp = /^rgba?\(\s*([\de.+-]+)(%)?(?:\s+|\s*(,)\s*)([\de.+-]+)(%)?(?:\s+|\s*(,)\s*)([\de.+-]+)(%)?(?:\s*([,\/])\s*([\de.+-]+)(%)?)?\s*\)$/;
        const rgbMatch = input.match(rgbRegExp);
        if (rgbMatch) {
            const [_, // eslint-disable-line @typescript-eslint/no-unused-vars
            r, // <numeric>
            rp, // %         (optional)
            f1, // ,         (optional)
            g, // <numeric>
            gp, // %         (optional)
            f2, // ,         (optional)
            b, // <numeric>
            bp, // %         (optional)
            f3, // ,|/       (optional)
            a, // <numeric> (optional)
            ap, // %         (optional)
            ] = rgbMatch;
            const argFormat = [f1 || ' ', f2 || ' ', f3].join('');
            if (argFormat === '  ' ||
                argFormat === '  /' ||
                argFormat === ',,' ||
                argFormat === ',,,') {
                const valFormat = [rp, gp, bp].join('');
                const maxValue = (valFormat === '%%%') ? 100 :
                    (valFormat === '') ? 255 : 0;
                if (maxValue) {
                    const rgba = [
                        clamp(+r / maxValue, 0, 1),
                        clamp(+g / maxValue, 0, 1),
                        clamp(+b / maxValue, 0, 1),
                        a ? parseAlpha(+a, ap) : 1,
                    ];
                    if (validateNumbers(rgba)) {
                        return rgba;
                    }
                    // invalid numbers
                }
                // values must be all numbers or all percentages
            }
            return; // comma optional syntax requires no commas at all
        }
    }
    // hsl(120 50% 80%), hsla(120deg,50%,80%,.9), hsl(12e1 50% 80% / 90%)
    const hslRegExp = /^hsla?\(\s*([\de.+-]+)(?:deg)?(?:\s+|\s*(,)\s*)([\de.+-]+)%(?:\s+|\s*(,)\s*)([\de.+-]+)%(?:\s*([,\/])\s*([\de.+-]+)(%)?)?\s*\)$/;
    const hslMatch = input.match(hslRegExp);
    if (hslMatch) {
        const [_, // eslint-disable-line @typescript-eslint/no-unused-vars
        h, // <numeric>
        f1, // ,         (optional)
        s, // <numeric>
        f2, // ,         (optional)
        l, // <numeric>
        f3, // ,|/       (optional)
        a, // <numeric> (optional)
        ap, // %         (optional)
        ] = hslMatch;
        const argFormat = [f1 || ' ', f2 || ' ', f3].join('');
        if (argFormat === '  ' ||
            argFormat === '  /' ||
            argFormat === ',,' ||
            argFormat === ',,,') {
            const hsla = [
                +h,
                clamp(+s, 0, 100),
                clamp(+l, 0, 100),
                a ? parseAlpha(+a, ap) : 1,
            ];
            if (validateNumbers(hsla)) {
                return hslToRgb(hsla);
            }
            // invalid numbers
        }
        // comma optional syntax requires no commas at all
    }
}
function parseHex(hex) {
    return parseInt(hex.padEnd(2, hex), 16) / 255;
}
function parseAlpha(a, asPercentage) {
    return clamp(asPercentage ? (a / 100) : a, 0, 1);
}
function clamp(n, min, max) {
    return Math.min(Math.max(min, n), max);
}
/**
 * The regular expression for numeric values is not super specific, and it may
 * happen that it will accept a value that is not a valid number. In order to
 * detect and eliminate such values this function exists.
 *
 * @param array Array of uncertain numbers.
 * @returns `true` if the specified array contains only valid numbers, `false` otherwise.
 */
function validateNumbers(array) {
    return !array.some(Number.isNaN);
}
/**
 * To generate:
 * - visit {@link https://www.w3.org/TR/css-color-4/#named-colors}
 * - run in the console:
 * @example
 * copy(`{\n${[...document.querySelector('.named-color-table tbody').children].map((tr) => `${tr.cells[2].textContent.trim()}: [${tr.cells[4].textContent.trim().split(/\s+/).join(', ')}],`).join('\n')}\n}`);
 */
const namedColors = {
    aliceblue: [240, 248, 255],
    antiquewhite: [250, 235, 215],
    aqua: [0, 255, 255],
    aquamarine: [127, 255, 212],
    azure: [240, 255, 255],
    beige: [245, 245, 220],
    bisque: [255, 228, 196],
    black: [0, 0, 0],
    blanchedalmond: [255, 235, 205],
    blue: [0, 0, 255],
    blueviolet: [138, 43, 226],
    brown: [165, 42, 42],
    burlywood: [222, 184, 135],
    cadetblue: [95, 158, 160],
    chartreuse: [127, 255, 0],
    chocolate: [210, 105, 30],
    coral: [255, 127, 80],
    cornflowerblue: [100, 149, 237],
    cornsilk: [255, 248, 220],
    crimson: [220, 20, 60],
    cyan: [0, 255, 255],
    darkblue: [0, 0, 139],
    darkcyan: [0, 139, 139],
    darkgoldenrod: [184, 134, 11],
    darkgray: [169, 169, 169],
    darkgreen: [0, 100, 0],
    darkgrey: [169, 169, 169],
    darkkhaki: [189, 183, 107],
    darkmagenta: [139, 0, 139],
    darkolivegreen: [85, 107, 47],
    darkorange: [255, 140, 0],
    darkorchid: [153, 50, 204],
    darkred: [139, 0, 0],
    darksalmon: [233, 150, 122],
    darkseagreen: [143, 188, 143],
    darkslateblue: [72, 61, 139],
    darkslategray: [47, 79, 79],
    darkslategrey: [47, 79, 79],
    darkturquoise: [0, 206, 209],
    darkviolet: [148, 0, 211],
    deeppink: [255, 20, 147],
    deepskyblue: [0, 191, 255],
    dimgray: [105, 105, 105],
    dimgrey: [105, 105, 105],
    dodgerblue: [30, 144, 255],
    firebrick: [178, 34, 34],
    floralwhite: [255, 250, 240],
    forestgreen: [34, 139, 34],
    fuchsia: [255, 0, 255],
    gainsboro: [220, 220, 220],
    ghostwhite: [248, 248, 255],
    gold: [255, 215, 0],
    goldenrod: [218, 165, 32],
    gray: [128, 128, 128],
    green: [0, 128, 0],
    greenyellow: [173, 255, 47],
    grey: [128, 128, 128],
    honeydew: [240, 255, 240],
    hotpink: [255, 105, 180],
    indianred: [205, 92, 92],
    indigo: [75, 0, 130],
    ivory: [255, 255, 240],
    khaki: [240, 230, 140],
    lavender: [230, 230, 250],
    lavenderblush: [255, 240, 245],
    lawngreen: [124, 252, 0],
    lemonchiffon: [255, 250, 205],
    lightblue: [173, 216, 230],
    lightcoral: [240, 128, 128],
    lightcyan: [224, 255, 255],
    lightgoldenrodyellow: [250, 250, 210],
    lightgray: [211, 211, 211],
    lightgreen: [144, 238, 144],
    lightgrey: [211, 211, 211],
    lightpink: [255, 182, 193],
    lightsalmon: [255, 160, 122],
    lightseagreen: [32, 178, 170],
    lightskyblue: [135, 206, 250],
    lightslategray: [119, 136, 153],
    lightslategrey: [119, 136, 153],
    lightsteelblue: [176, 196, 222],
    lightyellow: [255, 255, 224],
    lime: [0, 255, 0],
    limegreen: [50, 205, 50],
    linen: [250, 240, 230],
    magenta: [255, 0, 255],
    maroon: [128, 0, 0],
    mediumaquamarine: [102, 205, 170],
    mediumblue: [0, 0, 205],
    mediumorchid: [186, 85, 211],
    mediumpurple: [147, 112, 219],
    mediumseagreen: [60, 179, 113],
    mediumslateblue: [123, 104, 238],
    mediumspringgreen: [0, 250, 154],
    mediumturquoise: [72, 209, 204],
    mediumvioletred: [199, 21, 133],
    midnightblue: [25, 25, 112],
    mintcream: [245, 255, 250],
    mistyrose: [255, 228, 225],
    moccasin: [255, 228, 181],
    navajowhite: [255, 222, 173],
    navy: [0, 0, 128],
    oldlace: [253, 245, 230],
    olive: [128, 128, 0],
    olivedrab: [107, 142, 35],
    orange: [255, 165, 0],
    orangered: [255, 69, 0],
    orchid: [218, 112, 214],
    palegoldenrod: [238, 232, 170],
    palegreen: [152, 251, 152],
    paleturquoise: [175, 238, 238],
    palevioletred: [219, 112, 147],
    papayawhip: [255, 239, 213],
    peachpuff: [255, 218, 185],
    peru: [205, 133, 63],
    pink: [255, 192, 203],
    plum: [221, 160, 221],
    powderblue: [176, 224, 230],
    purple: [128, 0, 128],
    rebeccapurple: [102, 51, 153],
    red: [255, 0, 0],
    rosybrown: [188, 143, 143],
    royalblue: [65, 105, 225],
    saddlebrown: [139, 69, 19],
    salmon: [250, 128, 114],
    sandybrown: [244, 164, 96],
    seagreen: [46, 139, 87],
    seashell: [255, 245, 238],
    sienna: [160, 82, 45],
    silver: [192, 192, 192],
    skyblue: [135, 206, 235],
    slateblue: [106, 90, 205],
    slategray: [112, 128, 144],
    slategrey: [112, 128, 144],
    snow: [255, 250, 250],
    springgreen: [0, 255, 127],
    steelblue: [70, 130, 180],
    tan: [210, 180, 140],
    teal: [0, 128, 128],
    thistle: [216, 191, 216],
    tomato: [255, 99, 71],
    turquoise: [64, 224, 208],
    violet: [238, 130, 238],
    wheat: [245, 222, 179],
    white: [255, 255, 255],
    whitesmoke: [245, 245, 245],
    yellow: [255, 255, 0],
    yellowgreen: [154, 205, 50],
};

function interpolateNumber(from, to, t) {
    return from + t * (to - from);
}
function interpolateArray(from, to, t) {
    return from.map((d, i) => {
        return interpolateNumber(d, to[i], t);
    });
}

/**
 * Color representation used by WebGL.
 * Defined in sRGB color space and pre-blended with alpha.
 * @private
 */
class Color {
    /**
     * @param r Red component premultiplied by `alpha` 0..1
     * @param g Green component premultiplied by `alpha` 0..1
     * @param b Blue component premultiplied by `alpha` 0..1
     * @param [alpha=1] Alpha component 0..1
     * @param [premultiplied=true] Whether the `r`, `g` and `b` values have already
     * been multiplied by alpha. If `true` nothing happens if `false` then they will
     * be multiplied automatically.
     */
    constructor(r, g, b, alpha = 1, premultiplied = true) {
        this.r = r;
        this.g = g;
        this.b = b;
        this.a = alpha;
        if (!premultiplied) {
            this.r *= alpha;
            this.g *= alpha;
            this.b *= alpha;
            if (!alpha) {
                // alpha = 0 erases completely rgb channels. This behavior is not desirable
                // if this particular color is later used in color interpolation.
                // Because of that, a reference to original color is saved.
                this.overwriteGetter('rgb', [r, g, b, alpha]);
            }
        }
    }
    /**
     * Parses CSS color strings and converts colors to sRGB color space if needed.
     * Officially supported color formats:
     * - keyword, e.g. 'aquamarine' or 'steelblue'
     * - hex (with 3, 4, 6 or 8 digits), e.g. '#f0f' or '#e9bebea9'
     * - rgb and rgba, e.g. 'rgb(0,240,120)' or 'rgba(0%,94%,47%,0.1)' or 'rgb(0 240 120 / .3)'
     * - hsl and hsla, e.g. 'hsl(0,0%,83%)' or 'hsla(0,0%,83%,.5)' or 'hsl(0 0% 83% / 20%)'
     *
     * @param input CSS color string to parse.
     * @returns A `Color` instance, or `undefined` if the input is not a valid color string.
     */
    static parse(input) {
        // in zoom-and-property function input could be an instance of Color class
        if (input instanceof Color) {
            return input;
        }
        if (typeof input !== 'string') {
            return;
        }
        const rgba = parseCssColor(input);
        if (rgba) {
            return new Color(...rgba, false);
        }
    }
    /**
     * Used in color interpolation and by 'to-rgba' expression.
     *
     * @returns Gien color, with reversed alpha blending, in sRGB color space.
     */
    get rgb() {
        const { r, g, b, a } = this;
        const f = a || Infinity; // reverse alpha blending factor
        return this.overwriteGetter('rgb', [r / f, g / f, b / f, a]);
    }
    /**
     * Used in color interpolation.
     *
     * @returns Gien color, with reversed alpha blending, in HCL color space.
     */
    get hcl() {
        return this.overwriteGetter('hcl', rgbToHcl(this.rgb));
    }
    /**
     * Used in color interpolation.
     *
     * @returns Gien color, with reversed alpha blending, in LAB color space.
     */
    get lab() {
        return this.overwriteGetter('lab', rgbToLab(this.rgb));
    }
    /**
     * Lazy getter pattern. When getter is called for the first time lazy value
     * is calculated and then overwrites getter function in given object instance.
     *
     * @example:
     * const redColor = Color.parse('red');
     * let x = redColor.hcl; // this will invoke `get hcl()`, which will calculate
     * // the value of red in HCL space and invoke this `overwriteGetter` function
     * // which in turn will set a field with a key 'hcl' in the `redColor` object.
     * // In other words it will override `get hcl()` from its `Color` prototype
     * // with its own property: hcl = [calculated red value in hcl].
     * let y = redColor.hcl; // next call will no longer invoke getter but simply
     * // return the previously calculated value
     * x === y; // true - `x` is exactly the same object as `y`
     *
     * @param getterKey Getter key
     * @param lazyValue Lazily calculated value to be memoized by current instance
     * @private
     */
    overwriteGetter(getterKey, lazyValue) {
        Object.defineProperty(this, getterKey, { value: lazyValue });
        return lazyValue;
    }
    /**
     * Used by 'to-string' expression.
     *
     * @returns Serialized color in format `rgba(r,g,b,a)`
     * where r,g,b are numbers within 0..255 and alpha is number within 1..0
     *
     * @example
     * var purple = new Color.parse('purple');
     * purple.toString; // = "rgba(128,0,128,1)"
     * var translucentGreen = new Color.parse('rgba(26, 207, 26, .73)');
     * translucentGreen.toString(); // = "rgba(26,207,26,0.73)"
     */
    toString() {
        const [r, g, b, a] = this.rgb;
        return `rgba(${[r, g, b].map(n => Math.round(n * 255)).join(',')},${a})`;
    }
    static interpolate(from, to, t, spaceKey = 'rgb') {
        switch (spaceKey) {
            case 'rgb': {
                const [r, g, b, alpha] = interpolateArray(from.rgb, to.rgb, t);
                return new Color(r, g, b, alpha, false);
            }
            case 'hcl': {
                const [hue0, chroma0, light0, alphaF] = from.hcl;
                const [hue1, chroma1, light1, alphaT] = to.hcl;
                // https://github.com/gka/chroma.js/blob/cd1b3c0926c7a85cbdc3b1453b3a94006de91a92/src/interpolator/_hsx.js
                let hue, chroma;
                if (!isNaN(hue0) && !isNaN(hue1)) {
                    let dh = hue1 - hue0;
                    if (hue1 > hue0 && dh > 180) {
                        dh -= 360;
                    }
                    else if (hue1 < hue0 && hue0 - hue1 > 180) {
                        dh += 360;
                    }
                    hue = hue0 + t * dh;
                }
                else if (!isNaN(hue0)) {
                    hue = hue0;
                    if (light1 === 1 || light1 === 0)
                        chroma = chroma0;
                }
                else if (!isNaN(hue1)) {
                    hue = hue1;
                    if (light0 === 1 || light0 === 0)
                        chroma = chroma1;
                }
                else {
                    hue = NaN;
                }
                const [r, g, b, alpha] = hclToRgb([
                    hue,
                    chroma !== null && chroma !== undefined ? chroma : interpolateNumber(chroma0, chroma1, t),
                    interpolateNumber(light0, light1, t),
                    interpolateNumber(alphaF, alphaT, t),
                ]);
                return new Color(r, g, b, alpha, false);
            }
            case 'lab': {
                const [r, g, b, alpha] = labToRgb(interpolateArray(from.lab, to.lab, t));
                return new Color(r, g, b, alpha, false);
            }
        }
    }
}
Color.black = new Color(0, 0, 0, 1);
Color.white = new Color(1, 1, 1, 1);
Color.transparent = new Color(0, 0, 0, 0);
Color.red = new Color(1, 0, 0, 1);

// Flow type declarations for Intl cribbed from
// https://github.com/facebook/flow/issues/1270
class Collator {
    constructor(caseSensitive, diacriticSensitive, locale) {
        if (caseSensitive)
            this.sensitivity = diacriticSensitive ? 'variant' : 'case';
        else
            this.sensitivity = diacriticSensitive ? 'accent' : 'base';
        this.locale = locale;
        this.collator = new Intl.Collator(this.locale ? this.locale : [], { sensitivity: this.sensitivity, usage: 'search' });
    }
    compare(lhs, rhs) {
        return this.collator.compare(lhs, rhs);
    }
    resolvedLocale() {
        // We create a Collator without "usage: search" because we don't want
        // the search options encoded in our result (e.g. "en-u-co-search")
        return new Intl.Collator(this.locale ? this.locale : [])
            .resolvedOptions().locale;
    }
}

const VERTICAL_ALIGN_OPTIONS = ['bottom', 'center', 'top'];
class FormattedSection {
    constructor(text, image, scale, fontStack, textColor, verticalAlign) {
        this.text = text;
        this.image = image;
        this.scale = scale;
        this.fontStack = fontStack;
        this.textColor = textColor;
        this.verticalAlign = verticalAlign;
    }
}
class Formatted {
    constructor(sections) {
        this.sections = sections;
    }
    static fromString(unformatted) {
        return new Formatted([new FormattedSection(unformatted, null, null, null, null, null)]);
    }
    isEmpty() {
        if (this.sections.length === 0)
            return true;
        return !this.sections.some(section => section.text.length !== 0 ||
            (section.image && section.image.name.length !== 0));
    }
    static factory(text) {
        if (text instanceof Formatted) {
            return text;
        }
        else {
            return Formatted.fromString(text);
        }
    }
    toString() {
        if (this.sections.length === 0)
            return '';
        return this.sections.map(section => section.text).join('');
    }
}

/**
 * A set of four numbers representing padding around a box. Create instances from
 * bare arrays or numeric values using the static method `Padding.parse`.
 * @private
 */
class Padding {
    constructor(values) {
        this.values = values.slice();
    }
    /**
     * Numeric padding values
     * @param input A padding value
     * @returns A `Padding` instance, or `undefined` if the input is not a valid padding value.
     */
    static parse(input) {
        if (input instanceof Padding) {
            return input;
        }
        // Backwards compatibility: bare number is treated the same as array with single value.
        // Padding applies to all four sides.
        if (typeof input === 'number') {
            return new Padding([input, input, input, input]);
        }
        if (!Array.isArray(input)) {
            return undefined;
        }
        if (input.length < 1 || input.length > 4) {
            return undefined;
        }
        for (const val of input) {
            if (typeof val !== 'number') {
                return undefined;
            }
        }
        // Expand shortcut properties into explicit 4-sided values
        switch (input.length) {
            case 1:
                input = [input[0], input[0], input[0], input[0]];
                break;
            case 2:
                input = [input[0], input[1], input[0], input[1]];
                break;
            case 3:
                input = [input[0], input[1], input[2], input[1]];
                break;
        }
        return new Padding(input);
    }
    toString() {
        return JSON.stringify(this.values);
    }
    static interpolate(from, to, t) {
        return new Padding(interpolateArray(from.values, to.values, t));
    }
}

class RuntimeError {
    constructor(message) {
        this.name = 'ExpressionEvaluationError';
        this.message = message;
    }
    toJSON() {
        return this.message;
    }
}

/** Set of valid anchor positions, as a set for validation */
const anchors = new Set(['center', 'left', 'right', 'top', 'bottom', 'top-left', 'top-right', 'bottom-left', 'bottom-right']);
/**
 * Utility class to assist managing values for text-variable-anchor-offset property. Create instances from
 * bare arrays using the static method `VariableAnchorOffsetCollection.parse`.
 * @private
 */
class VariableAnchorOffsetCollection {
    constructor(values) {
        this.values = values.slice();
    }
    static parse(input) {
        if (input instanceof VariableAnchorOffsetCollection) {
            return input;
        }
        if (!Array.isArray(input) ||
            input.length < 1 ||
            input.length % 2 !== 0) {
            return undefined;
        }
        for (let i = 0; i < input.length; i += 2) {
            // Elements in even positions should be anchor positions; Elements in odd positions should be offset values
            const anchorValue = input[i];
            const offsetValue = input[i + 1];
            if (typeof anchorValue !== 'string' || !anchors.has(anchorValue)) {
                return undefined;
            }
            if (!Array.isArray(offsetValue) || offsetValue.length !== 2 || typeof offsetValue[0] !== 'number' || typeof offsetValue[1] !== 'number') {
                return undefined;
            }
        }
        return new VariableAnchorOffsetCollection(input);
    }
    toString() {
        return JSON.stringify(this.values);
    }
    static interpolate(from, to, t) {
        const fromValues = from.values;
        const toValues = to.values;
        if (fromValues.length !== toValues.length) {
            throw new RuntimeError(`Cannot interpolate values of different length. from: ${from.toString()}, to: ${to.toString()}`);
        }
        const output = [];
        for (let i = 0; i < fromValues.length; i += 2) {
            // Anchor entries must match
            if (fromValues[i] !== toValues[i]) {
                throw new RuntimeError(`Cannot interpolate values containing mismatched anchors. from[${i}]: ${fromValues[i]}, to[${i}]: ${toValues[i]}`);
            }
            output.push(fromValues[i]);
            // Interpolate the offset values for each anchor
            const [fx, fy] = fromValues[i + 1];
            const [tx, ty] = toValues[i + 1];
            output.push([interpolateNumber(fx, tx, t), interpolateNumber(fy, ty, t)]);
        }
        return new VariableAnchorOffsetCollection(output);
    }
}

class ResolvedImage {
    constructor(options) {
        this.name = options.name;
        this.available = options.available;
    }
    toString() {
        return this.name;
    }
    static fromString(name) {
        if (!name)
            return null; // treat empty values as no image
        return new ResolvedImage({ name, available: false });
    }
}

class ProjectionDefinition {
    constructor(from, to, transition) {
        this.from = from;
        this.to = to;
        this.transition = transition;
    }
    static interpolate(from, to, t) {
        return new ProjectionDefinition(from, to, t);
    }
    static parse(input) {
        if (input instanceof ProjectionDefinition) {
            return input;
        }
        if (Array.isArray(input) && input.length === 3 && typeof input[0] === 'string' && typeof input[1] === 'string' && typeof input[2] === 'number') {
            return new ProjectionDefinition(input[0], input[1], input[2]);
        }
        if (typeof input === 'object' && typeof input.from === 'string' && typeof input.to === 'string' && typeof input.transition === 'number') {
            return new ProjectionDefinition(input.from, input.to, input.transition);
        }
        if (typeof input === 'string') {
            return new ProjectionDefinition(input, input, 1);
        }
        return undefined;
    }
}

function validateRGBA(r, g, b, a) {
    if (!(typeof r === 'number' && r >= 0 && r <= 255 &&
        typeof g === 'number' && g >= 0 && g <= 255 &&
        typeof b === 'number' && b >= 0 && b <= 255)) {
        const value = typeof a === 'number' ? [r, g, b, a] : [r, g, b];
        return `Invalid rgba value [${value.join(', ')}]: 'r', 'g', and 'b' must be between 0 and 255.`;
    }
    if (!(typeof a === 'undefined' || (typeof a === 'number' && a >= 0 && a <= 1))) {
        return `Invalid rgba value [${[r, g, b, a].join(', ')}]: 'a' must be between 0 and 1.`;
    }
    return null;
}
function isValue(mixed) {
    if (mixed === null ||
        typeof mixed === 'string' ||
        typeof mixed === 'boolean' ||
        typeof mixed === 'number' ||
        mixed instanceof ProjectionDefinition ||
        mixed instanceof Color ||
        mixed instanceof Collator ||
        mixed instanceof Formatted ||
        mixed instanceof Padding ||
        mixed instanceof VariableAnchorOffsetCollection ||
        mixed instanceof ResolvedImage) {
        return true;
    }
    else if (Array.isArray(mixed)) {
        for (const item of mixed) {
            if (!isValue(item)) {
                return false;
            }
        }
        return true;
    }
    else if (typeof mixed === 'object') {
        for (const key in mixed) {
            if (!isValue(mixed[key])) {
                return false;
            }
        }
        return true;
    }
    else {
        return false;
    }
}
function typeOf(value) {
    if (value === null) {
        return NullType;
    }
    else if (typeof value === 'string') {
        return StringType;
    }
    else if (typeof value === 'boolean') {
        return BooleanType;
    }
    else if (typeof value === 'number') {
        return NumberType;
    }
    else if (value instanceof Color) {
        return ColorType;
    }
    else if (value instanceof ProjectionDefinition) {
        return ProjectionDefinitionType;
    }
    else if (value instanceof Collator) {
        return CollatorType;
    }
    else if (value instanceof Formatted) {
        return FormattedType;
    }
    else if (value instanceof Padding) {
        return PaddingType;
    }
    else if (value instanceof VariableAnchorOffsetCollection) {
        return VariableAnchorOffsetCollectionType;
    }
    else if (value instanceof ResolvedImage) {
        return ResolvedImageType;
    }
    else if (Array.isArray(value)) {
        const length = value.length;
        let itemType;
        for (const item of value) {
            const t = typeOf(item);
            if (!itemType) {
                itemType = t;
            }
            else if (itemType === t) {
                continue;
            }
            else {
                itemType = ValueType;
                break;
            }
        }
        return array(itemType || ValueType, length);
    }
    else {
        return ObjectType;
    }
}
function valueToString(value) {
    const type = typeof value;
    if (value === null) {
        return '';
    }
    else if (type === 'string' || type === 'number' || type === 'boolean') {
        return String(value);
    }
    else if (value instanceof Color || value instanceof ProjectionDefinition || value instanceof Formatted || value instanceof Padding || value instanceof VariableAnchorOffsetCollection || value instanceof ResolvedImage) {
        return value.toString();
    }
    else {
        return JSON.stringify(value);
    }
}

class Literal {
    constructor(type, value) {
        this.type = type;
        this.value = value;
    }
    static parse(args, context) {
        if (args.length !== 2)
            return context.error(`'literal' expression requires exactly one argument, but found ${args.length - 1} instead.`);
        if (!isValue(args[1]))
            return context.error('invalid value');
        const value = args[1];
        let type = typeOf(value);
        // special case: infer the item type if possible for zero-length arrays
        const expected = context.expectedType;
        if (type.kind === 'array' &&
            type.N === 0 &&
            expected &&
            expected.kind === 'array' &&
            (typeof expected.N !== 'number' || expected.N === 0)) {
            type = expected;
        }
        return new Literal(type, value);
    }
    evaluate() {
        return this.value;
    }
    eachChild() { }
    outputDefined() {
        return true;
    }
}

const types$1 = {
    string: StringType,
    number: NumberType,
    boolean: BooleanType,
    object: ObjectType
};
class Assertion {
    constructor(type, args) {
        this.type = type;
        this.args = args;
    }
    static parse(args, context) {
        if (args.length < 2)
            return context.error('Expected at least one argument.');
        let i = 1;
        let type;
        const name = args[0];
        if (name === 'array') {
            let itemType;
            if (args.length > 2) {
                const type = args[1];
                if (typeof type !== 'string' || !(type in types$1) || type === 'object')
                    return context.error('The item type argument of "array" must be one of string, number, boolean', 1);
                itemType = types$1[type];
                i++;
            }
            else {
                itemType = ValueType;
            }
            let N;
            if (args.length > 3) {
                if (args[2] !== null &&
                    (typeof args[2] !== 'number' ||
                        args[2] < 0 ||
                        args[2] !== Math.floor(args[2]))) {
                    return context.error('The length argument to "array" must be a positive integer literal', 2);
                }
                N = args[2];
                i++;
            }
            type = array(itemType, N);
        }
        else {
            if (!types$1[name])
                throw new Error(`Types doesn't contain name = ${name}`);
            type = types$1[name];
        }
        const parsed = [];
        for (; i < args.length; i++) {
            const input = context.parse(args[i], i, ValueType);
            if (!input)
                return null;
            parsed.push(input);
        }
        return new Assertion(type, parsed);
    }
    evaluate(ctx) {
        for (let i = 0; i < this.args.length; i++) {
            const value = this.args[i].evaluate(ctx);
            const error = checkSubtype(this.type, typeOf(value));
            if (!error) {
                return value;
            }
            else if (i === this.args.length - 1) {
                throw new RuntimeError(`Expected value to be of type ${typeToString(this.type)}, but found ${typeToString(typeOf(value))} instead.`);
            }
        }
        throw new Error();
    }
    eachChild(fn) {
        this.args.forEach(fn);
    }
    outputDefined() {
        return this.args.every(arg => arg.outputDefined());
    }
}

const types = {
    'to-boolean': BooleanType,
    'to-color': ColorType,
    'to-number': NumberType,
    'to-string': StringType
};
/**
 * Special form for error-coalescing coercion expressions "to-number",
 * "to-color".  Since these coercions can fail at runtime, they accept multiple
 * arguments, only evaluating one at a time until one succeeds.
 *
 * @private
 */
class Coercion {
    constructor(type, args) {
        this.type = type;
        this.args = args;
    }
    static parse(args, context) {
        if (args.length < 2)
            return context.error('Expected at least one argument.');
        const name = args[0];
        if (!types[name])
            throw new Error(`Can't parse ${name} as it is not part of the known types`);
        if ((name === 'to-boolean' || name === 'to-string') && args.length !== 2)
            return context.error('Expected one argument.');
        const type = types[name];
        const parsed = [];
        for (let i = 1; i < args.length; i++) {
            const input = context.parse(args[i], i, ValueType);
            if (!input)
                return null;
            parsed.push(input);
        }
        return new Coercion(type, parsed);
    }
    evaluate(ctx) {
        switch (this.type.kind) {
            case 'boolean':
                return Boolean(this.args[0].evaluate(ctx));
            case 'color': {
                let input;
                let error;
                for (const arg of this.args) {
                    input = arg.evaluate(ctx);
                    error = null;
                    if (input instanceof Color) {
                        return input;
                    }
                    else if (typeof input === 'string') {
                        const c = ctx.parseColor(input);
                        if (c)
                            return c;
                    }
                    else if (Array.isArray(input)) {
                        if (input.length < 3 || input.length > 4) {
                            error = `Invalid rgba value ${JSON.stringify(input)}: expected an array containing either three or four numeric values.`;
                        }
                        else {
                            error = validateRGBA(input[0], input[1], input[2], input[3]);
                        }
                        if (!error) {
                            return new Color(input[0] / 255, input[1] / 255, input[2] / 255, input[3]);
                        }
                    }
                }
                throw new RuntimeError(error || `Could not parse color from value '${typeof input === 'string' ? input : JSON.stringify(input)}'`);
            }
            case 'padding': {
                let input;
                for (const arg of this.args) {
                    input = arg.evaluate(ctx);
                    const pad = Padding.parse(input);
                    if (pad) {
                        return pad;
                    }
                }
                throw new RuntimeError(`Could not parse padding from value '${typeof input === 'string' ? input : JSON.stringify(input)}'`);
            }
            case 'variableAnchorOffsetCollection': {
                let input;
                for (const arg of this.args) {
                    input = arg.evaluate(ctx);
                    const coll = VariableAnchorOffsetCollection.parse(input);
                    if (coll) {
                        return coll;
                    }
                }
                throw new RuntimeError(`Could not parse variableAnchorOffsetCollection from value '${typeof input === 'string' ? input : JSON.stringify(input)}'`);
            }
            case 'number': {
                let value = null;
                for (const arg of this.args) {
                    value = arg.evaluate(ctx);
                    if (value === null)
                        return 0;
                    const num = Number(value);
                    if (isNaN(num))
                        continue;
                    return num;
                }
                throw new RuntimeError(`Could not convert ${JSON.stringify(value)} to number.`);
            }
            case 'formatted':
                // There is no explicit 'to-formatted' but this coercion can be implicitly
                // created by properties that expect the 'formatted' type.
                return Formatted.fromString(valueToString(this.args[0].evaluate(ctx)));
            case 'resolvedImage':
                return ResolvedImage.fromString(valueToString(this.args[0].evaluate(ctx)));
            case 'projectionDefinition':
                return this.args[0].evaluate(ctx);
            default:
                return valueToString(this.args[0].evaluate(ctx));
        }
    }
    eachChild(fn) {
        this.args.forEach(fn);
    }
    outputDefined() {
        return this.args.every(arg => arg.outputDefined());
    }
}

const geometryTypes = ['Unknown', 'Point', 'LineString', 'Polygon'];
class EvaluationContext {
    constructor() {
        this.globals = null;
        this.feature = null;
        this.featureState = null;
        this.formattedSection = null;
        this._parseColorCache = {};
        this.availableImages = null;
        this.canonical = null;
    }
    id() {
        return this.feature && 'id' in this.feature ? this.feature.id : null;
    }
    geometryType() {
        return this.feature ? typeof this.feature.type === 'number' ? geometryTypes[this.feature.type] : this.feature.type : null;
    }
    geometry() {
        return this.feature && 'geometry' in this.feature ? this.feature.geometry : null;
    }
    canonicalID() {
        return this.canonical;
    }
    properties() {
        return this.feature && this.feature.properties || {};
    }
    parseColor(input) {
        let cached = this._parseColorCache[input];
        if (!cached) {
            cached = this._parseColorCache[input] = Color.parse(input);
        }
        return cached;
    }
}

/**
 * State associated parsing at a given point in an expression tree.
 * @private
 */
class ParsingContext {
    constructor(registry, isConstantFunc, path = [], expectedType, scope = new Scope(), errors = []) {
        this.registry = registry;
        this.path = path;
        this.key = path.map(part => `[${part}]`).join('');
        this.scope = scope;
        this.errors = errors;
        this.expectedType = expectedType;
        this._isConstant = isConstantFunc;
    }
    /**
     * @param expr the JSON expression to parse
     * @param index the optional argument index if this expression is an argument of a parent expression that's being parsed
     * @param options
     * @param options.omitTypeAnnotations set true to omit inferred type annotations.  Caller beware: with this option set, the parsed expression's type will NOT satisfy `expectedType` if it would normally be wrapped in an inferred annotation.
     * @private
     */
    parse(expr, index, expectedType, bindings, options = {}) {
        if (index) {
            return this.concat(index, expectedType, bindings)._parse(expr, options);
        }
        return this._parse(expr, options);
    }
    _parse(expr, options) {
        if (expr === null || typeof expr === 'string' || typeof expr === 'boolean' || typeof expr === 'number') {
            expr = ['literal', expr];
        }
        function annotate(parsed, type, typeAnnotation) {
            if (typeAnnotation === 'assert') {
                return new Assertion(type, [parsed]);
            }
            else if (typeAnnotation === 'coerce') {
                return new Coercion(type, [parsed]);
            }
            else {
                return parsed;
            }
        }
        if (Array.isArray(expr)) {
            if (expr.length === 0) {
                return this.error('Expected an array with at least one element. If you wanted a literal array, use ["literal", []].');
            }
            const op = expr[0];
            if (typeof op !== 'string') {
                this.error(`Expression name must be a string, but found ${typeof op} instead. If you wanted a literal array, use ["literal", [...]].`, 0);
                return null;
            }
            const Expr = this.registry[op];
            if (Expr) {
                let parsed = Expr.parse(expr, this);
                if (!parsed)
                    return null;
                if (this.expectedType) {
                    const expected = this.expectedType;
                    const actual = parsed.type;
                    // When we expect a number, string, boolean, or array but have a value, wrap it in an assertion.
                    // When we expect a color or formatted string, but have a string or value, wrap it in a coercion.
                    // Otherwise, we do static type-checking.
                    //
                    // These behaviors are overridable for:
                    //   * The "coalesce" operator, which needs to omit type annotations.
                    //   * String-valued properties (e.g. `text-field`), where coercion is more convenient than assertion.
                    //
                    if ((expected.kind === 'string' || expected.kind === 'number' || expected.kind === 'boolean' || expected.kind === 'object' || expected.kind === 'array') && actual.kind === 'value') {
                        parsed = annotate(parsed, expected, options.typeAnnotation || 'assert');
                    }
                    else if ((expected.kind === 'projectionDefinition') && (actual.kind === 'string' || actual.kind === 'array')) {
                        parsed = annotate(parsed, expected, options.typeAnnotation || 'coerce');
                    }
                    else if ((expected.kind === 'color' || expected.kind === 'formatted' || expected.kind === 'resolvedImage') && (actual.kind === 'value' || actual.kind === 'string')) {
                        parsed = annotate(parsed, expected, options.typeAnnotation || 'coerce');
                    }
                    else if (expected.kind === 'padding' && (actual.kind === 'value' || actual.kind === 'number' || actual.kind === 'array')) {
                        parsed = annotate(parsed, expected, options.typeAnnotation || 'coerce');
                    }
                    else if (expected.kind === 'variableAnchorOffsetCollection' && (actual.kind === 'value' || actual.kind === 'array')) {
                        parsed = annotate(parsed, expected, options.typeAnnotation || 'coerce');
                    }
                    else if (this.checkSubtype(expected, actual)) {
                        return null;
                    }
                }
                // If an expression's arguments are all literals, we can evaluate
                // it immediately and replace it with a literal value in the
                // parsed/compiled result. Expressions that expect an image should
                // not be resolved here so we can later get the available images.
                if (!(parsed instanceof Literal) && (parsed.type.kind !== 'resolvedImage') && this._isConstant(parsed)) {
                    const ec = new EvaluationContext();
                    try {
                        parsed = new Literal(parsed.type, parsed.evaluate(ec));
                    }
                    catch (e) {
                        this.error(e.message);
                        return null;
                    }
                }
                return parsed;
            }
            return this.error(`Unknown expression "${op}". If you wanted a literal array, use ["literal", [...]].`, 0);
        }
        else if (typeof expr === 'undefined') {
            return this.error('\'undefined\' value invalid. Use null instead.');
        }
        else if (typeof expr === 'object') {
            return this.error('Bare objects invalid. Use ["literal", {...}] instead.');
        }
        else {
            return this.error(`Expected an array, but found ${typeof expr} instead.`);
        }
    }
    /**
     * Returns a copy of this context suitable for parsing the subexpression at
     * index `index`, optionally appending to 'let' binding map.
     *
     * Note that `errors` property, intended for collecting errors while
     * parsing, is copied by reference rather than cloned.
     * @private
     */
    concat(index, expectedType, bindings) {
        const path = typeof index === 'number' ? this.path.concat(index) : this.path;
        const scope = bindings ? this.scope.concat(bindings) : this.scope;
        return new ParsingContext(this.registry, this._isConstant, path, expectedType || null, scope, this.errors);
    }
    /**
     * Push a parsing (or type checking) error into the `this.errors`
     * @param error The message
     * @param keys Optionally specify the source of the error at a child
     * of the current expression at `this.key`.
     * @private
     */
    error(error, ...keys) {
        const key = `${this.key}${keys.map(k => `[${k}]`).join('')}`;
        this.errors.push(new ExpressionParsingError(key, error));
    }
    /**
     * Returns null if `t` is a subtype of `expected`; otherwise returns an
     * error message and also pushes it to `this.errors`.
     * @param expected The expected type
     * @param t The actual type
     * @returns null if `t` is a subtype of `expected`; otherwise returns an error message
     */
    checkSubtype(expected, t) {
        const error = checkSubtype(expected, t);
        if (error)
            this.error(error);
        return error;
    }
}

class Let {
    constructor(bindings, result) {
        this.type = result.type;
        this.bindings = [].concat(bindings);
        this.result = result;
    }
    evaluate(ctx) {
        return this.result.evaluate(ctx);
    }
    eachChild(fn) {
        for (const binding of this.bindings) {
            fn(binding[1]);
        }
        fn(this.result);
    }
    static parse(args, context) {
        if (args.length < 4)
            return context.error(`Expected at least 3 arguments, but found ${args.length - 1} instead.`);
        const bindings = [];
        for (let i = 1; i < args.length - 1; i += 2) {
            const name = args[i];
            if (typeof name !== 'string') {
                return context.error(`Expected string, but found ${typeof name} instead.`, i);
            }
            if (/[^a-zA-Z0-9_]/.test(name)) {
                return context.error('Variable names must contain only alphanumeric characters or \'_\'.', i);
            }
            const value = context.parse(args[i + 1], i + 1);
            if (!value)
                return null;
            bindings.push([name, value]);
        }
        const result = context.parse(args[args.length - 1], args.length - 1, context.expectedType, bindings);
        if (!result)
            return null;
        return new Let(bindings, result);
    }
    outputDefined() {
        return this.result.outputDefined();
    }
}

class Var {
    constructor(name, boundExpression) {
        this.type = boundExpression.type;
        this.name = name;
        this.boundExpression = boundExpression;
    }
    static parse(args, context) {
        if (args.length !== 2 || typeof args[1] !== 'string')
            return context.error('\'var\' expression requires exactly one string literal argument.');
        const name = args[1];
        if (!context.scope.has(name)) {
            return context.error(`Unknown variable "${name}". Make sure "${name}" has been bound in an enclosing "let" expression before using it.`, 1);
        }
        return new Var(name, context.scope.get(name));
    }
    evaluate(ctx) {
        return this.boundExpression.evaluate(ctx);
    }
    eachChild() { }
    outputDefined() {
        return false;
    }
}

class At {
    constructor(type, index, input) {
        this.type = type;
        this.index = index;
        this.input = input;
    }
    static parse(args, context) {
        if (args.length !== 3)
            return context.error(`Expected 2 arguments, but found ${args.length - 1} instead.`);
        const index = context.parse(args[1], 1, NumberType);
        const input = context.parse(args[2], 2, array(context.expectedType || ValueType));
        if (!index || !input)
            return null;
        const t = input.type;
        return new At(t.itemType, index, input);
    }
    evaluate(ctx) {
        const index = this.index.evaluate(ctx);
        const array = this.input.evaluate(ctx);
        if (index < 0) {
            throw new RuntimeError(`Array index out of bounds: ${index} < 0.`);
        }
        if (index >= array.length) {
            throw new RuntimeError(`Array index out of bounds: ${index} > ${array.length - 1}.`);
        }
        if (index !== Math.floor(index)) {
            throw new RuntimeError(`Array index must be an integer, but found ${index} instead.`);
        }
        return array[index];
    }
    eachChild(fn) {
        fn(this.index);
        fn(this.input);
    }
    outputDefined() {
        return false;
    }
}

class In {
    constructor(needle, haystack) {
        this.type = BooleanType;
        this.needle = needle;
        this.haystack = haystack;
    }
    static parse(args, context) {
        if (args.length !== 3) {
            return context.error(`Expected 2 arguments, but found ${args.length - 1} instead.`);
        }
        const needle = context.parse(args[1], 1, ValueType);
        const haystack = context.parse(args[2], 2, ValueType);
        if (!needle || !haystack)
            return null;
        if (!isValidType(needle.type, [BooleanType, StringType, NumberType, NullType, ValueType])) {
            return context.error(`Expected first argument to be of type boolean, string, number or null, but found ${typeToString(needle.type)} instead`);
        }
        return new In(needle, haystack);
    }
    evaluate(ctx) {
        const needle = this.needle.evaluate(ctx);
        const haystack = this.haystack.evaluate(ctx);
        if (!haystack)
            return false;
        if (!isValidNativeType(needle, ['boolean', 'string', 'number', 'null'])) {
            throw new RuntimeError(`Expected first argument to be of type boolean, string, number or null, but found ${typeToString(typeOf(needle))} instead.`);
        }
        if (!isValidNativeType(haystack, ['string', 'array'])) {
            throw new RuntimeError(`Expected second argument to be of type array or string, but found ${typeToString(typeOf(haystack))} instead.`);
        }
        return haystack.indexOf(needle) >= 0;
    }
    eachChild(fn) {
        fn(this.needle);
        fn(this.haystack);
    }
    outputDefined() {
        return true;
    }
}

class IndexOf {
    constructor(needle, haystack, fromIndex) {
        this.type = NumberType;
        this.needle = needle;
        this.haystack = haystack;
        this.fromIndex = fromIndex;
    }
    static parse(args, context) {
        if (args.length <= 2 || args.length >= 5) {
            return context.error(`Expected 3 or 4 arguments, but found ${args.length - 1} instead.`);
        }
        const needle = context.parse(args[1], 1, ValueType);
        const haystack = context.parse(args[2], 2, ValueType);
        if (!needle || !haystack)
            return null;
        if (!isValidType(needle.type, [BooleanType, StringType, NumberType, NullType, ValueType])) {
            return context.error(`Expected first argument to be of type boolean, string, number or null, but found ${typeToString(needle.type)} instead`);
        }
        if (args.length === 4) {
            const fromIndex = context.parse(args[3], 3, NumberType);
            if (!fromIndex)
                return null;
            return new IndexOf(needle, haystack, fromIndex);
        }
        else {
            return new IndexOf(needle, haystack);
        }
    }
    evaluate(ctx) {
        const needle = this.needle.evaluate(ctx);
        const haystack = this.haystack.evaluate(ctx);
        if (!isValidNativeType(needle, ['boolean', 'string', 'number', 'null'])) {
            throw new RuntimeError(`Expected first argument to be of type boolean, string, number or null, but found ${typeToString(typeOf(needle))} instead.`);
        }
        let fromIndex;
        if (this.fromIndex) {
            fromIndex = this.fromIndex.evaluate(ctx);
        }
        if (isValidNativeType(haystack, ['string'])) {
            const rawIndex = haystack.indexOf(needle, fromIndex);
            if (rawIndex === -1) {
                return -1;
            }
            else {
                // The index may be affected by surrogate pairs, so get the length of the preceding substring.
                return [...haystack.slice(0, rawIndex)].length;
            }
        }
        else if (isValidNativeType(haystack, ['array'])) {
            return haystack.indexOf(needle, fromIndex);
        }
        else {
            throw new RuntimeError(`Expected second argument to be of type array or string, but found ${typeToString(typeOf(haystack))} instead.`);
        }
    }
    eachChild(fn) {
        fn(this.needle);
        fn(this.haystack);
        if (this.fromIndex) {
            fn(this.fromIndex);
        }
    }
    outputDefined() {
        return false;
    }
}

class Match {
    constructor(inputType, outputType, input, cases, outputs, otherwise) {
        this.inputType = inputType;
        this.type = outputType;
        this.input = input;
        this.cases = cases;
        this.outputs = outputs;
        this.otherwise = otherwise;
    }
    static parse(args, context) {
        if (args.length < 5)
            return context.error(`Expected at least 4 arguments, but found only ${args.length - 1}.`);
        if (args.length % 2 !== 1)
            return context.error('Expected an even number of arguments.');
        let inputType;
        let outputType;
        if (context.expectedType && context.expectedType.kind !== 'value') {
            outputType = context.expectedType;
        }
        const cases = {};
        const outputs = [];
        for (let i = 2; i < args.length - 1; i += 2) {
            let labels = args[i];
            const value = args[i + 1];
            if (!Array.isArray(labels)) {
                labels = [labels];
            }
            const labelContext = context.concat(i);
            if (labels.length === 0) {
                return labelContext.error('Expected at least one branch label.');
            }
            for (const label of labels) {
                if (typeof label !== 'number' && typeof label !== 'string') {
                    return labelContext.error('Branch labels must be numbers or strings.');
                }
                else if (typeof label === 'number' && Math.abs(label) > Number.MAX_SAFE_INTEGER) {
                    return labelContext.error(`Branch labels must be integers no larger than ${Number.MAX_SAFE_INTEGER}.`);
                }
                else if (typeof label === 'number' && Math.floor(label) !== label) {
                    return labelContext.error('Numeric branch labels must be integer values.');
                }
                else if (!inputType) {
                    inputType = typeOf(label);
                }
                else if (labelContext.checkSubtype(inputType, typeOf(label))) {
                    return null;
                }
                if (typeof cases[String(label)] !== 'undefined') {
                    return labelContext.error('Branch labels must be unique.');
                }
                cases[String(label)] = outputs.length;
            }
            const result = context.parse(value, i, outputType);
            if (!result)
                return null;
            outputType = outputType || result.type;
            outputs.push(result);
        }
        const input = context.parse(args[1], 1, ValueType);
        if (!input)
            return null;
        const otherwise = context.parse(args[args.length - 1], args.length - 1, outputType);
        if (!otherwise)
            return null;
        if (input.type.kind !== 'value' && context.concat(1).checkSubtype(inputType, input.type)) {
            return null;
        }
        return new Match(inputType, outputType, input, cases, outputs, otherwise);
    }
    evaluate(ctx) {
        const input = this.input.evaluate(ctx);
        const output = (typeOf(input) === this.inputType && this.outputs[this.cases[input]]) || this.otherwise;
        return output.evaluate(ctx);
    }
    eachChild(fn) {
        fn(this.input);
        this.outputs.forEach(fn);
        fn(this.otherwise);
    }
    outputDefined() {
        return this.outputs.every(out => out.outputDefined()) && this.otherwise.outputDefined();
    }
}

class Case {
    constructor(type, branches, otherwise) {
        this.type = type;
        this.branches = branches;
        this.otherwise = otherwise;
    }
    static parse(args, context) {
        if (args.length < 4)
            return context.error(`Expected at least 3 arguments, but found only ${args.length - 1}.`);
        if (args.length % 2 !== 0)
            return context.error('Expected an odd number of arguments.');
        let outputType;
        if (context.expectedType && context.expectedType.kind !== 'value') {
            outputType = context.expectedType;
        }
        const branches = [];
        for (let i = 1; i < args.length - 1; i += 2) {
            const test = context.parse(args[i], i, BooleanType);
            if (!test)
                return null;
            const result = context.parse(args[i + 1], i + 1, outputType);
            if (!result)
                return null;
            branches.push([test, result]);
            outputType = outputType || result.type;
        }
        const otherwise = context.parse(args[args.length - 1], args.length - 1, outputType);
        if (!otherwise)
            return null;
        if (!outputType)
            throw new Error('Can\'t infer output type');
        return new Case(outputType, branches, otherwise);
    }
    evaluate(ctx) {
        for (const [test, expression] of this.branches) {
            if (test.evaluate(ctx)) {
                return expression.evaluate(ctx);
            }
        }
        return this.otherwise.evaluate(ctx);
    }
    eachChild(fn) {
        for (const [test, expression] of this.branches) {
            fn(test);
            fn(expression);
        }
        fn(this.otherwise);
    }
    outputDefined() {
        return this.branches.every(([_, out]) => out.outputDefined()) && this.otherwise.outputDefined();
    }
}

class Slice {
    constructor(type, input, beginIndex, endIndex) {
        this.type = type;
        this.input = input;
        this.beginIndex = beginIndex;
        this.endIndex = endIndex;
    }
    static parse(args, context) {
        if (args.length <= 2 || args.length >= 5) {
            return context.error(`Expected 3 or 4 arguments, but found ${args.length - 1} instead.`);
        }
        const input = context.parse(args[1], 1, ValueType);
        const beginIndex = context.parse(args[2], 2, NumberType);
        if (!input || !beginIndex)
            return null;
        if (!isValidType(input.type, [array(ValueType), StringType, ValueType])) {
            return context.error(`Expected first argument to be of type array or string, but found ${typeToString(input.type)} instead`);
        }
        if (args.length === 4) {
            const endIndex = context.parse(args[3], 3, NumberType);
            if (!endIndex)
                return null;
            return new Slice(input.type, input, beginIndex, endIndex);
        }
        else {
            return new Slice(input.type, input, beginIndex);
        }
    }
    evaluate(ctx) {
        const input = this.input.evaluate(ctx);
        const beginIndex = this.beginIndex.evaluate(ctx);
        let endIndex;
        if (this.endIndex) {
            endIndex = this.endIndex.evaluate(ctx);
        }
        if (isValidNativeType(input, ['string'])) {
            // Indices may be affected by surrogate pairs.
            return [...input].slice(beginIndex, endIndex).join('');
        }
        else if (isValidNativeType(input, ['array'])) {
            return input.slice(beginIndex, endIndex);
        }
        else {
            throw new RuntimeError(`Expected first argument to be of type array or string, but found ${typeToString(typeOf(input))} instead.`);
        }
    }
    eachChild(fn) {
        fn(this.input);
        fn(this.beginIndex);
        if (this.endIndex) {
            fn(this.endIndex);
        }
    }
    outputDefined() {
        return false;
    }
}

/**
 * Returns the index of the last stop <= input, or 0 if it doesn't exist.
 * @private
 */
function findStopLessThanOrEqualTo(stops, input) {
    const lastIndex = stops.length - 1;
    let lowerIndex = 0;
    let upperIndex = lastIndex;
    let currentIndex = 0;
    let currentValue, nextValue;
    while (lowerIndex <= upperIndex) {
        currentIndex = Math.floor((lowerIndex + upperIndex) / 2);
        currentValue = stops[currentIndex];
        nextValue = stops[currentIndex + 1];
        if (currentValue <= input) {
            if (currentIndex === lastIndex || input < nextValue) { // Search complete
                return currentIndex;
            }
            lowerIndex = currentIndex + 1;
        }
        else if (currentValue > input) {
            upperIndex = currentIndex - 1;
        }
        else {
            throw new RuntimeError('Input is not a number.');
        }
    }
    return 0;
}

class Step {
    constructor(type, input, stops) {
        this.type = type;
        this.input = input;
        this.labels = [];
        this.outputs = [];
        for (const [label, expression] of stops) {
            this.labels.push(label);
            this.outputs.push(expression);
        }
    }
    static parse(args, context) {
        if (args.length - 1 < 4) {
            return context.error(`Expected at least 4 arguments, but found only ${args.length - 1}.`);
        }
        if ((args.length - 1) % 2 !== 0) {
            return context.error('Expected an even number of arguments.');
        }
        const input = context.parse(args[1], 1, NumberType);
        if (!input)
            return null;
        const stops = [];
        let outputType = null;
        if (context.expectedType && context.expectedType.kind !== 'value') {
            outputType = context.expectedType;
        }
        for (let i = 1; i < args.length; i += 2) {
            const label = i === 1 ? -Infinity : args[i];
            const value = args[i + 1];
            const labelKey = i;
            const valueKey = i + 1;
            if (typeof label !== 'number') {
                return context.error('Input/output pairs for "step" expressions must be defined using literal numeric values (not computed expressions) for the input values.', labelKey);
            }
            if (stops.length && stops[stops.length - 1][0] >= label) {
                return context.error('Input/output pairs for "step" expressions must be arranged with input values in strictly ascending order.', labelKey);
            }
            const parsed = context.parse(value, valueKey, outputType);
            if (!parsed)
                return null;
            outputType = outputType || parsed.type;
            stops.push([label, parsed]);
        }
        return new Step(outputType, input, stops);
    }
    evaluate(ctx) {
        const labels = this.labels;
        const outputs = this.outputs;
        if (labels.length === 1) {
            return outputs[0].evaluate(ctx);
        }
        const value = this.input.evaluate(ctx);
        if (value <= labels[0]) {
            return outputs[0].evaluate(ctx);
        }
        const stopCount = labels.length;
        if (value >= labels[stopCount - 1]) {
            return outputs[stopCount - 1].evaluate(ctx);
        }
        const index = findStopLessThanOrEqualTo(labels, value);
        return outputs[index].evaluate(ctx);
    }
    eachChild(fn) {
        fn(this.input);
        for (const expression of this.outputs) {
            fn(expression);
        }
    }
    outputDefined() {
        return this.outputs.every(out => out.outputDefined());
    }
}

var unitbezier;
var hasRequiredUnitbezier;

function requireUnitbezier () {
	if (hasRequiredUnitbezier) return unitbezier;
	hasRequiredUnitbezier = 1;

	unitbezier = UnitBezier;

	function UnitBezier(p1x, p1y, p2x, p2y) {
	    // Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
	    this.cx = 3.0 * p1x;
	    this.bx = 3.0 * (p2x - p1x) - this.cx;
	    this.ax = 1.0 - this.cx - this.bx;

	    this.cy = 3.0 * p1y;
	    this.by = 3.0 * (p2y - p1y) - this.cy;
	    this.ay = 1.0 - this.cy - this.by;

	    this.p1x = p1x;
	    this.p1y = p1y;
	    this.p2x = p2x;
	    this.p2y = p2y;
	}

	UnitBezier.prototype = {
	    sampleCurveX: function (t) {
	        // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
	        return ((this.ax * t + this.bx) * t + this.cx) * t;
	    },

	    sampleCurveY: function (t) {
	        return ((this.ay * t + this.by) * t + this.cy) * t;
	    },

	    sampleCurveDerivativeX: function (t) {
	        return (3.0 * this.ax * t + 2.0 * this.bx) * t + this.cx;
	    },

	    solveCurveX: function (x, epsilon) {
	        if (epsilon === undefined) epsilon = 1e-6;

	        if (x < 0.0) return 0.0;
	        if (x > 1.0) return 1.0;

	        var t = x;

	        // First try a few iterations of Newton's method - normally very fast.
	        for (var i = 0; i < 8; i++) {
	            var x2 = this.sampleCurveX(t) - x;
	            if (Math.abs(x2) < epsilon) return t;

	            var d2 = this.sampleCurveDerivativeX(t);
	            if (Math.abs(d2) < 1e-6) break;

	            t = t - x2 / d2;
	        }

	        // Fall back to the bisection method for reliability.
	        var t0 = 0.0;
	        var t1 = 1.0;
	        t = x;

	        for (i = 0; i < 20; i++) {
	            x2 = this.sampleCurveX(t);
	            if (Math.abs(x2 - x) < epsilon) break;

	            if (x > x2) {
	                t0 = t;
	            } else {
	                t1 = t;
	            }

	            t = (t1 - t0) * 0.5 + t0;
	        }

	        return t;
	    },

	    solve: function (x, epsilon) {
	        return this.sampleCurveY(this.solveCurveX(x, epsilon));
	    }
	};
	return unitbezier;
}

var unitbezierExports = requireUnitbezier();
var UnitBezier = /*@__PURE__*/getDefaultExportFromCjs(unitbezierExports);

class Interpolate {
    constructor(type, operator, interpolation, input, stops) {
        this.type = type;
        this.operator = operator;
        this.interpolation = interpolation;
        this.input = input;
        this.labels = [];
        this.outputs = [];
        for (const [label, expression] of stops) {
            this.labels.push(label);
            this.outputs.push(expression);
        }
    }
    static interpolationFactor(interpolation, input, lower, upper) {
        let t = 0;
        if (interpolation.name === 'exponential') {
            t = exponentialInterpolation(input, interpolation.base, lower, upper);
        }
        else if (interpolation.name === 'linear') {
            t = exponentialInterpolation(input, 1, lower, upper);
        }
        else if (interpolation.name === 'cubic-bezier') {
            const c = interpolation.controlPoints;
            const ub = new UnitBezier(c[0], c[1], c[2], c[3]);
            t = ub.solve(exponentialInterpolation(input, 1, lower, upper));
        }
        return t;
    }
    static parse(args, context) {
        let [operator, interpolation, input, ...rest] = args;
        if (!Array.isArray(interpolation) || interpolation.length === 0) {
            return context.error('Expected an interpolation type expression.', 1);
        }
        if (interpolation[0] === 'linear') {
            interpolation = { name: 'linear' };
        }
        else if (interpolation[0] === 'exponential') {
            const base = interpolation[1];
            if (typeof base !== 'number')
                return context.error('Exponential interpolation requires a numeric base.', 1, 1);
            interpolation = {
                name: 'exponential',
                base
            };
        }
        else if (interpolation[0] === 'cubic-bezier') {
            const controlPoints = interpolation.slice(1);
            if (controlPoints.length !== 4 ||
                controlPoints.some(t => typeof t !== 'number' || t < 0 || t > 1)) {
                return context.error('Cubic bezier interpolation requires four numeric arguments with values between 0 and 1.', 1);
            }
            interpolation = {
                name: 'cubic-bezier',
                controlPoints: controlPoints
            };
        }
        else {
            return context.error(`Unknown interpolation type ${String(interpolation[0])}`, 1, 0);
        }
        if (args.length - 1 < 4) {
            return context.error(`Expected at least 4 arguments, but found only ${args.length - 1}.`);
        }
        if ((args.length - 1) % 2 !== 0) {
            return context.error('Expected an even number of arguments.');
        }
        input = context.parse(input, 2, NumberType);
        if (!input)
            return null;
        const stops = [];
        let outputType = null;
        if (operator === 'interpolate-hcl' || operator === 'interpolate-lab') {
            outputType = ColorType;
        }
        else if (context.expectedType && context.expectedType.kind !== 'value') {
            outputType = context.expectedType;
        }
        for (let i = 0; i < rest.length; i += 2) {
            const label = rest[i];
            const value = rest[i + 1];
            const labelKey = i + 3;
            const valueKey = i + 4;
            if (typeof label !== 'number') {
                return context.error('Input/output pairs for "interpolate" expressions must be defined using literal numeric values (not computed expressions) for the input values.', labelKey);
            }
            if (stops.length && stops[stops.length - 1][0] >= label) {
                return context.error('Input/output pairs for "interpolate" expressions must be arranged with input values in strictly ascending order.', labelKey);
            }
            const parsed = context.parse(value, valueKey, outputType);
            if (!parsed)
                return null;
            outputType = outputType || parsed.type;
            stops.push([label, parsed]);
        }
        if (!verifyType(outputType, NumberType) &&
            !verifyType(outputType, ProjectionDefinitionType) &&
            !verifyType(outputType, ColorType) &&
            !verifyType(outputType, PaddingType) &&
            !verifyType(outputType, VariableAnchorOffsetCollectionType) &&
            !verifyType(outputType, array(NumberType))) {
            return context.error(`Type ${typeToString(outputType)} is not interpolatable.`);
        }
        return new Interpolate(outputType, operator, interpolation, input, stops);
    }
    evaluate(ctx) {
        const labels = this.labels;
        const outputs = this.outputs;
        if (labels.length === 1) {
            return outputs[0].evaluate(ctx);
        }
        const value = this.input.evaluate(ctx);
        if (value <= labels[0]) {
            return outputs[0].evaluate(ctx);
        }
        const stopCount = labels.length;
        if (value >= labels[stopCount - 1]) {
            return outputs[stopCount - 1].evaluate(ctx);
        }
        const index = findStopLessThanOrEqualTo(labels, value);
        const lower = labels[index];
        const upper = labels[index + 1];
        const t = Interpolate.interpolationFactor(this.interpolation, value, lower, upper);
        const outputLower = outputs[index].evaluate(ctx);
        const outputUpper = outputs[index + 1].evaluate(ctx);
        switch (this.operator) {
            case 'interpolate':
                switch (this.type.kind) {
                    case 'number':
                        return interpolateNumber(outputLower, outputUpper, t);
                    case 'color':
                        return Color.interpolate(outputLower, outputUpper, t);
                    case 'padding':
                        return Padding.interpolate(outputLower, outputUpper, t);
                    case 'variableAnchorOffsetCollection':
                        return VariableAnchorOffsetCollection.interpolate(outputLower, outputUpper, t);
                    case 'array':
                        return interpolateArray(outputLower, outputUpper, t);
                    case 'projectionDefinition':
                        return ProjectionDefinition.interpolate(outputLower, outputUpper, t);
                }
            case 'interpolate-hcl':
                return Color.interpolate(outputLower, outputUpper, t, 'hcl');
            case 'interpolate-lab':
                return Color.interpolate(outputLower, outputUpper, t, 'lab');
        }
    }
    eachChild(fn) {
        fn(this.input);
        for (const expression of this.outputs) {
            fn(expression);
        }
    }
    outputDefined() {
        return this.outputs.every(out => out.outputDefined());
    }
}
/**
 * Returns a ratio that can be used to interpolate between exponential function
 * stops.
 * How it works: Two consecutive stop values define a (scaled and shifted) exponential function `f(x) = a * base^x + b`, where `base` is the user-specified base,
 * and `a` and `b` are constants affording sufficient degrees of freedom to fit
 * the function to the given stops.
 *
 * Here's a bit of algebra that lets us compute `f(x)` directly from the stop
 * values without explicitly solving for `a` and `b`:
 *
 * First stop value: `f(x0) = y0 = a * base^x0 + b`
 * Second stop value: `f(x1) = y1 = a * base^x1 + b`
 * => `y1 - y0 = a(base^x1 - base^x0)`
 * => `a = (y1 - y0)/(base^x1 - base^x0)`
 *
 * Desired value: `f(x) = y = a * base^x + b`
 * => `f(x) = y0 + a * (base^x - base^x0)`
 *
 * From the above, we can replace the `a` in `a * (base^x - base^x0)` and do a
 * little algebra:
 * ```
 * a * (base^x - base^x0) = (y1 - y0)/(base^x1 - base^x0) * (base^x - base^x0)
 *                     = (y1 - y0) * (base^x - base^x0) / (base^x1 - base^x0)
 * ```
 *
 * If we let `(base^x - base^x0) / (base^x1 base^x0)`, then we have
 * `f(x) = y0 + (y1 - y0) * ratio`.  In other words, `ratio` may be treated as
 * an interpolation factor between the two stops' output values.
 *
 * (Note: a slightly different form for `ratio`,
 * `(base^(x-x0) - 1) / (base^(x1-x0) - 1) `, is equivalent, but requires fewer
 * expensive `Math.pow()` operations.)
 *
 * @private
*/
function exponentialInterpolation(input, base, lowerValue, upperValue) {
    const difference = upperValue - lowerValue;
    const progress = input - lowerValue;
    if (difference === 0) {
        return 0;
    }
    else if (base === 1) {
        return progress / difference;
    }
    else {
        return (Math.pow(base, progress) - 1) / (Math.pow(base, difference) - 1);
    }
}
({
    color: Color.interpolate,
    number: interpolateNumber,
    padding: Padding.interpolate,
    variableAnchorOffsetCollection: VariableAnchorOffsetCollection.interpolate,
    array: interpolateArray
});

class Coalesce {
    constructor(type, args) {
        this.type = type;
        this.args = args;
    }
    static parse(args, context) {
        if (args.length < 2) {
            return context.error('Expected at least one argument.');
        }
        let outputType = null;
        const expectedType = context.expectedType;
        if (expectedType && expectedType.kind !== 'value') {
            outputType = expectedType;
        }
        const parsedArgs = [];
        for (const arg of args.slice(1)) {
            const parsed = context.parse(arg, 1 + parsedArgs.length, outputType, undefined, { typeAnnotation: 'omit' });
            if (!parsed)
                return null;
            outputType = outputType || parsed.type;
            parsedArgs.push(parsed);
        }
        if (!outputType)
            throw new Error('No output type');
        // Above, we parse arguments without inferred type annotation so that
        // they don't produce a runtime error for `null` input, which would
        // preempt the desired null-coalescing behavior.
        // Thus, if any of our arguments would have needed an annotation, we
        // need to wrap the enclosing coalesce expression with it instead.
        const needsAnnotation = expectedType &&
            parsedArgs.some(arg => checkSubtype(expectedType, arg.type));
        return needsAnnotation ?
            new Coalesce(ValueType, parsedArgs) :
            new Coalesce(outputType, parsedArgs);
    }
    evaluate(ctx) {
        let result = null;
        let argCount = 0;
        let requestedImageName;
        for (const arg of this.args) {
            argCount++;
            result = arg.evaluate(ctx);
            // we need to keep track of the first requested image in a coalesce statement
            // if coalesce can't find a valid image, we return the first image name so styleimagemissing can fire
            if (result && result instanceof ResolvedImage && !result.available) {
                if (!requestedImageName) {
                    requestedImageName = result.name;
                }
                result = null;
                if (argCount === this.args.length) {
                    result = requestedImageName;
                }
            }
            if (result !== null)
                break;
        }
        return result;
    }
    eachChild(fn) {
        this.args.forEach(fn);
    }
    outputDefined() {
        return this.args.every(arg => arg.outputDefined());
    }
}

function isComparableType(op, type) {
    if (op === '==' || op === '!=') {
        // equality operator
        return type.kind === 'boolean' ||
            type.kind === 'string' ||
            type.kind === 'number' ||
            type.kind === 'null' ||
            type.kind === 'value';
    }
    else {
        // ordering operator
        return type.kind === 'string' ||
            type.kind === 'number' ||
            type.kind === 'value';
    }
}
function eq(ctx, a, b) { return a === b; }
function neq(ctx, a, b) { return a !== b; }
function lt(ctx, a, b) { return a < b; }
function gt(ctx, a, b) { return a > b; }
function lteq(ctx, a, b) { return a <= b; }
function gteq(ctx, a, b) { return a >= b; }
function eqCollate(ctx, a, b, c) { return c.compare(a, b) === 0; }
function neqCollate(ctx, a, b, c) { return !eqCollate(ctx, a, b, c); }
function ltCollate(ctx, a, b, c) { return c.compare(a, b) < 0; }
function gtCollate(ctx, a, b, c) { return c.compare(a, b) > 0; }
function lteqCollate(ctx, a, b, c) { return c.compare(a, b) <= 0; }
function gteqCollate(ctx, a, b, c) { return c.compare(a, b) >= 0; }
/**
 * Special form for comparison operators, implementing the signatures:
 * - (T, T, ?Collator) => boolean
 * - (T, value, ?Collator) => boolean
 * - (value, T, ?Collator) => boolean
 *
 * For inequalities, T must be either value, string, or number. For ==/!=, it
 * can also be boolean or null.
 *
 * Equality semantics are equivalent to Javascript's strict equality (===/!==)
 * -- i.e., when the arguments' types don't match, == evaluates to false, != to
 * true.
 *
 * When types don't match in an ordering comparison, a runtime error is thrown.
 *
 * @private
 */
function makeComparison(op, compareBasic, compareWithCollator) {
    const isOrderComparison = op !== '==' && op !== '!=';
    return class Comparison {
        constructor(lhs, rhs, collator) {
            this.type = BooleanType;
            this.lhs = lhs;
            this.rhs = rhs;
            this.collator = collator;
            this.hasUntypedArgument = lhs.type.kind === 'value' || rhs.type.kind === 'value';
        }
        static parse(args, context) {
            if (args.length !== 3 && args.length !== 4)
                return context.error('Expected two or three arguments.');
            const op = args[0];
            let lhs = context.parse(args[1], 1, ValueType);
            if (!lhs)
                return null;
            if (!isComparableType(op, lhs.type)) {
                return context.concat(1).error(`"${op}" comparisons are not supported for type '${typeToString(lhs.type)}'.`);
            }
            let rhs = context.parse(args[2], 2, ValueType);
            if (!rhs)
                return null;
            if (!isComparableType(op, rhs.type)) {
                return context.concat(2).error(`"${op}" comparisons are not supported for type '${typeToString(rhs.type)}'.`);
            }
            if (lhs.type.kind !== rhs.type.kind &&
                lhs.type.kind !== 'value' &&
                rhs.type.kind !== 'value') {
                return context.error(`Cannot compare types '${typeToString(lhs.type)}' and '${typeToString(rhs.type)}'.`);
            }
            if (isOrderComparison) {
                // typing rules specific to less/greater than operators
                if (lhs.type.kind === 'value' && rhs.type.kind !== 'value') {
                    // (value, T)
                    lhs = new Assertion(rhs.type, [lhs]);
                }
                else if (lhs.type.kind !== 'value' && rhs.type.kind === 'value') {
                    // (T, value)
                    rhs = new Assertion(lhs.type, [rhs]);
                }
            }
            let collator = null;
            if (args.length === 4) {
                if (lhs.type.kind !== 'string' &&
                    rhs.type.kind !== 'string' &&
                    lhs.type.kind !== 'value' &&
                    rhs.type.kind !== 'value') {
                    return context.error('Cannot use collator to compare non-string types.');
                }
                collator = context.parse(args[3], 3, CollatorType);
                if (!collator)
                    return null;
            }
            return new Comparison(lhs, rhs, collator);
        }
        evaluate(ctx) {
            const lhs = this.lhs.evaluate(ctx);
            const rhs = this.rhs.evaluate(ctx);
            if (isOrderComparison && this.hasUntypedArgument) {
                const lt = typeOf(lhs);
                const rt = typeOf(rhs);
                // check that type is string or number, and equal
                if (lt.kind !== rt.kind || !(lt.kind === 'string' || lt.kind === 'number')) {
                    throw new RuntimeError(`Expected arguments for "${op}" to be (string, string) or (number, number), but found (${lt.kind}, ${rt.kind}) instead.`);
                }
            }
            if (this.collator && !isOrderComparison && this.hasUntypedArgument) {
                const lt = typeOf(lhs);
                const rt = typeOf(rhs);
                if (lt.kind !== 'string' || rt.kind !== 'string') {
                    return compareBasic(ctx, lhs, rhs);
                }
            }
            return this.collator ?
                compareWithCollator(ctx, lhs, rhs, this.collator.evaluate(ctx)) :
                compareBasic(ctx, lhs, rhs);
        }
        eachChild(fn) {
            fn(this.lhs);
            fn(this.rhs);
            if (this.collator) {
                fn(this.collator);
            }
        }
        outputDefined() {
            return true;
        }
    };
}
const Equals = makeComparison('==', eq, eqCollate);
const NotEquals = makeComparison('!=', neq, neqCollate);
const LessThan = makeComparison('<', lt, ltCollate);
const GreaterThan = makeComparison('>', gt, gtCollate);
const LessThanOrEqual = makeComparison('<=', lteq, lteqCollate);
const GreaterThanOrEqual = makeComparison('>=', gteq, gteqCollate);

class CollatorExpression {
    constructor(caseSensitive, diacriticSensitive, locale) {
        this.type = CollatorType;
        this.locale = locale;
        this.caseSensitive = caseSensitive;
        this.diacriticSensitive = diacriticSensitive;
    }
    static parse(args, context) {
        if (args.length !== 2)
            return context.error('Expected one argument.');
        const options = args[1];
        if (typeof options !== 'object' || Array.isArray(options))
            return context.error('Collator options argument must be an object.');
        const caseSensitive = context.parse(options['case-sensitive'] === undefined ? false : options['case-sensitive'], 1, BooleanType);
        if (!caseSensitive)
            return null;
        const diacriticSensitive = context.parse(options['diacritic-sensitive'] === undefined ? false : options['diacritic-sensitive'], 1, BooleanType);
        if (!diacriticSensitive)
            return null;
        let locale = null;
        if (options['locale']) {
            locale = context.parse(options['locale'], 1, StringType);
            if (!locale)
                return null;
        }
        return new CollatorExpression(caseSensitive, diacriticSensitive, locale);
    }
    evaluate(ctx) {
        return new Collator(this.caseSensitive.evaluate(ctx), this.diacriticSensitive.evaluate(ctx), this.locale ? this.locale.evaluate(ctx) : null);
    }
    eachChild(fn) {
        fn(this.caseSensitive);
        fn(this.diacriticSensitive);
        if (this.locale) {
            fn(this.locale);
        }
    }
    outputDefined() {
        // Technically the set of possible outputs is the combinatoric set of Collators produced
        // by all possible outputs of locale/caseSensitive/diacriticSensitive
        // But for the primary use of Collators in comparison operators, we ignore the Collator's
        // possible outputs anyway, so we can get away with leaving this false for now.
        return false;
    }
}

class NumberFormat {
    constructor(number, locale, currency, minFractionDigits, maxFractionDigits) {
        this.type = StringType;
        this.number = number;
        this.locale = locale;
        this.currency = currency;
        this.minFractionDigits = minFractionDigits;
        this.maxFractionDigits = maxFractionDigits;
    }
    static parse(args, context) {
        if (args.length !== 3)
            return context.error('Expected two arguments.');
        const number = context.parse(args[1], 1, NumberType);
        if (!number)
            return null;
        const options = args[2];
        if (typeof options !== 'object' || Array.isArray(options))
            return context.error('NumberFormat options argument must be an object.');
        let locale = null;
        if (options['locale']) {
            locale = context.parse(options['locale'], 1, StringType);
            if (!locale)
                return null;
        }
        let currency = null;
        if (options['currency']) {
            currency = context.parse(options['currency'], 1, StringType);
            if (!currency)
                return null;
        }
        let minFractionDigits = null;
        if (options['min-fraction-digits']) {
            minFractionDigits = context.parse(options['min-fraction-digits'], 1, NumberType);
            if (!minFractionDigits)
                return null;
        }
        let maxFractionDigits = null;
        if (options['max-fraction-digits']) {
            maxFractionDigits = context.parse(options['max-fraction-digits'], 1, NumberType);
            if (!maxFractionDigits)
                return null;
        }
        return new NumberFormat(number, locale, currency, minFractionDigits, maxFractionDigits);
    }
    evaluate(ctx) {
        return new Intl.NumberFormat(this.locale ? this.locale.evaluate(ctx) : [], {
            style: this.currency ? 'currency' : 'decimal',
            currency: this.currency ? this.currency.evaluate(ctx) : undefined,
            minimumFractionDigits: this.minFractionDigits ? this.minFractionDigits.evaluate(ctx) : undefined,
            maximumFractionDigits: this.maxFractionDigits ? this.maxFractionDigits.evaluate(ctx) : undefined,
        }).format(this.number.evaluate(ctx));
    }
    eachChild(fn) {
        fn(this.number);
        if (this.locale) {
            fn(this.locale);
        }
        if (this.currency) {
            fn(this.currency);
        }
        if (this.minFractionDigits) {
            fn(this.minFractionDigits);
        }
        if (this.maxFractionDigits) {
            fn(this.maxFractionDigits);
        }
    }
    outputDefined() {
        return false;
    }
}

class FormatExpression {
    constructor(sections) {
        this.type = FormattedType;
        this.sections = sections;
    }
    static parse(args, context) {
        if (args.length < 2) {
            return context.error('Expected at least one argument.');
        }
        const firstArg = args[1];
        if (!Array.isArray(firstArg) && typeof firstArg === 'object') {
            return context.error('First argument must be an image or text section.');
        }
        const sections = [];
        let nextTokenMayBeObject = false;
        for (let i = 1; i <= args.length - 1; ++i) {
            const arg = args[i];
            if (nextTokenMayBeObject && typeof arg === 'object' && !Array.isArray(arg)) {
                nextTokenMayBeObject = false;
                let scale = null;
                if (arg['font-scale']) {
                    scale = context.parse(arg['font-scale'], 1, NumberType);
                    if (!scale)
                        return null;
                }
                let font = null;
                if (arg['text-font']) {
                    font = context.parse(arg['text-font'], 1, array(StringType));
                    if (!font)
                        return null;
                }
                let textColor = null;
                if (arg['text-color']) {
                    textColor = context.parse(arg['text-color'], 1, ColorType);
                    if (!textColor)
                        return null;
                }
                let verticalAlign = null;
                if (arg['vertical-align']) {
                    if (typeof arg['vertical-align'] === 'string' && !VERTICAL_ALIGN_OPTIONS.includes(arg['vertical-align'])) {
                        return context.error(`'vertical-align' must be one of: 'bottom', 'center', 'top' but found '${arg['vertical-align']}' instead.`);
                    }
                    verticalAlign = context.parse(arg['vertical-align'], 1, StringType);
                    if (!verticalAlign)
                        return null;
                }
                const lastExpression = sections[sections.length - 1];
                lastExpression.scale = scale;
                lastExpression.font = font;
                lastExpression.textColor = textColor;
                lastExpression.verticalAlign = verticalAlign;
            }
            else {
                const content = context.parse(args[i], 1, ValueType);
                if (!content)
                    return null;
                const kind = content.type.kind;
                if (kind !== 'string' && kind !== 'value' && kind !== 'null' && kind !== 'resolvedImage')
                    return context.error('Formatted text type must be \'string\', \'value\', \'image\' or \'null\'.');
                nextTokenMayBeObject = true;
                sections.push({ content, scale: null, font: null, textColor: null, verticalAlign: null });
            }
        }
        return new FormatExpression(sections);
    }
    evaluate(ctx) {
        const evaluateSection = section => {
            const evaluatedContent = section.content.evaluate(ctx);
            if (typeOf(evaluatedContent) === ResolvedImageType) {
                return new FormattedSection('', evaluatedContent, null, null, null, section.verticalAlign ? section.verticalAlign.evaluate(ctx) : null);
            }
            return new FormattedSection(valueToString(evaluatedContent), null, section.scale ? section.scale.evaluate(ctx) : null, section.font ? section.font.evaluate(ctx).join(',') : null, section.textColor ? section.textColor.evaluate(ctx) : null, section.verticalAlign ? section.verticalAlign.evaluate(ctx) : null);
        };
        return new Formatted(this.sections.map(evaluateSection));
    }
    eachChild(fn) {
        for (const section of this.sections) {
            fn(section.content);
            if (section.scale) {
                fn(section.scale);
            }
            if (section.font) {
                fn(section.font);
            }
            if (section.textColor) {
                fn(section.textColor);
            }
            if (section.verticalAlign) {
                fn(section.verticalAlign);
            }
        }
    }
    outputDefined() {
        // Technically the combinatoric set of all children
        // Usually, this.text will be undefined anyway
        return false;
    }
}

class ImageExpression {
    constructor(input) {
        this.type = ResolvedImageType;
        this.input = input;
    }
    static parse(args, context) {
        if (args.length !== 2) {
            return context.error('Expected two arguments.');
        }
        const name = context.parse(args[1], 1, StringType);
        if (!name)
            return context.error('No image name provided.');
        return new ImageExpression(name);
    }
    evaluate(ctx) {
        const evaluatedImageName = this.input.evaluate(ctx);
        const value = ResolvedImage.fromString(evaluatedImageName);
        if (value && ctx.availableImages)
            value.available = ctx.availableImages.indexOf(evaluatedImageName) > -1;
        return value;
    }
    eachChild(fn) {
        fn(this.input);
    }
    outputDefined() {
        // The output of image is determined by the list of available images in the evaluation context
        return false;
    }
}

class Length {
    constructor(input) {
        this.type = NumberType;
        this.input = input;
    }
    static parse(args, context) {
        if (args.length !== 2)
            return context.error(`Expected 1 argument, but found ${args.length - 1} instead.`);
        const input = context.parse(args[1], 1);
        if (!input)
            return null;
        if (input.type.kind !== 'array' && input.type.kind !== 'string' && input.type.kind !== 'value')
            return context.error(`Expected argument of type string or array, but found ${typeToString(input.type)} instead.`);
        return new Length(input);
    }
    evaluate(ctx) {
        const input = this.input.evaluate(ctx);
        if (typeof input === 'string') {
            // The length may be affected by surrogate pairs.
            return [...input].length;
        }
        else if (Array.isArray(input)) {
            return input.length;
        }
        else {
            throw new RuntimeError(`Expected value to be of type string or array, but found ${typeToString(typeOf(input))} instead.`);
        }
    }
    eachChild(fn) {
        fn(this.input);
    }
    outputDefined() {
        return false;
    }
}

const EXTENT = 8192;
function getTileCoordinates(p, canonical) {
    const x = mercatorXfromLng(p[0]);
    const y = mercatorYfromLat(p[1]);
    const tilesAtZoom = Math.pow(2, canonical.z);
    return [Math.round(x * tilesAtZoom * EXTENT), Math.round(y * tilesAtZoom * EXTENT)];
}
function getLngLatFromTileCoord(coord, canonical) {
    const tilesAtZoom = Math.pow(2, canonical.z);
    const x = (coord[0] / EXTENT + canonical.x) / tilesAtZoom;
    const y = (coord[1] / EXTENT + canonical.y) / tilesAtZoom;
    return [lngFromMercatorXfromLng(x), latFromMercatorY(y)];
}
function mercatorXfromLng(lng) {
    return (180 + lng) / 360;
}
function lngFromMercatorXfromLng(mercatorX) {
    return mercatorX * 360 - 180;
}
function mercatorYfromLat(lat) {
    return (180 - (180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360)))) / 360;
}
function latFromMercatorY(mercatorY) {
    return 360 / Math.PI * Math.atan(Math.exp((180 - mercatorY * 360) * Math.PI / 180)) - 90;
}
function updateBBox(bbox, coord) {
    bbox[0] = Math.min(bbox[0], coord[0]);
    bbox[1] = Math.min(bbox[1], coord[1]);
    bbox[2] = Math.max(bbox[2], coord[0]);
    bbox[3] = Math.max(bbox[3], coord[1]);
}
function boxWithinBox(bbox1, bbox2) {
    if (bbox1[0] <= bbox2[0])
        return false;
    if (bbox1[2] >= bbox2[2])
        return false;
    if (bbox1[1] <= bbox2[1])
        return false;
    if (bbox1[3] >= bbox2[3])
        return false;
    return true;
}
function rayIntersect(p, p1, p2) {
    return ((p1[1] > p[1]) !== (p2[1] > p[1])) && (p[0] < (p2[0] - p1[0]) * (p[1] - p1[1]) / (p2[1] - p1[1]) + p1[0]);
}
function pointOnBoundary(p, p1, p2) {
    const x1 = p[0] - p1[0];
    const y1 = p[1] - p1[1];
    const x2 = p[0] - p2[0];
    const y2 = p[1] - p2[1];
    return (x1 * y2 - x2 * y1 === 0) && (x1 * x2 <= 0) && (y1 * y2 <= 0);
}
// a, b are end points for line segment1, c and d are end points for line segment2
function segmentIntersectSegment(a, b, c, d) {
    // check if two segments are parallel or not
    // precondition is end point a, b is inside polygon, if line a->b is
    // parallel to polygon edge c->d, then a->b won't intersect with c->d
    const vectorP = [b[0] - a[0], b[1] - a[1]];
    const vectorQ = [d[0] - c[0], d[1] - c[1]];
    if (perp(vectorQ, vectorP) === 0)
        return false;
    // If lines are intersecting with each other, the relative location should be:
    // a and b lie in different sides of segment c->d
    // c and d lie in different sides of segment a->b
    if (twoSided(a, b, c, d) && twoSided(c, d, a, b))
        return true;
    return false;
}
function lineIntersectPolygon(p1, p2, polygon) {
    for (const ring of polygon) {
        // loop through every edge of the ring
        for (let j = 0; j < ring.length - 1; ++j) {
            if (segmentIntersectSegment(p1, p2, ring[j], ring[j + 1])) {
                return true;
            }
        }
    }
    return false;
}
// ray casting algorithm for detecting if point is in polygon
function pointWithinPolygon(point, rings, trueIfOnBoundary = false) {
    let inside = false;
    for (const ring of rings) {
        for (let j = 0; j < ring.length - 1; j++) {
            if (pointOnBoundary(point, ring[j], ring[j + 1]))
                return trueIfOnBoundary;
            if (rayIntersect(point, ring[j], ring[j + 1]))
                inside = !inside;
        }
    }
    return inside;
}
function pointWithinPolygons(point, polygons) {
    for (const polygon of polygons) {
        if (pointWithinPolygon(point, polygon))
            return true;
    }
    return false;
}
function lineStringWithinPolygon(line, polygon) {
    // First, check if geometry points of line segments are all inside polygon
    for (const point of line) {
        if (!pointWithinPolygon(point, polygon)) {
            return false;
        }
    }
    // Second, check if there is line segment intersecting polygon edge
    for (let i = 0; i < line.length - 1; ++i) {
        if (lineIntersectPolygon(line[i], line[i + 1], polygon)) {
            return false;
        }
    }
    return true;
}
function lineStringWithinPolygons(line, polygons) {
    for (const polygon of polygons) {
        if (lineStringWithinPolygon(line, polygon))
            return true;
    }
    return false;
}
function perp(v1, v2) {
    return (v1[0] * v2[1] - v1[1] * v2[0]);
}
// check if p1 and p2 are in different sides of line segment q1->q2
function twoSided(p1, p2, q1, q2) {
    // q1->p1 (x1, y1), q1->p2 (x2, y2), q1->q2 (x3, y3)
    const x1 = p1[0] - q1[0];
    const y1 = p1[1] - q1[1];
    const x2 = p2[0] - q1[0];
    const y2 = p2[1] - q1[1];
    const x3 = q2[0] - q1[0];
    const y3 = q2[1] - q1[1];
    const det1 = (x1 * y3 - x3 * y1);
    const det2 = (x2 * y3 - x3 * y2);
    if ((det1 > 0 && det2 < 0) || (det1 < 0 && det2 > 0))
        return true;
    return false;
}

function getTilePolygon(coordinates, bbox, canonical) {
    const polygon = [];
    for (let i = 0; i < coordinates.length; i++) {
        const ring = [];
        for (let j = 0; j < coordinates[i].length; j++) {
            const coord = getTileCoordinates(coordinates[i][j], canonical);
            updateBBox(bbox, coord);
            ring.push(coord);
        }
        polygon.push(ring);
    }
    return polygon;
}
function getTilePolygons(coordinates, bbox, canonical) {
    const polygons = [];
    for (let i = 0; i < coordinates.length; i++) {
        const polygon = getTilePolygon(coordinates[i], bbox, canonical);
        polygons.push(polygon);
    }
    return polygons;
}
function updatePoint(p, bbox, polyBBox, worldSize) {
    if (p[0] < polyBBox[0] || p[0] > polyBBox[2]) {
        const halfWorldSize = worldSize * 0.5;
        let shift = (p[0] - polyBBox[0] > halfWorldSize) ? -worldSize : (polyBBox[0] - p[0] > halfWorldSize) ? worldSize : 0;
        if (shift === 0) {
            shift = (p[0] - polyBBox[2] > halfWorldSize) ? -worldSize : (polyBBox[2] - p[0] > halfWorldSize) ? worldSize : 0;
        }
        p[0] += shift;
    }
    updateBBox(bbox, p);
}
function resetBBox(bbox) {
    bbox[0] = bbox[1] = Infinity;
    bbox[2] = bbox[3] = -Infinity;
}
function getTilePoints(geometry, pointBBox, polyBBox, canonical) {
    const worldSize = Math.pow(2, canonical.z) * EXTENT;
    const shifts = [canonical.x * EXTENT, canonical.y * EXTENT];
    const tilePoints = [];
    for (const points of geometry) {
        for (const point of points) {
            const p = [point.x + shifts[0], point.y + shifts[1]];
            updatePoint(p, pointBBox, polyBBox, worldSize);
            tilePoints.push(p);
        }
    }
    return tilePoints;
}
function getTileLines(geometry, lineBBox, polyBBox, canonical) {
    const worldSize = Math.pow(2, canonical.z) * EXTENT;
    const shifts = [canonical.x * EXTENT, canonical.y * EXTENT];
    const tileLines = [];
    for (const line of geometry) {
        const tileLine = [];
        for (const point of line) {
            const p = [point.x + shifts[0], point.y + shifts[1]];
            updateBBox(lineBBox, p);
            tileLine.push(p);
        }
        tileLines.push(tileLine);
    }
    if (lineBBox[2] - lineBBox[0] <= worldSize / 2) {
        resetBBox(lineBBox);
        for (const line of tileLines) {
            for (const p of line) {
                updatePoint(p, lineBBox, polyBBox, worldSize);
            }
        }
    }
    return tileLines;
}
function pointsWithinPolygons(ctx, polygonGeometry) {
    const pointBBox = [Infinity, Infinity, -Infinity, -Infinity];
    const polyBBox = [Infinity, Infinity, -Infinity, -Infinity];
    const canonical = ctx.canonicalID();
    if (polygonGeometry.type === 'Polygon') {
        const tilePolygon = getTilePolygon(polygonGeometry.coordinates, polyBBox, canonical);
        const tilePoints = getTilePoints(ctx.geometry(), pointBBox, polyBBox, canonical);
        if (!boxWithinBox(pointBBox, polyBBox))
            return false;
        for (const point of tilePoints) {
            if (!pointWithinPolygon(point, tilePolygon))
                return false;
        }
    }
    if (polygonGeometry.type === 'MultiPolygon') {
        const tilePolygons = getTilePolygons(polygonGeometry.coordinates, polyBBox, canonical);
        const tilePoints = getTilePoints(ctx.geometry(), pointBBox, polyBBox, canonical);
        if (!boxWithinBox(pointBBox, polyBBox))
            return false;
        for (const point of tilePoints) {
            if (!pointWithinPolygons(point, tilePolygons))
                return false;
        }
    }
    return true;
}
function linesWithinPolygons(ctx, polygonGeometry) {
    const lineBBox = [Infinity, Infinity, -Infinity, -Infinity];
    const polyBBox = [Infinity, Infinity, -Infinity, -Infinity];
    const canonical = ctx.canonicalID();
    if (polygonGeometry.type === 'Polygon') {
        const tilePolygon = getTilePolygon(polygonGeometry.coordinates, polyBBox, canonical);
        const tileLines = getTileLines(ctx.geometry(), lineBBox, polyBBox, canonical);
        if (!boxWithinBox(lineBBox, polyBBox))
            return false;
        for (const line of tileLines) {
            if (!lineStringWithinPolygon(line, tilePolygon))
                return false;
        }
    }
    if (polygonGeometry.type === 'MultiPolygon') {
        const tilePolygons = getTilePolygons(polygonGeometry.coordinates, polyBBox, canonical);
        const tileLines = getTileLines(ctx.geometry(), lineBBox, polyBBox, canonical);
        if (!boxWithinBox(lineBBox, polyBBox))
            return false;
        for (const line of tileLines) {
            if (!lineStringWithinPolygons(line, tilePolygons))
                return false;
        }
    }
    return true;
}
class Within {
    constructor(geojson, geometries) {
        this.type = BooleanType;
        this.geojson = geojson;
        this.geometries = geometries;
    }
    static parse(args, context) {
        if (args.length !== 2)
            return context.error(`'within' expression requires exactly one argument, but found ${args.length - 1} instead.`);
        if (isValue(args[1])) {
            const geojson = args[1];
            if (geojson.type === 'FeatureCollection') {
                const polygonsCoords = [];
                for (const polygon of geojson.features) {
                    const { type, coordinates } = polygon.geometry;
                    if (type === 'Polygon') {
                        polygonsCoords.push(coordinates);
                    }
                    if (type === 'MultiPolygon') {
                        polygonsCoords.push(...coordinates);
                    }
                }
                if (polygonsCoords.length) {
                    const multipolygonWrapper = {
                        type: 'MultiPolygon',
                        coordinates: polygonsCoords
                    };
                    return new Within(geojson, multipolygonWrapper);
                }
            }
            else if (geojson.type === 'Feature') {
                const type = geojson.geometry.type;
                if (type === 'Polygon' || type === 'MultiPolygon') {
                    return new Within(geojson, geojson.geometry);
                }
            }
            else if (geojson.type === 'Polygon' || geojson.type === 'MultiPolygon') {
                return new Within(geojson, geojson);
            }
        }
        return context.error('\'within\' expression requires valid geojson object that contains polygon geometry type.');
    }
    evaluate(ctx) {
        if (ctx.geometry() != null && ctx.canonicalID() != null) {
            if (ctx.geometryType() === 'Point') {
                return pointsWithinPolygons(ctx, this.geometries);
            }
            else if (ctx.geometryType() === 'LineString') {
                return linesWithinPolygons(ctx, this.geometries);
            }
        }
        return false;
    }
    eachChild() { }
    outputDefined() {
        return true;
    }
}

class TinyQueue {
    constructor(data = [], compare = (a, b) => (a < b ? -1 : a > b ? 1 : 0)) {
        this.data = data;
        this.length = this.data.length;
        this.compare = compare;

        if (this.length > 0) {
            for (let i = (this.length >> 1) - 1; i >= 0; i--) this._down(i);
        }
    }

    push(item) {
        this.data.push(item);
        this._up(this.length++);
    }

    pop() {
        if (this.length === 0) return undefined;

        const top = this.data[0];
        const bottom = this.data.pop();

        if (--this.length > 0) {
            this.data[0] = bottom;
            this._down(0);
        }

        return top;
    }

    peek() {
        return this.data[0];
    }

    _up(pos) {
        const {data, compare} = this;
        const item = data[pos];

        while (pos > 0) {
            const parent = (pos - 1) >> 1;
            const current = data[parent];
            if (compare(item, current) >= 0) break;
            data[pos] = current;
            pos = parent;
        }

        data[pos] = item;
    }

    _down(pos) {
        const {data, compare} = this;
        const halfLength = this.length >> 1;
        const item = data[pos];

        while (pos < halfLength) {
            let bestChild = (pos << 1) + 1; // initially it is the left child
            const right = bestChild + 1;

            if (right < this.length && compare(data[right], data[bestChild]) < 0) {
                bestChild = right;
            }
            if (compare(data[bestChild], item) >= 0) break;

            data[pos] = data[bestChild];
            pos = bestChild;
        }

        data[pos] = item;
    }
}

/**
 * Classifies an array of rings into polygons with outer rings and holes
 * @param rings - the rings to classify
 * @param maxRings - the maximum number of rings to include in a polygon, use 0 to include all rings
 * @returns an array of polygons with internal rings as holes
 */
function classifyRings(rings, maxRings) {
    const len = rings.length;
    if (len <= 1)
        return [rings];
    const polygons = [];
    let polygon;
    let ccw;
    for (const ring of rings) {
        const area = calculateSignedArea(ring);
        if (area === 0)
            continue;
        ring.area = Math.abs(area);
        if (ccw === undefined)
            ccw = area < 0;
        if (ccw === area < 0) {
            if (polygon)
                polygons.push(polygon);
            polygon = [ring];
        }
        else {
            polygon.push(ring);
        }
    }
    if (polygon)
        polygons.push(polygon);
    return polygons;
}
/**
 * Returns the signed area for the polygon ring.  Positive areas are exterior rings and
 * have a clockwise winding.  Negative areas are interior rings and have a counter clockwise
 * ordering.
 *
 * @param ring - Exterior or interior ring
 * @returns Signed area
 */
function calculateSignedArea(ring) {
    let sum = 0;
    for (let i = 0, len = ring.length, j = len - 1, p1, p2; i < len; j = i++) {
        p1 = ring[i];
        p2 = ring[j];
        sum += (p2.x - p1.x) * (p1.y + p2.y);
    }
    return sum;
}

// This is taken from https://github.com/mapbox/cheap-ruler/ in order to take only the relevant parts
// Values that define WGS84 ellipsoid model of the Earth
const RE = 6378.137; // equatorial radius
const FE = 1 / 298.257223563; // flattening
const E2 = FE * (2 - FE);
const RAD = Math.PI / 180;
class CheapRuler {
    constructor(lat) {
        // Curvature formulas from https://en.wikipedia.org/wiki/Earth_radius#Meridional
        const m = RAD * RE * 1000;
        const coslat = Math.cos(lat * RAD);
        const w2 = 1 / (1 - E2 * (1 - coslat * coslat));
        const w = Math.sqrt(w2);
        // multipliers for converting longitude and latitude degrees into distance
        this.kx = m * w * coslat; // based on normal radius of curvature
        this.ky = m * w * w2 * (1 - E2); // based on meridional radius of curvature
    }
    /**
     * Given two points of the form [longitude, latitude], returns the distance.
     *
     * @param a - point [longitude, latitude]
     * @param b - point [longitude, latitude]
     * @returns distance
     * @example
     * const distance = ruler.distance([30.5, 50.5], [30.51, 50.49]);
     * //=distance
     */
    distance(a, b) {
        const dx = this.wrap(a[0] - b[0]) * this.kx;
        const dy = (a[1] - b[1]) * this.ky;
        return Math.sqrt(dx * dx + dy * dy);
    }
    /**
     * Returns an object of the form {point, index, t}, where point is closest point on the line
     * from the given point, index is the start index of the segment with the closest point,
     * and t is a parameter from 0 to 1 that indicates where the closest point is on that segment.
     *
     * @param line - an array of points that form the line
     * @param p - point [longitude, latitude]
     * @returns the nearest point, its index in the array and the proportion along the line
     * @example
     * const point = ruler.pointOnLine(line, [-67.04, 50.5]).point;
     * //=point
     */
    pointOnLine(line, p) {
        let minDist = Infinity;
        let minX, minY, minI, minT;
        for (let i = 0; i < line.length - 1; i++) {
            let x = line[i][0];
            let y = line[i][1];
            let dx = this.wrap(line[i + 1][0] - x) * this.kx;
            let dy = (line[i + 1][1] - y) * this.ky;
            let t = 0;
            if (dx !== 0 || dy !== 0) {
                t = (this.wrap(p[0] - x) * this.kx * dx + (p[1] - y) * this.ky * dy) / (dx * dx + dy * dy);
                if (t > 1) {
                    x = line[i + 1][0];
                    y = line[i + 1][1];
                }
                else if (t > 0) {
                    x += (dx / this.kx) * t;
                    y += (dy / this.ky) * t;
                }
            }
            dx = this.wrap(p[0] - x) * this.kx;
            dy = (p[1] - y) * this.ky;
            const sqDist = dx * dx + dy * dy;
            if (sqDist < minDist) {
                minDist = sqDist;
                minX = x;
                minY = y;
                minI = i;
                minT = t;
            }
        }
        return {
            point: [minX, minY],
            index: minI,
            t: Math.max(0, Math.min(1, minT))
        };
    }
    wrap(deg) {
        while (deg < -180)
            deg += 360;
        while (deg > 180)
            deg -= 360;
        return deg;
    }
}

const MinPointsSize = 100;
const MinLinePointsSize = 50;
function compareDistPair(a, b) {
    return b[0] - a[0];
}
function getRangeSize(range) {
    return range[1] - range[0] + 1;
}
function isRangeSafe(range, threshold) {
    return range[1] >= range[0] && range[1] < threshold;
}
function splitRange(range, isLine) {
    if (range[0] > range[1]) {
        return [null, null];
    }
    const size = getRangeSize(range);
    if (isLine) {
        if (size === 2) {
            return [range, null];
        }
        const size1 = Math.floor(size / 2);
        return [[range[0], range[0] + size1],
            [range[0] + size1, range[1]]];
    }
    if (size === 1) {
        return [range, null];
    }
    const size1 = Math.floor(size / 2) - 1;
    return [[range[0], range[0] + size1],
        [range[0] + size1 + 1, range[1]]];
}
function getBBox(coords, range) {
    if (!isRangeSafe(range, coords.length)) {
        return [Infinity, Infinity, -Infinity, -Infinity];
    }
    const bbox = [Infinity, Infinity, -Infinity, -Infinity];
    for (let i = range[0]; i <= range[1]; ++i) {
        updateBBox(bbox, coords[i]);
    }
    return bbox;
}
function getPolygonBBox(polygon) {
    const bbox = [Infinity, Infinity, -Infinity, -Infinity];
    for (const ring of polygon) {
        for (const coord of ring) {
            updateBBox(bbox, coord);
        }
    }
    return bbox;
}
function isValidBBox(bbox) {
    return bbox[0] !== -Infinity && bbox[1] !== -Infinity && bbox[2] !== Infinity && bbox[3] !== Infinity;
}
// Calculate the distance between two bounding boxes.
// Calculate the delta in x and y direction, and use two fake points {0.0, 0.0}
// and {dx, dy} to calculate the distance. Distance will be 0.0 if bounding box are overlapping.
function bboxToBBoxDistance(bbox1, bbox2, ruler) {
    if (!isValidBBox(bbox1) || !isValidBBox(bbox2)) {
        return NaN;
    }
    let dx = 0.0;
    let dy = 0.0;
    // bbox1 in left side
    if (bbox1[2] < bbox2[0]) {
        dx = bbox2[0] - bbox1[2];
    }
    // bbox1 in right side
    if (bbox1[0] > bbox2[2]) {
        dx = bbox1[0] - bbox2[2];
    }
    // bbox1 in above side
    if (bbox1[1] > bbox2[3]) {
        dy = bbox1[1] - bbox2[3];
    }
    // bbox1 in down side
    if (bbox1[3] < bbox2[1]) {
        dy = bbox2[1] - bbox1[3];
    }
    return ruler.distance([0.0, 0.0], [dx, dy]);
}
function pointToLineDistance(point, line, ruler) {
    const nearestPoint = ruler.pointOnLine(line, point);
    return ruler.distance(point, nearestPoint.point);
}
function segmentToSegmentDistance(p1, p2, q1, q2, ruler) {
    const dist1 = Math.min(pointToLineDistance(p1, [q1, q2], ruler), pointToLineDistance(p2, [q1, q2], ruler));
    const dist2 = Math.min(pointToLineDistance(q1, [p1, p2], ruler), pointToLineDistance(q2, [p1, p2], ruler));
    return Math.min(dist1, dist2);
}
function lineToLineDistance(line1, range1, line2, range2, ruler) {
    const rangeSafe = isRangeSafe(range1, line1.length) && isRangeSafe(range2, line2.length);
    if (!rangeSafe) {
        return Infinity;
    }
    let dist = Infinity;
    for (let i = range1[0]; i < range1[1]; ++i) {
        const p1 = line1[i];
        const p2 = line1[i + 1];
        for (let j = range2[0]; j < range2[1]; ++j) {
            const q1 = line2[j];
            const q2 = line2[j + 1];
            if (segmentIntersectSegment(p1, p2, q1, q2)) {
                return 0.0;
            }
            dist = Math.min(dist, segmentToSegmentDistance(p1, p2, q1, q2, ruler));
        }
    }
    return dist;
}
function pointsToPointsDistance(points1, range1, points2, range2, ruler) {
    const rangeSafe = isRangeSafe(range1, points1.length) && isRangeSafe(range2, points2.length);
    if (!rangeSafe) {
        return NaN;
    }
    let dist = Infinity;
    for (let i = range1[0]; i <= range1[1]; ++i) {
        for (let j = range2[0]; j <= range2[1]; ++j) {
            dist = Math.min(dist, ruler.distance(points1[i], points2[j]));
            if (dist === 0.0) {
                return dist;
            }
        }
    }
    return dist;
}
function pointToPolygonDistance(point, polygon, ruler) {
    if (pointWithinPolygon(point, polygon, true)) {
        return 0.0;
    }
    let dist = Infinity;
    for (const ring of polygon) {
        const front = ring[0];
        const back = ring[ring.length - 1];
        if (front !== back) {
            dist = Math.min(dist, pointToLineDistance(point, [back, front], ruler));
            if (dist === 0.0) {
                return dist;
            }
        }
        const nearestPoint = ruler.pointOnLine(ring, point);
        dist = Math.min(dist, ruler.distance(point, nearestPoint.point));
        if (dist === 0.0) {
            return dist;
        }
    }
    return dist;
}
function lineToPolygonDistance(line, range, polygon, ruler) {
    if (!isRangeSafe(range, line.length)) {
        return NaN;
    }
    for (let i = range[0]; i <= range[1]; ++i) {
        if (pointWithinPolygon(line[i], polygon, true)) {
            return 0.0;
        }
    }
    let dist = Infinity;
    for (let i = range[0]; i < range[1]; ++i) {
        const p1 = line[i];
        const p2 = line[i + 1];
        for (const ring of polygon) {
            for (let j = 0, len = ring.length, k = len - 1; j < len; k = j++) {
                const q1 = ring[k];
                const q2 = ring[j];
                if (segmentIntersectSegment(p1, p2, q1, q2)) {
                    return 0.0;
                }
                dist = Math.min(dist, segmentToSegmentDistance(p1, p2, q1, q2, ruler));
            }
        }
    }
    return dist;
}
function polygonIntersect(poly1, poly2) {
    for (const ring of poly1) {
        for (const point of ring) {
            if (pointWithinPolygon(point, poly2, true)) {
                return true;
            }
        }
    }
    return false;
}
function polygonToPolygonDistance(polygon1, polygon2, ruler, currentMiniDist = Infinity) {
    const bbox1 = getPolygonBBox(polygon1);
    const bbox2 = getPolygonBBox(polygon2);
    if (currentMiniDist !== Infinity && bboxToBBoxDistance(bbox1, bbox2, ruler) >= currentMiniDist) {
        return currentMiniDist;
    }
    if (boxWithinBox(bbox1, bbox2)) {
        if (polygonIntersect(polygon1, polygon2)) {
            return 0.0;
        }
    }
    else if (polygonIntersect(polygon2, polygon1)) {
        return 0.0;
    }
    let dist = Infinity;
    for (const ring1 of polygon1) {
        for (let i = 0, len1 = ring1.length, l = len1 - 1; i < len1; l = i++) {
            const p1 = ring1[l];
            const p2 = ring1[i];
            for (const ring2 of polygon2) {
                for (let j = 0, len2 = ring2.length, k = len2 - 1; j < len2; k = j++) {
                    const q1 = ring2[k];
                    const q2 = ring2[j];
                    if (segmentIntersectSegment(p1, p2, q1, q2)) {
                        return 0.0;
                    }
                    dist = Math.min(dist, segmentToSegmentDistance(p1, p2, q1, q2, ruler));
                }
            }
        }
    }
    return dist;
}
function updateQueue(distQueue, miniDist, ruler, points, polyBBox, rangeA) {
    if (!rangeA) {
        return;
    }
    const tempDist = bboxToBBoxDistance(getBBox(points, rangeA), polyBBox, ruler);
    // Insert new pair to the queue if the bbox distance is less than
    // miniDist, The pair with biggest distance will be at the top
    if (tempDist < miniDist) {
        distQueue.push([tempDist, rangeA, [0, 0]]);
    }
}
function updateQueueTwoSets(distQueue, miniDist, ruler, pointSet1, pointSet2, range1, range2) {
    if (!range1 || !range2) {
        return;
    }
    const tempDist = bboxToBBoxDistance(getBBox(pointSet1, range1), getBBox(pointSet2, range2), ruler);
    // Insert new pair to the queue if the bbox distance is less than
    // miniDist, The pair with biggest distance will be at the top
    if (tempDist < miniDist) {
        distQueue.push([tempDist, range1, range2]);
    }
}
// Divide and conquer, the time complexity is O(n*lgn), faster than Brute force
// O(n*n) Most of the time, use index for in-place processing.
function pointsToPolygonDistance(points, isLine, polygon, ruler, currentMiniDist = Infinity) {
    let miniDist = Math.min(ruler.distance(points[0], polygon[0][0]), currentMiniDist);
    if (miniDist === 0.0) {
        return miniDist;
    }
    const distQueue = new TinyQueue([[0, [0, points.length - 1], [0, 0]]], compareDistPair);
    const polyBBox = getPolygonBBox(polygon);
    while (distQueue.length > 0) {
        const distPair = distQueue.pop();
        if (distPair[0] >= miniDist) {
            continue;
        }
        const range = distPair[1];
        // In case the set size are relatively small, we could use brute-force directly
        const threshold = isLine ? MinLinePointsSize : MinPointsSize;
        if (getRangeSize(range) <= threshold) {
            if (!isRangeSafe(range, points.length)) {
                return NaN;
            }
            if (isLine) {
                const tempDist = lineToPolygonDistance(points, range, polygon, ruler);
                if (isNaN(tempDist) || tempDist === 0.0) {
                    return tempDist;
                }
                miniDist = Math.min(miniDist, tempDist);
            }
            else {
                for (let i = range[0]; i <= range[1]; ++i) {
                    const tempDist = pointToPolygonDistance(points[i], polygon, ruler);
                    miniDist = Math.min(miniDist, tempDist);
                    if (miniDist === 0.0) {
                        return 0.0;
                    }
                }
            }
        }
        else {
            const newRangesA = splitRange(range, isLine);
            updateQueue(distQueue, miniDist, ruler, points, polyBBox, newRangesA[0]);
            updateQueue(distQueue, miniDist, ruler, points, polyBBox, newRangesA[1]);
        }
    }
    return miniDist;
}
function pointSetToPointSetDistance(pointSet1, isLine1, pointSet2, isLine2, ruler, currentMiniDist = Infinity) {
    let miniDist = Math.min(currentMiniDist, ruler.distance(pointSet1[0], pointSet2[0]));
    if (miniDist === 0.0) {
        return miniDist;
    }
    const distQueue = new TinyQueue([[0, [0, pointSet1.length - 1], [0, pointSet2.length - 1]]], compareDistPair);
    while (distQueue.length > 0) {
        const distPair = distQueue.pop();
        if (distPair[0] >= miniDist) {
            continue;
        }
        const rangeA = distPair[1];
        const rangeB = distPair[2];
        const threshold1 = isLine1 ? MinLinePointsSize : MinPointsSize;
        const threshold2 = isLine2 ? MinLinePointsSize : MinPointsSize;
        // In case the set size are relatively small, we could use brute-force directly
        if (getRangeSize(rangeA) <= threshold1 && getRangeSize(rangeB) <= threshold2) {
            if (!isRangeSafe(rangeA, pointSet1.length) && isRangeSafe(rangeB, pointSet2.length)) {
                return NaN;
            }
            let tempDist;
            if (isLine1 && isLine2) {
                tempDist = lineToLineDistance(pointSet1, rangeA, pointSet2, rangeB, ruler);
                miniDist = Math.min(miniDist, tempDist);
            }
            else if (isLine1 && !isLine2) {
                const sublibe = pointSet1.slice(rangeA[0], rangeA[1] + 1);
                for (let i = rangeB[0]; i <= rangeB[1]; ++i) {
                    tempDist = pointToLineDistance(pointSet2[i], sublibe, ruler);
                    miniDist = Math.min(miniDist, tempDist);
                    if (miniDist === 0.0) {
                        return miniDist;
                    }
                }
            }
            else if (!isLine1 && isLine2) {
                const sublibe = pointSet2.slice(rangeB[0], rangeB[1] + 1);
                for (let i = rangeA[0]; i <= rangeA[1]; ++i) {
                    tempDist = pointToLineDistance(pointSet1[i], sublibe, ruler);
                    miniDist = Math.min(miniDist, tempDist);
                    if (miniDist === 0.0) {
                        return miniDist;
                    }
                }
            }
            else {
                tempDist = pointsToPointsDistance(pointSet1, rangeA, pointSet2, rangeB, ruler);
                miniDist = Math.min(miniDist, tempDist);
            }
        }
        else {
            const newRangesA = splitRange(rangeA, isLine1);
            const newRangesB = splitRange(rangeB, isLine2);
            updateQueueTwoSets(distQueue, miniDist, ruler, pointSet1, pointSet2, newRangesA[0], newRangesB[0]);
            updateQueueTwoSets(distQueue, miniDist, ruler, pointSet1, pointSet2, newRangesA[0], newRangesB[1]);
            updateQueueTwoSets(distQueue, miniDist, ruler, pointSet1, pointSet2, newRangesA[1], newRangesB[0]);
            updateQueueTwoSets(distQueue, miniDist, ruler, pointSet1, pointSet2, newRangesA[1], newRangesB[1]);
        }
    }
    return miniDist;
}
function pointToGeometryDistance(ctx, geometries) {
    const tilePoints = ctx.geometry();
    const pointPosition = tilePoints.flat().map(p => getLngLatFromTileCoord([p.x, p.y], ctx.canonical));
    if (tilePoints.length === 0) {
        return NaN;
    }
    const ruler = new CheapRuler(pointPosition[0][1]);
    let dist = Infinity;
    for (const geometry of geometries) {
        switch (geometry.type) {
            case 'Point':
                dist = Math.min(dist, pointSetToPointSetDistance(pointPosition, false, [geometry.coordinates], false, ruler, dist));
                break;
            case 'LineString':
                dist = Math.min(dist, pointSetToPointSetDistance(pointPosition, false, geometry.coordinates, true, ruler, dist));
                break;
            case 'Polygon':
                dist = Math.min(dist, pointsToPolygonDistance(pointPosition, false, geometry.coordinates, ruler, dist));
                break;
        }
        if (dist === 0.0) {
            return dist;
        }
    }
    return dist;
}
function lineStringToGeometryDistance(ctx, geometries) {
    const tileLine = ctx.geometry();
    const linePositions = tileLine.flat().map(p => getLngLatFromTileCoord([p.x, p.y], ctx.canonical));
    if (tileLine.length === 0) {
        return NaN;
    }
    const ruler = new CheapRuler(linePositions[0][1]);
    let dist = Infinity;
    for (const geometry of geometries) {
        switch (geometry.type) {
            case 'Point':
                dist = Math.min(dist, pointSetToPointSetDistance(linePositions, true, [geometry.coordinates], false, ruler, dist));
                break;
            case 'LineString':
                dist = Math.min(dist, pointSetToPointSetDistance(linePositions, true, geometry.coordinates, true, ruler, dist));
                break;
            case 'Polygon':
                dist = Math.min(dist, pointsToPolygonDistance(linePositions, true, geometry.coordinates, ruler, dist));
                break;
        }
        if (dist === 0.0) {
            return dist;
        }
    }
    return dist;
}
function polygonToGeometryDistance(ctx, geometries) {
    const tilePolygon = ctx.geometry();
    if (tilePolygon.length === 0 || tilePolygon[0].length === 0) {
        return NaN;
    }
    const polygons = classifyRings(tilePolygon).map(polygon => {
        return polygon.map(ring => {
            return ring.map(p => getLngLatFromTileCoord([p.x, p.y], ctx.canonical));
        });
    });
    const ruler = new CheapRuler(polygons[0][0][0][1]);
    let dist = Infinity;
    for (const geometry of geometries) {
        for (const polygon of polygons) {
            switch (geometry.type) {
                case 'Point':
                    dist = Math.min(dist, pointsToPolygonDistance([geometry.coordinates], false, polygon, ruler, dist));
                    break;
                case 'LineString':
                    dist = Math.min(dist, pointsToPolygonDistance(geometry.coordinates, true, polygon, ruler, dist));
                    break;
                case 'Polygon':
                    dist = Math.min(dist, polygonToPolygonDistance(polygon, geometry.coordinates, ruler, dist));
                    break;
            }
            if (dist === 0.0) {
                return dist;
            }
        }
    }
    return dist;
}
function toSimpleGeometry(geometry) {
    if (geometry.type === 'MultiPolygon') {
        return geometry.coordinates.map(polygon => {
            return {
                type: 'Polygon',
                coordinates: polygon
            };
        });
    }
    if (geometry.type === 'MultiLineString') {
        return geometry.coordinates.map(lineString => {
            return {
                type: 'LineString',
                coordinates: lineString
            };
        });
    }
    if (geometry.type === 'MultiPoint') {
        return geometry.coordinates.map(point => {
            return {
                type: 'Point',
                coordinates: point
            };
        });
    }
    return [geometry];
}
class Distance {
    constructor(geojson, geometries) {
        this.type = NumberType;
        this.geojson = geojson;
        this.geometries = geometries;
    }
    static parse(args, context) {
        if (args.length !== 2)
            return context.error(`'distance' expression requires exactly one argument, but found ${args.length - 1} instead.`);
        if (isValue(args[1])) {
            const geojson = args[1];
            if (geojson.type === 'FeatureCollection') {
                return new Distance(geojson, geojson.features.map(feature => toSimpleGeometry(feature.geometry)).flat());
            }
            else if (geojson.type === 'Feature') {
                return new Distance(geojson, toSimpleGeometry(geojson.geometry));
            }
            else if ('type' in geojson && 'coordinates' in geojson) {
                return new Distance(geojson, toSimpleGeometry(geojson));
            }
        }
        return context.error('\'distance\' expression requires valid geojson object that contains polygon geometry type.');
    }
    evaluate(ctx) {
        if (ctx.geometry() != null && ctx.canonicalID() != null) {
            if (ctx.geometryType() === 'Point') {
                return pointToGeometryDistance(ctx, this.geometries);
            }
            else if (ctx.geometryType() === 'LineString') {
                return lineStringToGeometryDistance(ctx, this.geometries);
            }
            else if (ctx.geometryType() === 'Polygon') {
                return polygonToGeometryDistance(ctx, this.geometries);
            }
        }
        return NaN;
    }
    eachChild() { }
    outputDefined() {
        return true;
    }
}

const expressions = {
    // special forms
    '==': Equals,
    '!=': NotEquals,
    '>': GreaterThan,
    '<': LessThan,
    '>=': GreaterThanOrEqual,
    '<=': LessThanOrEqual,
    'array': Assertion,
    'at': At,
    'boolean': Assertion,
    'case': Case,
    'coalesce': Coalesce,
    'collator': CollatorExpression,
    'format': FormatExpression,
    'image': ImageExpression,
    'in': In,
    'index-of': IndexOf,
    'interpolate': Interpolate,
    'interpolate-hcl': Interpolate,
    'interpolate-lab': Interpolate,
    'length': Length,
    'let': Let,
    'literal': Literal,
    'match': Match,
    'number': Assertion,
    'number-format': NumberFormat,
    'object': Assertion,
    'slice': Slice,
    'step': Step,
    'string': Assertion,
    'to-boolean': Coercion,
    'to-color': Coercion,
    'to-number': Coercion,
    'to-string': Coercion,
    'var': Var,
    'within': Within,
    'distance': Distance
};

class CompoundExpression {
    constructor(name, type, evaluate, args) {
        this.name = name;
        this.type = type;
        this._evaluate = evaluate;
        this.args = args;
    }
    evaluate(ctx) {
        return this._evaluate(ctx, this.args);
    }
    eachChild(fn) {
        this.args.forEach(fn);
    }
    outputDefined() {
        return false;
    }
    static parse(args, context) {
        const op = args[0];
        const definition = CompoundExpression.definitions[op];
        if (!definition) {
            return context.error(`Unknown expression "${op}". If you wanted a literal array, use ["literal", [...]].`, 0);
        }
        // Now check argument types against each signature
        const type = Array.isArray(definition) ?
            definition[0] : definition.type;
        const availableOverloads = Array.isArray(definition) ?
            [[definition[1], definition[2]]] :
            definition.overloads;
        const overloads = availableOverloads.filter(([signature]) => (!Array.isArray(signature) || // varags
            signature.length === args.length - 1 // correct param count
        ));
        let signatureContext = null;
        for (const [params, evaluate] of overloads) {
            // Use a fresh context for each attempted signature so that, if
            // we eventually succeed, we haven't polluted `context.errors`.
            signatureContext = new ParsingContext(context.registry, isExpressionConstant, context.path, null, context.scope);
            // First parse all the args, potentially coercing to the
            // types expected by this overload.
            const parsedArgs = [];
            let argParseFailed = false;
            for (let i = 1; i < args.length; i++) {
                const arg = args[i];
                const expectedType = Array.isArray(params) ?
                    params[i - 1] :
                    params.type;
                const parsed = signatureContext.parse(arg, 1 + parsedArgs.length, expectedType);
                if (!parsed) {
                    argParseFailed = true;
                    break;
                }
                parsedArgs.push(parsed);
            }
            if (argParseFailed) {
                // Couldn't coerce args of this overload to expected type, move
                // on to next one.
                continue;
            }
            if (Array.isArray(params)) {
                if (params.length !== parsedArgs.length) {
                    signatureContext.error(`Expected ${params.length} arguments, but found ${parsedArgs.length} instead.`);
                    continue;
                }
            }
            for (let i = 0; i < parsedArgs.length; i++) {
                const expected = Array.isArray(params) ? params[i] : params.type;
                const arg = parsedArgs[i];
                signatureContext.concat(i + 1).checkSubtype(expected, arg.type);
            }
            if (signatureContext.errors.length === 0) {
                return new CompoundExpression(op, type, evaluate, parsedArgs);
            }
        }
        if (overloads.length === 1) {
            context.errors.push(...signatureContext.errors);
        }
        else {
            const expected = overloads.length ? overloads : availableOverloads;
            const signatures = expected
                .map(([params]) => stringifySignature(params))
                .join(' | ');
            const actualTypes = [];
            // For error message, re-parse arguments without trying to
            // apply any coercions
            for (let i = 1; i < args.length; i++) {
                const parsed = context.parse(args[i], 1 + actualTypes.length);
                if (!parsed)
                    return null;
                actualTypes.push(typeToString(parsed.type));
            }
            context.error(`Expected arguments of type ${signatures}, but found (${actualTypes.join(', ')}) instead.`);
        }
        return null;
    }
    static register(registry, definitions) {
        CompoundExpression.definitions = definitions;
        for (const name in definitions) {
            registry[name] = CompoundExpression;
        }
    }
}
function rgba(ctx, [r, g, b, a]) {
    r = r.evaluate(ctx);
    g = g.evaluate(ctx);
    b = b.evaluate(ctx);
    const alpha = a ? a.evaluate(ctx) : 1;
    const error = validateRGBA(r, g, b, alpha);
    if (error)
        throw new RuntimeError(error);
    return new Color(r / 255, g / 255, b / 255, alpha, false);
}
function has(key, obj) {
    return key in obj;
}
function get(key, obj) {
    const v = obj[key];
    return typeof v === 'undefined' ? null : v;
}
function binarySearch(v, a, i, j) {
    while (i <= j) {
        const m = (i + j) >> 1;
        if (a[m] === v)
            return true;
        if (a[m] > v)
            j = m - 1;
        else
            i = m + 1;
    }
    return false;
}
function varargs(type) {
    return { type };
}
CompoundExpression.register(expressions, {
    'error': [
        ErrorType,
        [StringType],
        (ctx, [v]) => { throw new RuntimeError(v.evaluate(ctx)); }
    ],
    'typeof': [
        StringType,
        [ValueType],
        (ctx, [v]) => typeToString(typeOf(v.evaluate(ctx)))
    ],
    'to-rgba': [
        array(NumberType, 4),
        [ColorType],
        (ctx, [v]) => {
            const [r, g, b, a] = v.evaluate(ctx).rgb;
            return [r * 255, g * 255, b * 255, a];
        },
    ],
    'rgb': [
        ColorType,
        [NumberType, NumberType, NumberType],
        rgba
    ],
    'rgba': [
        ColorType,
        [NumberType, NumberType, NumberType, NumberType],
        rgba
    ],
    'has': {
        type: BooleanType,
        overloads: [
            [
                [StringType],
                (ctx, [key]) => has(key.evaluate(ctx), ctx.properties())
            ], [
                [StringType, ObjectType],
                (ctx, [key, obj]) => has(key.evaluate(ctx), obj.evaluate(ctx))
            ]
        ]
    },
    'get': {
        type: ValueType,
        overloads: [
            [
                [StringType],
                (ctx, [key]) => get(key.evaluate(ctx), ctx.properties())
            ], [
                [StringType, ObjectType],
                (ctx, [key, obj]) => get(key.evaluate(ctx), obj.evaluate(ctx))
            ]
        ]
    },
    'feature-state': [
        ValueType,
        [StringType],
        (ctx, [key]) => get(key.evaluate(ctx), ctx.featureState || {})
    ],
    'properties': [
        ObjectType,
        [],
        (ctx) => ctx.properties()
    ],
    'geometry-type': [
        StringType,
        [],
        (ctx) => ctx.geometryType()
    ],
    'id': [
        ValueType,
        [],
        (ctx) => ctx.id()
    ],
    'zoom': [
        NumberType,
        [],
        (ctx) => ctx.globals.zoom
    ],
    'heatmap-density': [
        NumberType,
        [],
        (ctx) => ctx.globals.heatmapDensity || 0
    ],
    'line-progress': [
        NumberType,
        [],
        (ctx) => ctx.globals.lineProgress || 0
    ],
    'accumulated': [
        ValueType,
        [],
        (ctx) => ctx.globals.accumulated === undefined ? null : ctx.globals.accumulated
    ],
    '+': [
        NumberType,
        varargs(NumberType),
        (ctx, args) => {
            let result = 0;
            for (const arg of args) {
                result += arg.evaluate(ctx);
            }
            return result;
        }
    ],
    '*': [
        NumberType,
        varargs(NumberType),
        (ctx, args) => {
            let result = 1;
            for (const arg of args) {
                result *= arg.evaluate(ctx);
            }
            return result;
        }
    ],
    '-': {
        type: NumberType,
        overloads: [
            [
                [NumberType, NumberType],
                (ctx, [a, b]) => a.evaluate(ctx) - b.evaluate(ctx)
            ], [
                [NumberType],
                (ctx, [a]) => -a.evaluate(ctx)
            ]
        ]
    },
    '/': [
        NumberType,
        [NumberType, NumberType],
        (ctx, [a, b]) => a.evaluate(ctx) / b.evaluate(ctx)
    ],
    '%': [
        NumberType,
        [NumberType, NumberType],
        (ctx, [a, b]) => a.evaluate(ctx) % b.evaluate(ctx)
    ],
    'ln2': [
        NumberType,
        [],
        () => Math.LN2
    ],
    'pi': [
        NumberType,
        [],
        () => Math.PI
    ],
    'e': [
        NumberType,
        [],
        () => Math.E
    ],
    '^': [
        NumberType,
        [NumberType, NumberType],
        (ctx, [b, e]) => Math.pow(b.evaluate(ctx), e.evaluate(ctx))
    ],
    'sqrt': [
        NumberType,
        [NumberType],
        (ctx, [x]) => Math.sqrt(x.evaluate(ctx))
    ],
    'log10': [
        NumberType,
        [NumberType],
        (ctx, [n]) => Math.log(n.evaluate(ctx)) / Math.LN10
    ],
    'ln': [
        NumberType,
        [NumberType],
        (ctx, [n]) => Math.log(n.evaluate(ctx))
    ],
    'log2': [
        NumberType,
        [NumberType],
        (ctx, [n]) => Math.log(n.evaluate(ctx)) / Math.LN2
    ],
    'sin': [
        NumberType,
        [NumberType],
        (ctx, [n]) => Math.sin(n.evaluate(ctx))
    ],
    'cos': [
        NumberType,
        [NumberType],
        (ctx, [n]) => Math.cos(n.evaluate(ctx))
    ],
    'tan': [
        NumberType,
        [NumberType],
        (ctx, [n]) => Math.tan(n.evaluate(ctx))
    ],
    'asin': [
        NumberType,
        [NumberType],
        (ctx, [n]) => Math.asin(n.evaluate(ctx))
    ],
    'acos': [
        NumberType,
        [NumberType],
        (ctx, [n]) => Math.acos(n.evaluate(ctx))
    ],
    'atan': [
        NumberType,
        [NumberType],
        (ctx, [n]) => Math.atan(n.evaluate(ctx))
    ],
    'min': [
        NumberType,
        varargs(NumberType),
        (ctx, args) => Math.min(...args.map(arg => arg.evaluate(ctx)))
    ],
    'max': [
        NumberType,
        varargs(NumberType),
        (ctx, args) => Math.max(...args.map(arg => arg.evaluate(ctx)))
    ],
    'abs': [
        NumberType,
        [NumberType],
        (ctx, [n]) => Math.abs(n.evaluate(ctx))
    ],
    'round': [
        NumberType,
        [NumberType],
        (ctx, [n]) => {
            const v = n.evaluate(ctx);
            // Javascript's Math.round() rounds towards +Infinity for halfway
            // values, even when they're negative. It's more common to round
            // away from 0 (e.g., this is what python and C++ do)
            return v < 0 ? -Math.round(-v) : Math.round(v);
        }
    ],
    'floor': [
        NumberType,
        [NumberType],
        (ctx, [n]) => Math.floor(n.evaluate(ctx))
    ],
    'ceil': [
        NumberType,
        [NumberType],
        (ctx, [n]) => Math.ceil(n.evaluate(ctx))
    ],
    'filter-==': [
        BooleanType,
        [StringType, ValueType],
        (ctx, [k, v]) => ctx.properties()[k.value] === v.value
    ],
    'filter-id-==': [
        BooleanType,
        [ValueType],
        (ctx, [v]) => ctx.id() === v.value
    ],
    'filter-type-==': [
        BooleanType,
        [StringType],
        (ctx, [v]) => ctx.geometryType() === v.value
    ],
    'filter-<': [
        BooleanType,
        [StringType, ValueType],
        (ctx, [k, v]) => {
            const a = ctx.properties()[k.value];
            const b = v.value;
            return typeof a === typeof b && a < b;
        }
    ],
    'filter-id-<': [
        BooleanType,
        [ValueType],
        (ctx, [v]) => {
            const a = ctx.id();
            const b = v.value;
            return typeof a === typeof b && a < b;
        }
    ],
    'filter->': [
        BooleanType,
        [StringType, ValueType],
        (ctx, [k, v]) => {
            const a = ctx.properties()[k.value];
            const b = v.value;
            return typeof a === typeof b && a > b;
        }
    ],
    'filter-id->': [
        BooleanType,
        [ValueType],
        (ctx, [v]) => {
            const a = ctx.id();
            const b = v.value;
            return typeof a === typeof b && a > b;
        }
    ],
    'filter-<=': [
        BooleanType,
        [StringType, ValueType],
        (ctx, [k, v]) => {
            const a = ctx.properties()[k.value];
            const b = v.value;
            return typeof a === typeof b && a <= b;
        }
    ],
    'filter-id-<=': [
        BooleanType,
        [ValueType],
        (ctx, [v]) => {
            const a = ctx.id();
            const b = v.value;
            return typeof a === typeof b && a <= b;
        }
    ],
    'filter->=': [
        BooleanType,
        [StringType, ValueType],
        (ctx, [k, v]) => {
            const a = ctx.properties()[k.value];
            const b = v.value;
            return typeof a === typeof b && a >= b;
        }
    ],
    'filter-id->=': [
        BooleanType,
        [ValueType],
        (ctx, [v]) => {
            const a = ctx.id();
            const b = v.value;
            return typeof a === typeof b && a >= b;
        }
    ],
    'filter-has': [
        BooleanType,
        [ValueType],
        (ctx, [k]) => k.value in ctx.properties()
    ],
    'filter-has-id': [
        BooleanType,
        [],
        (ctx) => (ctx.id() !== null && ctx.id() !== undefined)
    ],
    'filter-type-in': [
        BooleanType,
        [array(StringType)],
        (ctx, [v]) => v.value.indexOf(ctx.geometryType()) >= 0
    ],
    'filter-id-in': [
        BooleanType,
        [array(ValueType)],
        (ctx, [v]) => v.value.indexOf(ctx.id()) >= 0
    ],
    'filter-in-small': [
        BooleanType,
        [StringType, array(ValueType)],
        // assumes v is an array literal
        (ctx, [k, v]) => v.value.indexOf(ctx.properties()[k.value]) >= 0
    ],
    'filter-in-large': [
        BooleanType,
        [StringType, array(ValueType)],
        // assumes v is a array literal with values sorted in ascending order and of a single type
        (ctx, [k, v]) => binarySearch(ctx.properties()[k.value], v.value, 0, v.value.length - 1)
    ],
    'all': {
        type: BooleanType,
        overloads: [
            [
                [BooleanType, BooleanType],
                (ctx, [a, b]) => a.evaluate(ctx) && b.evaluate(ctx)
            ],
            [
                varargs(BooleanType),
                (ctx, args) => {
                    for (const arg of args) {
                        if (!arg.evaluate(ctx))
                            return false;
                    }
                    return true;
                }
            ]
        ]
    },
    'any': {
        type: BooleanType,
        overloads: [
            [
                [BooleanType, BooleanType],
                (ctx, [a, b]) => a.evaluate(ctx) || b.evaluate(ctx)
            ],
            [
                varargs(BooleanType),
                (ctx, args) => {
                    for (const arg of args) {
                        if (arg.evaluate(ctx))
                            return true;
                    }
                    return false;
                }
            ]
        ]
    },
    '!': [
        BooleanType,
        [BooleanType],
        (ctx, [b]) => !b.evaluate(ctx)
    ],
    'is-supported-script': [
        BooleanType,
        [StringType],
        // At parse time this will always return true, so we need to exclude this expression with isGlobalPropertyConstant
        (ctx, [s]) => {
            const isSupportedScript = ctx.globals && ctx.globals.isSupportedScript;
            if (isSupportedScript) {
                return isSupportedScript(s.evaluate(ctx));
            }
            return true;
        }
    ],
    'upcase': [
        StringType,
        [StringType],
        (ctx, [s]) => s.evaluate(ctx).toUpperCase()
    ],
    'downcase': [
        StringType,
        [StringType],
        (ctx, [s]) => s.evaluate(ctx).toLowerCase()
    ],
    'concat': [
        StringType,
        varargs(ValueType),
        (ctx, args) => args.map(arg => valueToString(arg.evaluate(ctx))).join('')
    ],
    'resolved-locale': [
        StringType,
        [CollatorType],
        (ctx, [collator]) => collator.evaluate(ctx).resolvedLocale()
    ]
});
function stringifySignature(signature) {
    if (Array.isArray(signature)) {
        return `(${signature.map(typeToString).join(', ')})`;
    }
    else {
        return `(${typeToString(signature.type)}...)`;
    }
}
function isExpressionConstant(expression) {
    if (expression instanceof Var) {
        return isExpressionConstant(expression.boundExpression);
    }
    else if (expression instanceof CompoundExpression && expression.name === 'error') {
        return false;
    }
    else if (expression instanceof CollatorExpression) {
        // Although the results of a Collator expression with fixed arguments
        // generally shouldn't change between executions, we can't serialize them
        // as constant expressions because results change based on environment.
        return false;
    }
    else if (expression instanceof Within) {
        return false;
    }
    else if (expression instanceof Distance) {
        return false;
    }
    const isTypeAnnotation = expression instanceof Coercion ||
        expression instanceof Assertion;
    let childrenConstant = true;
    expression.eachChild(child => {
        // We can _almost_ assume that if `expressions` children are constant,
        // they would already have been evaluated to Literal values when they
        // were parsed.  Type annotations are the exception, because they might
        // have been inferred and added after a child was parsed.
        // So we recurse into isConstant() for the children of type annotations,
        // but otherwise simply check whether they are Literals.
        if (isTypeAnnotation) {
            childrenConstant = childrenConstant && isExpressionConstant(child);
        }
        else {
            childrenConstant = childrenConstant && child instanceof Literal;
        }
    });
    if (!childrenConstant) {
        return false;
    }
    return isFeatureConstant(expression) &&
        isGlobalPropertyConstant(expression, ['zoom', 'heatmap-density', 'line-progress', 'accumulated', 'is-supported-script']);
}
function isFeatureConstant(e) {
    if (e instanceof CompoundExpression) {
        if (e.name === 'get' && e.args.length === 1) {
            return false;
        }
        else if (e.name === 'feature-state') {
            return false;
        }
        else if (e.name === 'has' && e.args.length === 1) {
            return false;
        }
        else if (e.name === 'properties' ||
            e.name === 'geometry-type' ||
            e.name === 'id') {
            return false;
        }
        else if (/^filter-/.test(e.name)) {
            return false;
        }
    }
    if (e instanceof Within) {
        return false;
    }
    if (e instanceof Distance) {
        return false;
    }
    let result = true;
    e.eachChild(arg => {
        if (result && !isFeatureConstant(arg)) {
            result = false;
        }
    });
    return result;
}
function isStateConstant(e) {
    if (e instanceof CompoundExpression) {
        if (e.name === 'feature-state') {
            return false;
        }
    }
    let result = true;
    e.eachChild(arg => {
        if (result && !isStateConstant(arg)) {
            result = false;
        }
    });
    return result;
}
function isGlobalPropertyConstant(e, properties) {
    if (e instanceof CompoundExpression && properties.indexOf(e.name) >= 0) {
        return false;
    }
    let result = true;
    e.eachChild((arg) => {
        if (result && !isGlobalPropertyConstant(arg, properties)) {
            result = false;
        }
    });
    return result;
}

function success(value) {
    return { result: 'success', value };
}
function error(value) {
    return { result: 'error', value };
}

function supportsPropertyExpression(spec) {
    return spec['property-type'] === 'data-driven' || spec['property-type'] === 'cross-faded-data-driven';
}
function supportsZoomExpression(spec) {
    return !!spec.expression && spec.expression.parameters.indexOf('zoom') > -1;
}
function supportsInterpolation(spec) {
    return !!spec.expression && spec.expression.interpolated;
}

function getType(val) {
    if (val instanceof Number) {
        return 'number';
    }
    else if (val instanceof String) {
        return 'string';
    }
    else if (val instanceof Boolean) {
        return 'boolean';
    }
    else if (Array.isArray(val)) {
        return 'array';
    }
    else if (val === null) {
        return 'null';
    }
    else {
        return typeof val;
    }
}

function isFunction(value) {
    return typeof value === 'object' && value !== null && !Array.isArray(value);
}

class StyleExpression {
    constructor(expression, propertySpec) {
        this.expression = expression;
        this._warningHistory = {};
        this._evaluator = new EvaluationContext();
        this._defaultValue = propertySpec ? getDefaultValue(propertySpec) : null;
        this._enumValues = propertySpec && propertySpec.type === 'enum' ? propertySpec.values : null;
    }
    evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection) {
        this._evaluator.globals = globals;
        this._evaluator.feature = feature;
        this._evaluator.featureState = featureState;
        this._evaluator.canonical = canonical;
        this._evaluator.availableImages = availableImages || null;
        this._evaluator.formattedSection = formattedSection;
        return this.expression.evaluate(this._evaluator);
    }
    evaluate(globals, feature, featureState, canonical, availableImages, formattedSection) {
        this._evaluator.globals = globals;
        this._evaluator.feature = feature || null;
        this._evaluator.featureState = featureState || null;
        this._evaluator.canonical = canonical;
        this._evaluator.availableImages = availableImages || null;
        this._evaluator.formattedSection = formattedSection || null;
        try {
            const val = this.expression.evaluate(this._evaluator);
            if (val === null || val === undefined || (typeof val === 'number' && val !== val)) {
                return this._defaultValue;
            }
            if (this._enumValues && !(val in this._enumValues)) {
                throw new RuntimeError(`Expected value to be one of ${Object.keys(this._enumValues).map(v => JSON.stringify(v)).join(', ')}, but found ${JSON.stringify(val)} instead.`);
            }
            return val;
        }
        catch (e) {
            if (!this._warningHistory[e.message]) {
                this._warningHistory[e.message] = true;
                if (typeof console !== 'undefined') {
                    console.warn(e.message);
                }
            }
            return this._defaultValue;
        }
    }
}
function isExpression(expression) {
    return Array.isArray(expression) && expression.length > 0 &&
        typeof expression[0] === 'string' && expression[0] in expressions;
}
/**
 * Parse and typecheck the given style spec JSON expression.  If
 * options.defaultValue is provided, then the resulting StyleExpression's
 * `evaluate()` method will handle errors by logging a warning (once per
 * message) and returning the default value.  Otherwise, it will throw
 * evaluation errors.
 *
 * @private
 */
function createExpression(expression, propertySpec) {
    const parser = new ParsingContext(expressions, isExpressionConstant, [], propertySpec ? getExpectedType(propertySpec) : undefined);
    // For string-valued properties, coerce to string at the top level rather than asserting.
    const parsed = parser.parse(expression, undefined, undefined, undefined, propertySpec && propertySpec.type === 'string' ? { typeAnnotation: 'coerce' } : undefined);
    if (!parsed) {
        return error(parser.errors);
    }
    return success(new StyleExpression(parsed, propertySpec));
}
class ZoomConstantExpression {
    constructor(kind, expression) {
        this.kind = kind;
        this._styleExpression = expression;
        this.isStateDependent = kind !== 'constant' && !isStateConstant(expression.expression);
    }
    evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection) {
        return this._styleExpression.evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection);
    }
    evaluate(globals, feature, featureState, canonical, availableImages, formattedSection) {
        return this._styleExpression.evaluate(globals, feature, featureState, canonical, availableImages, formattedSection);
    }
}
class ZoomDependentExpression {
    constructor(kind, expression, zoomStops, interpolationType) {
        this.kind = kind;
        this.zoomStops = zoomStops;
        this._styleExpression = expression;
        this.isStateDependent = kind !== 'camera' && !isStateConstant(expression.expression);
        this.interpolationType = interpolationType;
    }
    evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection) {
        return this._styleExpression.evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection);
    }
    evaluate(globals, feature, featureState, canonical, availableImages, formattedSection) {
        return this._styleExpression.evaluate(globals, feature, featureState, canonical, availableImages, formattedSection);
    }
    interpolationFactor(input, lower, upper) {
        if (this.interpolationType) {
            return Interpolate.interpolationFactor(this.interpolationType, input, lower, upper);
        }
        else {
            return 0;
        }
    }
}
function createPropertyExpression(expressionInput, propertySpec) {
    const expression = createExpression(expressionInput, propertySpec);
    if (expression.result === 'error') {
        return expression;
    }
    const parsed = expression.value.expression;
    const isFeatureConstantResult = isFeatureConstant(parsed);
    if (!isFeatureConstantResult && !supportsPropertyExpression(propertySpec)) {
        return error([new ExpressionParsingError('', 'data expressions not supported')]);
    }
    const isZoomConstant = isGlobalPropertyConstant(parsed, ['zoom']);
    if (!isZoomConstant && !supportsZoomExpression(propertySpec)) {
        return error([new ExpressionParsingError('', 'zoom expressions not supported')]);
    }
    const zoomCurve = findZoomCurve(parsed);
    if (!zoomCurve && !isZoomConstant) {
        return error([new ExpressionParsingError('', '"zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.')]);
    }
    else if (zoomCurve instanceof ExpressionParsingError) {
        return error([zoomCurve]);
    }
    else if (zoomCurve instanceof Interpolate && !supportsInterpolation(propertySpec)) {
        return error([new ExpressionParsingError('', '"interpolate" expressions cannot be used with this property')]);
    }
    if (!zoomCurve) {
        return success(isFeatureConstantResult ?
            new ZoomConstantExpression('constant', expression.value) :
            new ZoomConstantExpression('source', expression.value));
    }
    const interpolationType = zoomCurve instanceof Interpolate ? zoomCurve.interpolation : undefined;
    return success(isFeatureConstantResult ?
        new ZoomDependentExpression('camera', expression.value, zoomCurve.labels, interpolationType) :
        new ZoomDependentExpression('composite', expression.value, zoomCurve.labels, interpolationType));
}
// Zoom-dependent expressions may only use ["zoom"] as the input to a top-level "step" or "interpolate"
// expression (collectively referred to as a "curve"). The curve may be wrapped in one or more "let" or
// "coalesce" expressions.
function findZoomCurve(expression) {
    let result = null;
    if (expression instanceof Let) {
        result = findZoomCurve(expression.result);
    }
    else if (expression instanceof Coalesce) {
        for (const arg of expression.args) {
            result = findZoomCurve(arg);
            if (result) {
                break;
            }
        }
    }
    else if ((expression instanceof Step || expression instanceof Interpolate) &&
        expression.input instanceof CompoundExpression &&
        expression.input.name === 'zoom') {
        result = expression;
    }
    if (result instanceof ExpressionParsingError) {
        return result;
    }
    expression.eachChild((child) => {
        const childResult = findZoomCurve(child);
        if (childResult instanceof ExpressionParsingError) {
            result = childResult;
        }
        else if (!result && childResult) {
            result = new ExpressionParsingError('', '"zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.');
        }
        else if (result && childResult && result !== childResult) {
            result = new ExpressionParsingError('', 'Only one zoom-based "step" or "interpolate" subexpression may be used in an expression.');
        }
    });
    return result;
}
function getExpectedType(spec) {
    const types = {
        color: ColorType,
        string: StringType,
        number: NumberType,
        enum: StringType,
        boolean: BooleanType,
        formatted: FormattedType,
        padding: PaddingType,
        projectionDefinition: ProjectionDefinitionType,
        resolvedImage: ResolvedImageType,
        variableAnchorOffsetCollection: VariableAnchorOffsetCollectionType
    };
    if (spec.type === 'array') {
        return array(types[spec.value] || ValueType, spec.length);
    }
    return types[spec.type];
}
function getDefaultValue(spec) {
    if (spec.type === 'color' && isFunction(spec.default)) {
        // Special case for heatmap-color: it uses the 'default:' to define a
        // default color ramp, but createExpression expects a simple value to fall
        // back to in case of runtime errors
        return new Color(0, 0, 0, 0);
    }
    else if (spec.type === 'color') {
        return Color.parse(spec.default) || null;
    }
    else if (spec.type === 'padding') {
        return Padding.parse(spec.default) || null;
    }
    else if (spec.type === 'variableAnchorOffsetCollection') {
        return VariableAnchorOffsetCollection.parse(spec.default) || null;
    }
    else if (spec.type === 'projectionDefinition') {
        return ProjectionDefinition.parse(spec.default) || null;
    }
    else if (spec.default === undefined) {
        return null;
    }
    else {
        return spec.default;
    }
}

function validateObject(options) {
    const key = options.key;
    const object = options.value;
    const elementSpecs = options.valueSpec || {};
    const elementValidators = options.objectElementValidators || {};
    const style = options.style;
    const styleSpec = options.styleSpec;
    const validateSpec = options.validateSpec;
    let errors = [];
    const type = getType(object);
    if (type !== 'object') {
        return [new ValidationError(key, object, `object expected, ${type} found`)];
    }
    for (const objectKey in object) {
        const elementSpecKey = objectKey.split('.')[0]; // treat 'paint.*' as 'paint'
        const elementSpec = elementSpecs[elementSpecKey] || elementSpecs['*'];
        let validateElement;
        if (elementValidators[elementSpecKey]) {
            validateElement = elementValidators[elementSpecKey];
        }
        else if (elementSpecs[elementSpecKey]) {
            validateElement = validateSpec;
        }
        else if (elementValidators['*']) {
            validateElement = elementValidators['*'];
        }
        else if (elementSpecs['*']) {
            validateElement = validateSpec;
        }
        else {
            errors.push(new ValidationError(key, object[objectKey], `unknown property "${objectKey}"`));
            continue;
        }
        errors = errors.concat(validateElement({
            key: (key ? `${key}.` : key) + objectKey,
            value: object[objectKey],
            valueSpec: elementSpec,
            style,
            styleSpec,
            object,
            objectKey,
            validateSpec,
        }, object));
    }
    for (const elementSpecKey in elementSpecs) {
        // Don't check `required` when there's a custom validator for that property.
        if (elementValidators[elementSpecKey]) {
            continue;
        }
        if (elementSpecs[elementSpecKey].required && elementSpecs[elementSpecKey]['default'] === undefined && object[elementSpecKey] === undefined) {
            errors.push(new ValidationError(key, object, `missing required property "${elementSpecKey}"`));
        }
    }
    return errors;
}

function validateArray(options) {
    const array = options.value;
    const arraySpec = options.valueSpec;
    const validateSpec = options.validateSpec;
    const style = options.style;
    const styleSpec = options.styleSpec;
    const key = options.key;
    const validateArrayElement = options.arrayElementValidator || validateSpec;
    if (getType(array) !== 'array') {
        return [new ValidationError(key, array, `array expected, ${getType(array)} found`)];
    }
    if (arraySpec.length && array.length !== arraySpec.length) {
        return [new ValidationError(key, array, `array length ${arraySpec.length} expected, length ${array.length} found`)];
    }
    if (arraySpec['min-length'] && array.length < arraySpec['min-length']) {
        return [new ValidationError(key, array, `array length at least ${arraySpec['min-length']} expected, length ${array.length} found`)];
    }
    let arrayElementSpec = {
        'type': arraySpec.value,
        'values': arraySpec.values
    };
    if (styleSpec.$version < 7) {
        arrayElementSpec['function'] = arraySpec.function;
    }
    if (getType(arraySpec.value) === 'object') {
        arrayElementSpec = arraySpec.value;
    }
    let errors = [];
    for (let i = 0; i < array.length; i++) {
        errors = errors.concat(validateArrayElement({
            array,
            arrayIndex: i,
            value: array[i],
            valueSpec: arrayElementSpec,
            validateSpec: options.validateSpec,
            style,
            styleSpec,
            key: `${key}[${i}]`
        }));
    }
    return errors;
}

function validateNumber(options) {
    const key = options.key;
    const value = options.value;
    const valueSpec = options.valueSpec;
    let type = getType(value);
    if (type === 'number' && value !== value) {
        type = 'NaN';
    }
    if (type !== 'number') {
        return [new ValidationError(key, value, `number expected, ${type} found`)];
    }
    if ('minimum' in valueSpec && value < valueSpec.minimum) {
        return [new ValidationError(key, value, `${value} is less than the minimum value ${valueSpec.minimum}`)];
    }
    if ('maximum' in valueSpec && value > valueSpec.maximum) {
        return [new ValidationError(key, value, `${value} is greater than the maximum value ${valueSpec.maximum}`)];
    }
    return [];
}

function validateFunction(options) {
    const functionValueSpec = options.valueSpec;
    const functionType = unbundle(options.value.type);
    let stopKeyType;
    let stopDomainValues = {};
    let previousStopDomainValue;
    let previousStopDomainZoom;
    const isZoomFunction = functionType !== 'categorical' && options.value.property === undefined;
    const isPropertyFunction = !isZoomFunction;
    const isZoomAndPropertyFunction = getType(options.value.stops) === 'array' &&
        getType(options.value.stops[0]) === 'array' &&
        getType(options.value.stops[0][0]) === 'object';
    const errors = validateObject({
        key: options.key,
        value: options.value,
        valueSpec: options.styleSpec.function,
        validateSpec: options.validateSpec,
        style: options.style,
        styleSpec: options.styleSpec,
        objectElementValidators: {
            stops: validateFunctionStops,
            default: validateFunctionDefault
        }
    });
    if (functionType === 'identity' && isZoomFunction) {
        errors.push(new ValidationError(options.key, options.value, 'missing required property "property"'));
    }
    if (functionType !== 'identity' && !options.value.stops) {
        errors.push(new ValidationError(options.key, options.value, 'missing required property "stops"'));
    }
    if (functionType === 'exponential' && options.valueSpec.expression && !supportsInterpolation(options.valueSpec)) {
        errors.push(new ValidationError(options.key, options.value, 'exponential functions not supported'));
    }
    if (options.styleSpec.$version >= 8) {
        if (isPropertyFunction && !supportsPropertyExpression(options.valueSpec)) {
            errors.push(new ValidationError(options.key, options.value, 'property functions not supported'));
        }
        else if (isZoomFunction && !supportsZoomExpression(options.valueSpec)) {
            errors.push(new ValidationError(options.key, options.value, 'zoom functions not supported'));
        }
    }
    if ((functionType === 'categorical' || isZoomAndPropertyFunction) && options.value.property === undefined) {
        errors.push(new ValidationError(options.key, options.value, '"property" property is required'));
    }
    return errors;
    function validateFunctionStops(options) {
        if (functionType === 'identity') {
            return [new ValidationError(options.key, options.value, 'identity function may not have a "stops" property')];
        }
        let errors = [];
        const value = options.value;
        errors = errors.concat(validateArray({
            key: options.key,
            value,
            valueSpec: options.valueSpec,
            validateSpec: options.validateSpec,
            style: options.style,
            styleSpec: options.styleSpec,
            arrayElementValidator: validateFunctionStop
        }));
        if (getType(value) === 'array' && value.length === 0) {
            errors.push(new ValidationError(options.key, value, 'array must have at least one stop'));
        }
        return errors;
    }
    function validateFunctionStop(options) {
        let errors = [];
        const value = options.value;
        const key = options.key;
        if (getType(value) !== 'array') {
            return [new ValidationError(key, value, `array expected, ${getType(value)} found`)];
        }
        if (value.length !== 2) {
            return [new ValidationError(key, value, `array length 2 expected, length ${value.length} found`)];
        }
        if (isZoomAndPropertyFunction) {
            if (getType(value[0]) !== 'object') {
                return [new ValidationError(key, value, `object expected, ${getType(value[0])} found`)];
            }
            if (value[0].zoom === undefined) {
                return [new ValidationError(key, value, 'object stop key must have zoom')];
            }
            if (value[0].value === undefined) {
                return [new ValidationError(key, value, 'object stop key must have value')];
            }
            if (previousStopDomainZoom && previousStopDomainZoom > unbundle(value[0].zoom)) {
                return [new ValidationError(key, value[0].zoom, 'stop zoom values must appear in ascending order')];
            }
            if (unbundle(value[0].zoom) !== previousStopDomainZoom) {
                previousStopDomainZoom = unbundle(value[0].zoom);
                previousStopDomainValue = undefined;
                stopDomainValues = {};
            }
            errors = errors.concat(validateObject({
                key: `${key}[0]`,
                value: value[0],
                valueSpec: { zoom: {} },
                validateSpec: options.validateSpec,
                style: options.style,
                styleSpec: options.styleSpec,
                objectElementValidators: { zoom: validateNumber, value: validateStopDomainValue }
            }));
        }
        else {
            errors = errors.concat(validateStopDomainValue({
                key: `${key}[0]`,
                value: value[0],
                valueSpec: {},
                validateSpec: options.validateSpec,
                style: options.style,
                styleSpec: options.styleSpec
            }, value));
        }
        if (isExpression(deepUnbundle(value[1]))) {
            return errors.concat([new ValidationError(`${key}[1]`, value[1], 'expressions are not allowed in function stops.')]);
        }
        return errors.concat(options.validateSpec({
            key: `${key}[1]`,
            value: value[1],
            valueSpec: functionValueSpec,
            validateSpec: options.validateSpec,
            style: options.style,
            styleSpec: options.styleSpec
        }));
    }
    function validateStopDomainValue(options, stop) {
        const type = getType(options.value);
        const value = unbundle(options.value);
        const reportValue = options.value !== null ? options.value : stop;
        if (!stopKeyType) {
            stopKeyType = type;
        }
        else if (type !== stopKeyType) {
            return [new ValidationError(options.key, reportValue, `${type} stop domain type must match previous stop domain type ${stopKeyType}`)];
        }
        if (type !== 'number' && type !== 'string' && type !== 'boolean') {
            return [new ValidationError(options.key, reportValue, 'stop domain value must be a number, string, or boolean')];
        }
        if (type !== 'number' && functionType !== 'categorical') {
            let message = `number expected, ${type} found`;
            if (supportsPropertyExpression(functionValueSpec) && functionType === undefined) {
                message += '\nIf you intended to use a categorical function, specify `"type": "categorical"`.';
            }
            return [new ValidationError(options.key, reportValue, message)];
        }
        if (functionType === 'categorical' && type === 'number' && (!isFinite(value) || Math.floor(value) !== value)) {
            return [new ValidationError(options.key, reportValue, `integer expected, found ${value}`)];
        }
        if (functionType !== 'categorical' && type === 'number' && previousStopDomainValue !== undefined && value < previousStopDomainValue) {
            return [new ValidationError(options.key, reportValue, 'stop domain values must appear in ascending order')];
        }
        else {
            previousStopDomainValue = value;
        }
        if (functionType === 'categorical' && value in stopDomainValues) {
            return [new ValidationError(options.key, reportValue, 'stop domain values must be unique')];
        }
        else {
            stopDomainValues[value] = true;
        }
        return [];
    }
    function validateFunctionDefault(options) {
        return options.validateSpec({
            key: options.key,
            value: options.value,
            valueSpec: functionValueSpec,
            validateSpec: options.validateSpec,
            style: options.style,
            styleSpec: options.styleSpec
        });
    }
}

function validateExpression(options) {
    const expression = (options.expressionContext === 'property' ? createPropertyExpression : createExpression)(deepUnbundle(options.value), options.valueSpec);
    if (expression.result === 'error') {
        return expression.value.map((error) => {
            return new ValidationError(`${options.key}${error.key}`, options.value, error.message);
        });
    }
    const expressionObj = expression.value.expression || expression.value._styleExpression.expression;
    if (options.expressionContext === 'property' && (options.propertyKey === 'text-font') &&
        !expressionObj.outputDefined()) {
        return [new ValidationError(options.key, options.value, `Invalid data expression for "${options.propertyKey}". Output values must be contained as literals within the expression.`)];
    }
    if (options.expressionContext === 'property' && options.propertyType === 'layout' &&
        (!isStateConstant(expressionObj))) {
        return [new ValidationError(options.key, options.value, '"feature-state" data expressions are not supported with layout properties.')];
    }
    if (options.expressionContext === 'filter' && !isStateConstant(expressionObj)) {
        return [new ValidationError(options.key, options.value, '"feature-state" data expressions are not supported with filters.')];
    }
    if (options.expressionContext && options.expressionContext.indexOf('cluster') === 0) {
        if (!isGlobalPropertyConstant(expressionObj, ['zoom', 'feature-state'])) {
            return [new ValidationError(options.key, options.value, '"zoom" and "feature-state" expressions are not supported with cluster properties.')];
        }
        if (options.expressionContext === 'cluster-initial' && !isFeatureConstant(expressionObj)) {
            return [new ValidationError(options.key, options.value, 'Feature data expressions are not supported with initial expression part of cluster properties.')];
        }
    }
    return [];
}

function validateBoolean(options) {
    const value = options.value;
    const key = options.key;
    const type = getType(value);
    if (type !== 'boolean') {
        return [new ValidationError(key, value, `boolean expected, ${type} found`)];
    }
    return [];
}

function validateColor(options) {
    const key = options.key;
    const value = options.value;
    const type = getType(value);
    if (type !== 'string') {
        return [new ValidationError(key, value, `color expected, ${type} found`)];
    }
    if (!Color.parse(String(value))) { // cast String object to string primitive
        return [new ValidationError(key, value, `color expected, "${value}" found`)];
    }
    return [];
}

function validateEnum(options) {
    const key = options.key;
    const value = options.value;
    const valueSpec = options.valueSpec;
    const errors = [];
    if (Array.isArray(valueSpec.values)) { // <=v7
        if (valueSpec.values.indexOf(unbundle(value)) === -1) {
            errors.push(new ValidationError(key, value, `expected one of [${valueSpec.values.join(', ')}], ${JSON.stringify(value)} found`));
        }
    }
    else { // >=v8
        if (Object.keys(valueSpec.values).indexOf(unbundle(value)) === -1) {
            errors.push(new ValidationError(key, value, `expected one of [${Object.keys(valueSpec.values).join(', ')}], ${JSON.stringify(value)} found`));
        }
    }
    return errors;
}

function isExpressionFilter(filter) {
    if (filter === true || filter === false) {
        return true;
    }
    if (!Array.isArray(filter) || filter.length === 0) {
        return false;
    }
    switch (filter[0]) {
        case 'has':
            return filter.length >= 2 && filter[1] !== '$id' && filter[1] !== '$type';
        case 'in':
            return filter.length >= 3 && (typeof filter[1] !== 'string' || Array.isArray(filter[2]));
        case '!in':
        case '!has':
        case 'none':
            return false;
        case '==':
        case '!=':
        case '>':
        case '>=':
        case '<':
        case '<=':
            return filter.length !== 3 || (Array.isArray(filter[1]) || Array.isArray(filter[2]));
        case 'any':
        case 'all':
            for (const f of filter.slice(1)) {
                if (!isExpressionFilter(f) && typeof f !== 'boolean') {
                    return false;
                }
            }
            return true;
        default:
            return true;
    }
}

function validateFilter(options) {
    if (isExpressionFilter(deepUnbundle(options.value))) {
        return validateExpression(extendBy({}, options, {
            expressionContext: 'filter',
            valueSpec: { value: 'boolean' }
        }));
    }
    else {
        return validateNonExpressionFilter(options);
    }
}
function validateNonExpressionFilter(options) {
    const value = options.value;
    const key = options.key;
    if (getType(value) !== 'array') {
        return [new ValidationError(key, value, `array expected, ${getType(value)} found`)];
    }
    const styleSpec = options.styleSpec;
    let type;
    let errors = [];
    if (value.length < 1) {
        return [new ValidationError(key, value, 'filter array must have at least 1 element')];
    }
    errors = errors.concat(validateEnum({
        key: `${key}[0]`,
        value: value[0],
        valueSpec: styleSpec.filter_operator,
        style: options.style,
        styleSpec: options.styleSpec
    }));
    switch (unbundle(value[0])) {
        case '<':
        case '<=':
        case '>':
        case '>=':
            if (value.length >= 2 && unbundle(value[1]) === '$type') {
                errors.push(new ValidationError(key, value, `"$type" cannot be use with operator "${value[0]}"`));
            }
        /* falls through */
        case '==':
        case '!=':
            if (value.length !== 3) {
                errors.push(new ValidationError(key, value, `filter array for operator "${value[0]}" must have 3 elements`));
            }
        /* falls through */
        case 'in':
        case '!in':
            if (value.length >= 2) {
                type = getType(value[1]);
                if (type !== 'string') {
                    errors.push(new ValidationError(`${key}[1]`, value[1], `string expected, ${type} found`));
                }
            }
            for (let i = 2; i < value.length; i++) {
                type = getType(value[i]);
                if (unbundle(value[1]) === '$type') {
                    errors = errors.concat(validateEnum({
                        key: `${key}[${i}]`,
                        value: value[i],
                        valueSpec: styleSpec.geometry_type,
                        style: options.style,
                        styleSpec: options.styleSpec
                    }));
                }
                else if (type !== 'string' && type !== 'number' && type !== 'boolean') {
                    errors.push(new ValidationError(`${key}[${i}]`, value[i], `string, number, or boolean expected, ${type} found`));
                }
            }
            break;
        case 'any':
        case 'all':
        case 'none':
            for (let i = 1; i < value.length; i++) {
                errors = errors.concat(validateNonExpressionFilter({
                    key: `${key}[${i}]`,
                    value: value[i],
                    style: options.style,
                    styleSpec: options.styleSpec
                }));
            }
            break;
        case 'has':
        case '!has':
            type = getType(value[1]);
            if (value.length !== 2) {
                errors.push(new ValidationError(key, value, `filter array for "${value[0]}" operator must have 2 elements`));
            }
            else if (type !== 'string') {
                errors.push(new ValidationError(`${key}[1]`, value[1], `string expected, ${type} found`));
            }
            break;
    }
    return errors;
}

function validateProperty(options, propertyType) {
    const key = options.key;
    const validateSpec = options.validateSpec;
    const style = options.style;
    const styleSpec = options.styleSpec;
    const value = options.value;
    const propertyKey = options.objectKey;
    const layerSpec = styleSpec[`${propertyType}_${options.layerType}`];
    if (!layerSpec)
        return [];
    const transitionMatch = propertyKey.match(/^(.*)-transition$/);
    if (propertyType === 'paint' && transitionMatch && layerSpec[transitionMatch[1]] && layerSpec[transitionMatch[1]].transition) {
        return validateSpec({
            key,
            value,
            valueSpec: styleSpec.transition,
            style,
            styleSpec
        });
    }
    const valueSpec = options.valueSpec || layerSpec[propertyKey];
    if (!valueSpec) {
        return [new ValidationError(key, value, `unknown property "${propertyKey}"`)];
    }
    let tokenMatch;
    if (getType(value) === 'string' && supportsPropertyExpression(valueSpec) && !valueSpec.tokens && (tokenMatch = /^{([^}]+)}$/.exec(value))) {
        return [new ValidationError(key, value, `"${propertyKey}" does not support interpolation syntax\n` +
                `Use an identity property function instead: \`{ "type": "identity", "property": ${JSON.stringify(tokenMatch[1])} }\`.`)];
    }
    const errors = [];
    if (options.layerType === 'symbol') {
        if (propertyKey === 'text-field' && style && !style.glyphs) {
            errors.push(new ValidationError(key, value, 'use of "text-field" requires a style "glyphs" property'));
        }
        if (propertyKey === 'text-font' && isFunction(deepUnbundle(value)) && unbundle(value.type) === 'identity') {
            errors.push(new ValidationError(key, value, '"text-font" does not support identity functions'));
        }
    }
    return errors.concat(validateSpec({
        key: options.key,
        value,
        valueSpec,
        style,
        styleSpec,
        expressionContext: 'property',
        propertyType,
        propertyKey
    }));
}

function validatePaintProperty(options) {
    return validateProperty(options, 'paint');
}

function validateLayoutProperty(options) {
    return validateProperty(options, 'layout');
}

function validateLayer(options) {
    let errors = [];
    const layer = options.value;
    const key = options.key;
    const style = options.style;
    const styleSpec = options.styleSpec;
    if (!layer.type && !layer.ref) {
        errors.push(new ValidationError(key, layer, 'either "type" or "ref" is required'));
    }
    let type = unbundle(layer.type);
    const ref = unbundle(layer.ref);
    if (layer.id) {
        const layerId = unbundle(layer.id);
        for (let i = 0; i < options.arrayIndex; i++) {
            const otherLayer = style.layers[i];
            if (unbundle(otherLayer.id) === layerId) {
                errors.push(new ValidationError(key, layer.id, `duplicate layer id "${layer.id}", previously used at line ${otherLayer.id.__line__}`));
            }
        }
    }
    if ('ref' in layer) {
        ['type', 'source', 'source-layer', 'filter', 'layout'].forEach((p) => {
            if (p in layer) {
                errors.push(new ValidationError(key, layer[p], `"${p}" is prohibited for ref layers`));
            }
        });
        let parent;
        style.layers.forEach((layer) => {
            if (unbundle(layer.id) === ref)
                parent = layer;
        });
        if (!parent) {
            errors.push(new ValidationError(key, layer.ref, `ref layer "${ref}" not found`));
        }
        else if (parent.ref) {
            errors.push(new ValidationError(key, layer.ref, 'ref cannot reference another ref layer'));
        }
        else {
            type = unbundle(parent.type);
        }
    }
    else if (type !== 'background') {
        if (!layer.source) {
            errors.push(new ValidationError(key, layer, 'missing required property "source"'));
        }
        else {
            const source = style.sources && style.sources[layer.source];
            const sourceType = source && unbundle(source.type);
            if (!source) {
                errors.push(new ValidationError(key, layer.source, `source "${layer.source}" not found`));
            }
            else if (sourceType === 'vector' && type === 'raster') {
                errors.push(new ValidationError(key, layer.source, `layer "${layer.id}" requires a raster source`));
            }
            else if (sourceType !== 'raster-dem' && type === 'hillshade') {
                errors.push(new ValidationError(key, layer.source, `layer "${layer.id}" requires a raster-dem source`));
            }
            else if (sourceType === 'raster' && type !== 'raster') {
                errors.push(new ValidationError(key, layer.source, `layer "${layer.id}" requires a vector source`));
            }
            else if (sourceType === 'vector' && !layer['source-layer']) {
                errors.push(new ValidationError(key, layer, `layer "${layer.id}" must specify a "source-layer"`));
            }
            else if (sourceType === 'raster-dem' && type !== 'hillshade') {
                errors.push(new ValidationError(key, layer.source, 'raster-dem source can only be used with layer type \'hillshade\'.'));
            }
            else if (type === 'line' && layer.paint && layer.paint['line-gradient'] &&
                (sourceType !== 'geojson' || !source.lineMetrics)) {
                errors.push(new ValidationError(key, layer, `layer "${layer.id}" specifies a line-gradient, which requires a GeoJSON source with \`lineMetrics\` enabled.`));
            }
        }
    }
    errors = errors.concat(validateObject({
        key,
        value: layer,
        valueSpec: styleSpec.layer,
        style: options.style,
        styleSpec: options.styleSpec,
        validateSpec: options.validateSpec,
        objectElementValidators: {
            '*'() {
                return [];
            },
            // We don't want to enforce the spec's `"requires": true` for backward compatibility with refs;
            // the actual requirement is validated above. See https://github.com/mapbox/mapbox-gl-js/issues/5772.
            type() {
                return options.validateSpec({
                    key: `${key}.type`,
                    value: layer.type,
                    valueSpec: styleSpec.layer.type,
                    style: options.style,
                    styleSpec: options.styleSpec,
                    validateSpec: options.validateSpec,
                    object: layer,
                    objectKey: 'type'
                });
            },
            filter: validateFilter,
            layout(options) {
                return validateObject({
                    layer,
                    key: options.key,
                    value: options.value,
                    style: options.style,
                    styleSpec: options.styleSpec,
                    validateSpec: options.validateSpec,
                    objectElementValidators: {
                        '*'(options) {
                            return validateLayoutProperty(extendBy({ layerType: type }, options));
                        }
                    }
                });
            },
            paint(options) {
                return validateObject({
                    layer,
                    key: options.key,
                    value: options.value,
                    style: options.style,
                    styleSpec: options.styleSpec,
                    validateSpec: options.validateSpec,
                    objectElementValidators: {
                        '*'(options) {
                            return validatePaintProperty(extendBy({ layerType: type }, options));
                        }
                    }
                });
            }
        }
    }));
    return errors;
}

function validateString(options) {
    const value = options.value;
    const key = options.key;
    const type = getType(value);
    if (type !== 'string') {
        return [new ValidationError(key, value, `string expected, ${type} found`)];
    }
    return [];
}

function validateRasterDEMSource(options) {
    var _a;
    const sourceName = (_a = options.sourceName) !== null && _a !== undefined ? _a : '';
    const rasterDEM = options.value;
    const styleSpec = options.styleSpec;
    const rasterDEMSpec = styleSpec.source_raster_dem;
    const style = options.style;
    let errors = [];
    const rootType = getType(rasterDEM);
    if (rasterDEM === undefined) {
        return errors;
    }
    else if (rootType !== 'object') {
        errors.push(new ValidationError('source_raster_dem', rasterDEM, `object expected, ${rootType} found`));
        return errors;
    }
    const encoding = unbundle(rasterDEM.encoding);
    const isCustomEncoding = encoding === 'custom';
    const customEncodingKeys = ['redFactor', 'greenFactor', 'blueFactor', 'baseShift'];
    const encodingName = options.value.encoding ? `"${options.value.encoding}"` : 'Default';
    for (const key in rasterDEM) {
        if (!isCustomEncoding && customEncodingKeys.includes(key)) {
            errors.push(new ValidationError(key, rasterDEM[key], `In "${sourceName}": "${key}" is only valid when "encoding" is set to "custom". ${encodingName} encoding found`));
        }
        else if (rasterDEMSpec[key]) {
            errors = errors.concat(options.validateSpec({
                key,
                value: rasterDEM[key],
                valueSpec: rasterDEMSpec[key],
                validateSpec: options.validateSpec,
                style,
                styleSpec
            }));
        }
        else {
            errors.push(new ValidationError(key, rasterDEM[key], `unknown property "${key}"`));
        }
    }
    return errors;
}

const objectElementValidators = {
    promoteId: validatePromoteId
};
function validateSource(options) {
    const value = options.value;
    const key = options.key;
    const styleSpec = options.styleSpec;
    const style = options.style;
    const validateSpec = options.validateSpec;
    if (!value.type) {
        return [new ValidationError(key, value, '"type" is required')];
    }
    const type = unbundle(value.type);
    let errors;
    switch (type) {
        case 'vector':
        case 'raster':
            errors = validateObject({
                key,
                value,
                valueSpec: styleSpec[`source_${type.replace('-', '_')}`],
                style: options.style,
                styleSpec,
                objectElementValidators,
                validateSpec,
            });
            return errors;
        case 'raster-dem':
            errors = validateRasterDEMSource({
                sourceName: key,
                value,
                style: options.style,
                styleSpec,
                validateSpec,
            });
            return errors;
        case 'geojson':
            errors = validateObject({
                key,
                value,
                valueSpec: styleSpec.source_geojson,
                style,
                styleSpec,
                validateSpec,
                objectElementValidators
            });
            if (value.cluster) {
                for (const prop in value.clusterProperties) {
                    const [operator, mapExpr] = value.clusterProperties[prop];
                    const reduceExpr = typeof operator === 'string' ? [operator, ['accumulated'], ['get', prop]] : operator;
                    errors.push(...validateExpression({
                        key: `${key}.${prop}.map`,
                        value: mapExpr,
                        validateSpec,
                        expressionContext: 'cluster-map'
                    }));
                    errors.push(...validateExpression({
                        key: `${key}.${prop}.reduce`,
                        value: reduceExpr,
                        validateSpec,
                        expressionContext: 'cluster-reduce'
                    }));
                }
            }
            return errors;
        case 'video':
            return validateObject({
                key,
                value,
                valueSpec: styleSpec.source_video,
                style,
                validateSpec,
                styleSpec
            });
        case 'image':
            return validateObject({
                key,
                value,
                valueSpec: styleSpec.source_image,
                style,
                validateSpec,
                styleSpec
            });
        case 'canvas':
            return [new ValidationError(key, null, 'Please use runtime APIs to add canvas sources, rather than including them in stylesheets.', 'source.canvas')];
        default:
            return validateEnum({
                key: `${key}.type`,
                value: value.type,
                valueSpec: { values: ['vector', 'raster', 'raster-dem', 'geojson', 'video', 'image'] },
                style,
                validateSpec,
                styleSpec
            });
    }
}
function validatePromoteId({ key, value }) {
    if (getType(value) === 'string') {
        return validateString({ key, value });
    }
    else {
        const errors = [];
        for (const prop in value) {
            errors.push(...validateString({ key: `${key}.${prop}`, value: value[prop] }));
        }
        return errors;
    }
}

function validateLight(options) {
    const light = options.value;
    const styleSpec = options.styleSpec;
    const lightSpec = styleSpec.light;
    const style = options.style;
    let errors = [];
    const rootType = getType(light);
    if (light === undefined) {
        return errors;
    }
    else if (rootType !== 'object') {
        errors = errors.concat([new ValidationError('light', light, `object expected, ${rootType} found`)]);
        return errors;
    }
    for (const key in light) {
        const transitionMatch = key.match(/^(.*)-transition$/);
        if (transitionMatch && lightSpec[transitionMatch[1]] && lightSpec[transitionMatch[1]].transition) {
            errors = errors.concat(options.validateSpec({
                key,
                value: light[key],
                valueSpec: styleSpec.transition,
                validateSpec: options.validateSpec,
                style,
                styleSpec
            }));
        }
        else if (lightSpec[key]) {
            errors = errors.concat(options.validateSpec({
                key,
                value: light[key],
                valueSpec: lightSpec[key],
                validateSpec: options.validateSpec,
                style,
                styleSpec
            }));
        }
        else {
            errors = errors.concat([new ValidationError(key, light[key], `unknown property "${key}"`)]);
        }
    }
    return errors;
}

function validateSky(options) {
    const sky = options.value;
    const styleSpec = options.styleSpec;
    const skySpec = styleSpec.sky;
    const style = options.style;
    const rootType = getType(sky);
    if (sky === undefined) {
        return [];
    }
    else if (rootType !== 'object') {
        return [new ValidationError('sky', sky, `object expected, ${rootType} found`)];
    }
    let errors = [];
    for (const key in sky) {
        if (skySpec[key]) {
            errors = errors.concat(options.validateSpec({
                key,
                value: sky[key],
                valueSpec: skySpec[key],
                style,
                styleSpec
            }));
        }
        else {
            errors = errors.concat([new ValidationError(key, sky[key], `unknown property "${key}"`)]);
        }
    }
    return errors;
}

function validateTerrain(options) {
    const terrain = options.value;
    const styleSpec = options.styleSpec;
    const terrainSpec = styleSpec.terrain;
    const style = options.style;
    let errors = [];
    const rootType = getType(terrain);
    if (terrain === undefined) {
        return errors;
    }
    else if (rootType !== 'object') {
        errors = errors.concat([new ValidationError('terrain', terrain, `object expected, ${rootType} found`)]);
        return errors;
    }
    for (const key in terrain) {
        if (terrainSpec[key]) {
            errors = errors.concat(options.validateSpec({
                key,
                value: terrain[key],
                valueSpec: terrainSpec[key],
                validateSpec: options.validateSpec,
                style,
                styleSpec
            }));
        }
        else {
            errors = errors.concat([new ValidationError(key, terrain[key], `unknown property "${key}"`)]);
        }
    }
    return errors;
}

function validateFormatted(options) {
    if (validateString(options).length === 0) {
        return [];
    }
    return validateExpression(options);
}

function validateImage(options) {
    if (validateString(options).length === 0) {
        return [];
    }
    return validateExpression(options);
}

function validatePadding(options) {
    const key = options.key;
    const value = options.value;
    const type = getType(value);
    if (type === 'array') {
        if (value.length < 1 || value.length > 4) {
            return [new ValidationError(key, value, `padding requires 1 to 4 values; ${value.length} values found`)];
        }
        const arrayElementSpec = {
            type: 'number'
        };
        let errors = [];
        for (let i = 0; i < value.length; i++) {
            errors = errors.concat(options.validateSpec({
                key: `${key}[${i}]`,
                value: value[i],
                validateSpec: options.validateSpec,
                valueSpec: arrayElementSpec
            }));
        }
        return errors;
    }
    else {
        return validateNumber({
            key,
            value,
            valueSpec: {}
        });
    }
}

function validateVariableAnchorOffsetCollection(options) {
    const key = options.key;
    const value = options.value;
    const type = getType(value);
    const styleSpec = options.styleSpec;
    if (type !== 'array' || value.length < 1 || value.length % 2 !== 0) {
        return [new ValidationError(key, value, 'variableAnchorOffsetCollection requires a non-empty array of even length')];
    }
    let errors = [];
    for (let i = 0; i < value.length; i += 2) {
        // Elements in even positions should be values from text-anchor enum
        errors = errors.concat(validateEnum({
            key: `${key}[${i}]`,
            value: value[i],
            valueSpec: styleSpec['layout_symbol']['text-anchor']
        }));
        // Elements in odd positions should be points (2-element numeric arrays)
        errors = errors.concat(validateArray({
            key: `${key}[${i + 1}]`,
            value: value[i + 1],
            valueSpec: {
                length: 2,
                value: 'number'
            },
            validateSpec: options.validateSpec,
            style: options.style,
            styleSpec
        }));
    }
    return errors;
}

function validateSprite(options) {
    let errors = [];
    const sprite = options.value;
    const key = options.key;
    if (!Array.isArray(sprite)) {
        return validateString({
            key,
            value: sprite
        });
    }
    else {
        const allSpriteIds = [];
        const allSpriteURLs = [];
        for (const i in sprite) {
            if (sprite[i].id && allSpriteIds.includes(sprite[i].id))
                errors.push(new ValidationError(key, sprite, `all the sprites' ids must be unique, but ${sprite[i].id} is duplicated`));
            allSpriteIds.push(sprite[i].id);
            if (sprite[i].url && allSpriteURLs.includes(sprite[i].url))
                errors.push(new ValidationError(key, sprite, `all the sprites' URLs must be unique, but ${sprite[i].url} is duplicated`));
            allSpriteURLs.push(sprite[i].url);
            const pairSpec = {
                id: {
                    type: 'string',
                    required: true,
                },
                url: {
                    type: 'string',
                    required: true,
                }
            };
            errors = errors.concat(validateObject({
                key: `${key}[${i}]`,
                value: sprite[i],
                valueSpec: pairSpec,
                validateSpec: options.validateSpec,
            }));
        }
        return errors;
    }
}

function validateProjection(options) {
    const projection = options.value;
    const styleSpec = options.styleSpec;
    const projectionSpec = styleSpec.projection;
    const style = options.style;
    const rootType = getType(projection);
    if (projection === undefined) {
        return [];
    }
    else if (rootType !== 'object') {
        return [new ValidationError('projection', projection, `object expected, ${rootType} found`)];
    }
    let errors = [];
    for (const key in projection) {
        if (projectionSpec[key]) {
            errors = errors.concat(options.validateSpec({
                key,
                value: projection[key],
                valueSpec: projectionSpec[key],
                style,
                styleSpec
            }));
        }
        else {
            errors = errors.concat([new ValidationError(key, projection[key], `unknown property "${key}"`)]);
        }
    }
    return errors;
}

function validateProjectionDefinition(options) {
    const key = options.key;
    let value = options.value;
    value = value instanceof String ? value.valueOf() : value;
    const type = getType(value);
    if (type === 'array' && !isProjectionDefinitionValue(value) && !isPropertyValueSpecification(value)) {
        return [new ValidationError(key, value, `projection expected, invalid array ${JSON.stringify(value)} found`)];
    }
    else if (!['array', 'string'].includes(type)) {
        return [new ValidationError(key, value, `projection expected, invalid type "${type}" found`)];
    }
    return [];
}
function isPropertyValueSpecification(value) {
    if (['interpolate', 'step', 'literal'].includes(value[0])) {
        return true;
    }
    return false;
}
function isProjectionDefinitionValue(value) {
    return Array.isArray(value) &&
        value.length === 3 &&
        typeof value[0] === 'string' &&
        typeof value[1] === 'string' &&
        typeof value[2] === 'number';
}

const VALIDATORS = {
    '*'() {
        return [];
    },
    'array': validateArray,
    'boolean': validateBoolean,
    'number': validateNumber,
    'color': validateColor,
    'constants': validateConstants,
    'enum': validateEnum,
    'filter': validateFilter,
    'function': validateFunction,
    'layer': validateLayer,
    'object': validateObject,
    'source': validateSource,
    'light': validateLight,
    'sky': validateSky,
    'terrain': validateTerrain,
    'projection': validateProjection,
    'projectionDefinition': validateProjectionDefinition,
    'string': validateString,
    'formatted': validateFormatted,
    'resolvedImage': validateImage,
    'padding': validatePadding,
    'variableAnchorOffsetCollection': validateVariableAnchorOffsetCollection,
    'sprite': validateSprite,
};
/**
 * Main recursive validation function used internally.
 * You should use `validateStyleMin` in the browser or `validateStyle` in node env.
 * @param options - the options object
 * @param options.key - string representing location of validation in style tree. Used only
 * for more informative error reporting.
 * @param options.value - current value from style being evaluated. May be anything from a
 * high level object that needs to be descended into deeper or a simple
 * scalar value.
 * @param options.valueSpec - current spec being evaluated. Tracks value.
 * @param options.styleSpec - current full spec being evaluated.
 * @param options.validateSpec - the validate function itself
 * @param options.style - the style object
 * @param options.objectElementValidators - optional object of functions that will be called
 * @returns an array of errors, or an empty array if no errors are found.
 */
function validate(options) {
    const value = options.value;
    const valueSpec = options.valueSpec;
    const styleSpec = options.styleSpec;
    options.validateSpec = validate;
    if (valueSpec.expression && isFunction(unbundle(value))) {
        return validateFunction(options);
    }
    else if (valueSpec.expression && isExpression(deepUnbundle(value))) {
        return validateExpression(options);
    }
    else if (valueSpec.type && VALIDATORS[valueSpec.type]) {
        return VALIDATORS[valueSpec.type](options);
    }
    else {
        const valid = validateObject(extendBy({}, options, {
            valueSpec: valueSpec.type ? styleSpec[valueSpec.type] : valueSpec
        }));
        return valid;
    }
}

var $version = 8;
var $root = {
	version: {
		required: true,
		type: "enum",
		values: [
			8
		]
	},
	name: {
		type: "string"
	},
	metadata: {
		type: "*"
	},
	center: {
		type: "array",
		value: "number"
	},
	centerAltitude: {
		type: "number"
	},
	zoom: {
		type: "number"
	},
	bearing: {
		type: "number",
		"default": 0,
		period: 360,
		units: "degrees"
	},
	pitch: {
		type: "number",
		"default": 0,
		units: "degrees"
	},
	roll: {
		type: "number",
		"default": 0,
		units: "degrees"
	},
	light: {
		type: "light"
	},
	sky: {
		type: "sky"
	},
	projection: {
		type: "projection"
	},
	terrain: {
		type: "terrain"
	},
	sources: {
		required: true,
		type: "sources"
	},
	sprite: {
		type: "sprite"
	},
	glyphs: {
		type: "string"
	},
	transition: {
		type: "transition"
	},
	layers: {
		required: true,
		type: "array",
		value: "layer"
	}
};
var sources = {
	"*": {
		type: "source"
	}
};
var source = [
	"source_vector",
	"source_raster",
	"source_raster_dem",
	"source_geojson",
	"source_video",
	"source_image"
];
var source_vector = {
	type: {
		required: true,
		type: "enum",
		values: {
			vector: {
			}
		}
	},
	url: {
		type: "string"
	},
	tiles: {
		type: "array",
		value: "string"
	},
	bounds: {
		type: "array",
		value: "number",
		length: 4,
		"default": [
			-180,
			-85.051129,
			180,
			85.051129
		]
	},
	scheme: {
		type: "enum",
		values: {
			xyz: {
			},
			tms: {
			}
		},
		"default": "xyz"
	},
	minzoom: {
		type: "number",
		"default": 0
	},
	maxzoom: {
		type: "number",
		"default": 22
	},
	attribution: {
		type: "string"
	},
	promoteId: {
		type: "promoteId"
	},
	volatile: {
		type: "boolean",
		"default": false
	},
	"*": {
		type: "*"
	}
};
var source_raster = {
	type: {
		required: true,
		type: "enum",
		values: {
			raster: {
			}
		}
	},
	url: {
		type: "string"
	},
	tiles: {
		type: "array",
		value: "string"
	},
	bounds: {
		type: "array",
		value: "number",
		length: 4,
		"default": [
			-180,
			-85.051129,
			180,
			85.051129
		]
	},
	minzoom: {
		type: "number",
		"default": 0
	},
	maxzoom: {
		type: "number",
		"default": 22
	},
	tileSize: {
		type: "number",
		"default": 512,
		units: "pixels"
	},
	scheme: {
		type: "enum",
		values: {
			xyz: {
			},
			tms: {
			}
		},
		"default": "xyz"
	},
	attribution: {
		type: "string"
	},
	volatile: {
		type: "boolean",
		"default": false
	},
	"*": {
		type: "*"
	}
};
var source_raster_dem = {
	type: {
		required: true,
		type: "enum",
		values: {
			"raster-dem": {
			}
		}
	},
	url: {
		type: "string"
	},
	tiles: {
		type: "array",
		value: "string"
	},
	bounds: {
		type: "array",
		value: "number",
		length: 4,
		"default": [
			-180,
			-85.051129,
			180,
			85.051129
		]
	},
	minzoom: {
		type: "number",
		"default": 0
	},
	maxzoom: {
		type: "number",
		"default": 22
	},
	tileSize: {
		type: "number",
		"default": 512,
		units: "pixels"
	},
	attribution: {
		type: "string"
	},
	encoding: {
		type: "enum",
		values: {
			terrarium: {
			},
			mapbox: {
			},
			custom: {
			}
		},
		"default": "mapbox"
	},
	redFactor: {
		type: "number",
		"default": 1
	},
	blueFactor: {
		type: "number",
		"default": 1
	},
	greenFactor: {
		type: "number",
		"default": 1
	},
	baseShift: {
		type: "number",
		"default": 0
	},
	volatile: {
		type: "boolean",
		"default": false
	},
	"*": {
		type: "*"
	}
};
var source_geojson = {
	type: {
		required: true,
		type: "enum",
		values: {
			geojson: {
			}
		}
	},
	data: {
		required: true,
		type: "*"
	},
	maxzoom: {
		type: "number",
		"default": 18
	},
	attribution: {
		type: "string"
	},
	buffer: {
		type: "number",
		"default": 128,
		maximum: 512,
		minimum: 0
	},
	filter: {
		type: "*"
	},
	tolerance: {
		type: "number",
		"default": 0.375
	},
	cluster: {
		type: "boolean",
		"default": false
	},
	clusterRadius: {
		type: "number",
		"default": 50,
		minimum: 0
	},
	clusterMaxZoom: {
		type: "number"
	},
	clusterMinPoints: {
		type: "number"
	},
	clusterProperties: {
		type: "*"
	},
	lineMetrics: {
		type: "boolean",
		"default": false
	},
	generateId: {
		type: "boolean",
		"default": false
	},
	promoteId: {
		type: "promoteId"
	}
};
var source_video = {
	type: {
		required: true,
		type: "enum",
		values: {
			video: {
			}
		}
	},
	urls: {
		required: true,
		type: "array",
		value: "string"
	},
	coordinates: {
		required: true,
		type: "array",
		length: 4,
		value: {
			type: "array",
			length: 2,
			value: "number"
		}
	}
};
var source_image = {
	type: {
		required: true,
		type: "enum",
		values: {
			image: {
			}
		}
	},
	url: {
		required: true,
		type: "string"
	},
	coordinates: {
		required: true,
		type: "array",
		length: 4,
		value: {
			type: "array",
			length: 2,
			value: "number"
		}
	}
};
var layer = {
	id: {
		type: "string",
		required: true
	},
	type: {
		type: "enum",
		values: {
			fill: {
			},
			line: {
			},
			symbol: {
			},
			circle: {
			},
			heatmap: {
			},
			"fill-extrusion": {
			},
			raster: {
			},
			hillshade: {
			},
			background: {
			}
		},
		required: true
	},
	metadata: {
		type: "*"
	},
	source: {
		type: "string"
	},
	"source-layer": {
		type: "string"
	},
	minzoom: {
		type: "number",
		minimum: 0,
		maximum: 24
	},
	maxzoom: {
		type: "number",
		minimum: 0,
		maximum: 24
	},
	filter: {
		type: "filter"
	},
	layout: {
		type: "layout"
	},
	paint: {
		type: "paint"
	}
};
var layout = [
	"layout_fill",
	"layout_line",
	"layout_circle",
	"layout_heatmap",
	"layout_fill-extrusion",
	"layout_symbol",
	"layout_raster",
	"layout_hillshade",
	"layout_background"
];
var layout_background = {
	visibility: {
		type: "enum",
		values: {
			visible: {
			},
			none: {
			}
		},
		"default": "visible",
		"property-type": "constant"
	}
};
var layout_fill = {
	"fill-sort-key": {
		type: "number",
		expression: {
			interpolated: false,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	visibility: {
		type: "enum",
		values: {
			visible: {
			},
			none: {
			}
		},
		"default": "visible",
		"property-type": "constant"
	}
};
var layout_circle = {
	"circle-sort-key": {
		type: "number",
		expression: {
			interpolated: false,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	visibility: {
		type: "enum",
		values: {
			visible: {
			},
			none: {
			}
		},
		"default": "visible",
		"property-type": "constant"
	}
};
var layout_heatmap = {
	visibility: {
		type: "enum",
		values: {
			visible: {
			},
			none: {
			}
		},
		"default": "visible",
		"property-type": "constant"
	}
};
var layout_line = {
	"line-cap": {
		type: "enum",
		values: {
			butt: {
			},
			round: {
			},
			square: {
			}
		},
		"default": "butt",
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"line-join": {
		type: "enum",
		values: {
			bevel: {
			},
			round: {
			},
			miter: {
			}
		},
		"default": "miter",
		expression: {
			interpolated: false,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	"line-miter-limit": {
		type: "number",
		"default": 2,
		requires: [
			{
				"line-join": "miter"
			}
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"line-round-limit": {
		type: "number",
		"default": 1.05,
		requires: [
			{
				"line-join": "round"
			}
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"line-sort-key": {
		type: "number",
		expression: {
			interpolated: false,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	visibility: {
		type: "enum",
		values: {
			visible: {
			},
			none: {
			}
		},
		"default": "visible",
		"property-type": "constant"
	}
};
var layout_symbol = {
	"symbol-placement": {
		type: "enum",
		values: {
			point: {
			},
			line: {
			},
			"line-center": {
			}
		},
		"default": "point",
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"symbol-spacing": {
		type: "number",
		"default": 250,
		minimum: 1,
		units: "pixels",
		requires: [
			{
				"symbol-placement": "line"
			}
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"symbol-avoid-edges": {
		type: "boolean",
		"default": false,
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"symbol-sort-key": {
		type: "number",
		expression: {
			interpolated: false,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	"symbol-z-order": {
		type: "enum",
		values: {
			auto: {
			},
			"viewport-y": {
			},
			source: {
			}
		},
		"default": "auto",
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"icon-allow-overlap": {
		type: "boolean",
		"default": false,
		requires: [
			"icon-image",
			{
				"!": "icon-overlap"
			}
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"icon-overlap": {
		type: "enum",
		values: {
			never: {
			},
			always: {
			},
			cooperative: {
			}
		},
		requires: [
			"icon-image"
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"icon-ignore-placement": {
		type: "boolean",
		"default": false,
		requires: [
			"icon-image"
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"icon-optional": {
		type: "boolean",
		"default": false,
		requires: [
			"icon-image",
			"text-field"
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"icon-rotation-alignment": {
		type: "enum",
		values: {
			map: {
			},
			viewport: {
			},
			auto: {
			}
		},
		"default": "auto",
		requires: [
			"icon-image"
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"icon-size": {
		type: "number",
		"default": 1,
		minimum: 0,
		units: "factor of the original icon size",
		requires: [
			"icon-image"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	"icon-text-fit": {
		type: "enum",
		values: {
			none: {
			},
			width: {
			},
			height: {
			},
			both: {
			}
		},
		"default": "none",
		requires: [
			"icon-image",
			"text-field"
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"icon-text-fit-padding": {
		type: "array",
		value: "number",
		length: 4,
		"default": [
			0,
			0,
			0,
			0
		],
		units: "pixels",
		requires: [
			"icon-image",
			"text-field",
			{
				"icon-text-fit": [
					"both",
					"width",
					"height"
				]
			}
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"icon-image": {
		type: "resolvedImage",
		tokens: true,
		expression: {
			interpolated: false,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	"icon-rotate": {
		type: "number",
		"default": 0,
		period: 360,
		units: "degrees",
		requires: [
			"icon-image"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	"icon-padding": {
		type: "padding",
		"default": [
			2
		],
		units: "pixels",
		requires: [
			"icon-image"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	"icon-keep-upright": {
		type: "boolean",
		"default": false,
		requires: [
			"icon-image",
			{
				"icon-rotation-alignment": "map"
			},
			{
				"symbol-placement": [
					"line",
					"line-center"
				]
			}
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"icon-offset": {
		type: "array",
		value: "number",
		length: 2,
		"default": [
			0,
			0
		],
		requires: [
			"icon-image"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	"icon-anchor": {
		type: "enum",
		values: {
			center: {
			},
			left: {
			},
			right: {
			},
			top: {
			},
			bottom: {
			},
			"top-left": {
			},
			"top-right": {
			},
			"bottom-left": {
			},
			"bottom-right": {
			}
		},
		"default": "center",
		requires: [
			"icon-image"
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	"icon-pitch-alignment": {
		type: "enum",
		values: {
			map: {
			},
			viewport: {
			},
			auto: {
			}
		},
		"default": "auto",
		requires: [
			"icon-image"
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"text-pitch-alignment": {
		type: "enum",
		values: {
			map: {
			},
			viewport: {
			},
			auto: {
			}
		},
		"default": "auto",
		requires: [
			"text-field"
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"text-rotation-alignment": {
		type: "enum",
		values: {
			map: {
			},
			viewport: {
			},
			"viewport-glyph": {
			},
			auto: {
			}
		},
		"default": "auto",
		requires: [
			"text-field"
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"text-field": {
		type: "formatted",
		"default": "",
		tokens: true,
		expression: {
			interpolated: false,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	"text-font": {
		type: "array",
		value: "string",
		"default": [
			"Open Sans Regular",
			"Arial Unicode MS Regular"
		],
		requires: [
			"text-field"
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	"text-size": {
		type: "number",
		"default": 16,
		minimum: 0,
		units: "pixels",
		requires: [
			"text-field"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	"text-max-width": {
		type: "number",
		"default": 10,
		minimum: 0,
		units: "ems",
		requires: [
			"text-field"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	"text-line-height": {
		type: "number",
		"default": 1.2,
		units: "ems",
		requires: [
			"text-field"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"text-letter-spacing": {
		type: "number",
		"default": 0,
		units: "ems",
		requires: [
			"text-field"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	"text-justify": {
		type: "enum",
		values: {
			auto: {
			},
			left: {
			},
			center: {
			},
			right: {
			}
		},
		"default": "center",
		requires: [
			"text-field"
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	"text-radial-offset": {
		type: "number",
		units: "ems",
		"default": 0,
		requires: [
			"text-field"
		],
		"property-type": "data-driven",
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature"
			]
		}
	},
	"text-variable-anchor": {
		type: "array",
		value: "enum",
		values: {
			center: {
			},
			left: {
			},
			right: {
			},
			top: {
			},
			bottom: {
			},
			"top-left": {
			},
			"top-right": {
			},
			"bottom-left": {
			},
			"bottom-right": {
			}
		},
		requires: [
			"text-field",
			{
				"symbol-placement": [
					"point"
				]
			}
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"text-variable-anchor-offset": {
		type: "variableAnchorOffsetCollection",
		requires: [
			"text-field",
			{
				"symbol-placement": [
					"point"
				]
			}
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	"text-anchor": {
		type: "enum",
		values: {
			center: {
			},
			left: {
			},
			right: {
			},
			top: {
			},
			bottom: {
			},
			"top-left": {
			},
			"top-right": {
			},
			"bottom-left": {
			},
			"bottom-right": {
			}
		},
		"default": "center",
		requires: [
			"text-field",
			{
				"!": "text-variable-anchor"
			}
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	"text-max-angle": {
		type: "number",
		"default": 45,
		units: "degrees",
		requires: [
			"text-field",
			{
				"symbol-placement": [
					"line",
					"line-center"
				]
			}
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"text-writing-mode": {
		type: "array",
		value: "enum",
		values: {
			horizontal: {
			},
			vertical: {
			}
		},
		requires: [
			"text-field",
			{
				"symbol-placement": [
					"point"
				]
			}
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"text-rotate": {
		type: "number",
		"default": 0,
		period: 360,
		units: "degrees",
		requires: [
			"text-field"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	"text-padding": {
		type: "number",
		"default": 2,
		minimum: 0,
		units: "pixels",
		requires: [
			"text-field"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"text-keep-upright": {
		type: "boolean",
		"default": true,
		requires: [
			"text-field",
			{
				"text-rotation-alignment": "map"
			},
			{
				"symbol-placement": [
					"line",
					"line-center"
				]
			}
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"text-transform": {
		type: "enum",
		values: {
			none: {
			},
			uppercase: {
			},
			lowercase: {
			}
		},
		"default": "none",
		requires: [
			"text-field"
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	"text-offset": {
		type: "array",
		value: "number",
		units: "ems",
		length: 2,
		"default": [
			0,
			0
		],
		requires: [
			"text-field",
			{
				"!": "text-radial-offset"
			}
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "data-driven"
	},
	"text-allow-overlap": {
		type: "boolean",
		"default": false,
		requires: [
			"text-field",
			{
				"!": "text-overlap"
			}
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"text-overlap": {
		type: "enum",
		values: {
			never: {
			},
			always: {
			},
			cooperative: {
			}
		},
		requires: [
			"text-field"
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"text-ignore-placement": {
		type: "boolean",
		"default": false,
		requires: [
			"text-field"
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"text-optional": {
		type: "boolean",
		"default": false,
		requires: [
			"text-field",
			"icon-image"
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	visibility: {
		type: "enum",
		values: {
			visible: {
			},
			none: {
			}
		},
		"default": "visible",
		"property-type": "constant"
	}
};
var layout_raster = {
	visibility: {
		type: "enum",
		values: {
			visible: {
			},
			none: {
			}
		},
		"default": "visible",
		"property-type": "constant"
	}
};
var layout_hillshade = {
	visibility: {
		type: "enum",
		values: {
			visible: {
			},
			none: {
			}
		},
		"default": "visible",
		"property-type": "constant"
	}
};
var filter = {
	type: "array",
	value: "*"
};
var filter_operator = {
	type: "enum",
	values: {
		"==": {
		},
		"!=": {
		},
		">": {
		},
		">=": {
		},
		"<": {
		},
		"<=": {
		},
		"in": {
		},
		"!in": {
		},
		all: {
		},
		any: {
		},
		none: {
		},
		has: {
		},
		"!has": {
		}
	}
};
var geometry_type = {
	type: "enum",
	values: {
		Point: {
		},
		LineString: {
		},
		Polygon: {
		}
	}
};
var function_stop = {
	type: "array",
	minimum: 0,
	maximum: 24,
	value: [
		"number",
		"color"
	],
	length: 2
};
var expression = {
	type: "array",
	value: "*",
	minimum: 1
};
var light = {
	anchor: {
		type: "enum",
		"default": "viewport",
		values: {
			map: {
			},
			viewport: {
			}
		},
		"property-type": "data-constant",
		transition: false,
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		}
	},
	position: {
		type: "array",
		"default": [
			1.15,
			210,
			30
		],
		length: 3,
		value: "number",
		"property-type": "data-constant",
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		}
	},
	color: {
		type: "color",
		"property-type": "data-constant",
		"default": "#ffffff",
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		transition: true
	},
	intensity: {
		type: "number",
		"property-type": "data-constant",
		"default": 0.5,
		minimum: 0,
		maximum: 1,
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		transition: true
	}
};
var sky = {
	"sky-color": {
		type: "color",
		"property-type": "data-constant",
		"default": "#88C6FC",
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		transition: true
	},
	"horizon-color": {
		type: "color",
		"property-type": "data-constant",
		"default": "#ffffff",
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		transition: true
	},
	"fog-color": {
		type: "color",
		"property-type": "data-constant",
		"default": "#ffffff",
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		transition: true
	},
	"fog-ground-blend": {
		type: "number",
		"property-type": "data-constant",
		"default": 0.5,
		minimum: 0,
		maximum: 1,
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		transition: true
	},
	"horizon-fog-blend": {
		type: "number",
		"property-type": "data-constant",
		"default": 0.8,
		minimum: 0,
		maximum: 1,
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		transition: true
	},
	"sky-horizon-blend": {
		type: "number",
		"property-type": "data-constant",
		"default": 0.8,
		minimum: 0,
		maximum: 1,
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		transition: true
	},
	"atmosphere-blend": {
		type: "number",
		"property-type": "data-constant",
		"default": 0.8,
		minimum: 0,
		maximum: 1,
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		transition: true
	}
};
var terrain = {
	source: {
		type: "string",
		required: true
	},
	exaggeration: {
		type: "number",
		minimum: 0,
		"default": 1
	}
};
var projection = {
	type: {
		type: "projectionDefinition",
		"default": "mercator",
		"property-type": "data-constant",
		transition: false,
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		}
	}
};
var paint = [
	"paint_fill",
	"paint_line",
	"paint_circle",
	"paint_heatmap",
	"paint_fill-extrusion",
	"paint_symbol",
	"paint_raster",
	"paint_hillshade",
	"paint_background"
];
var paint_fill = {
	"fill-antialias": {
		type: "boolean",
		"default": true,
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"fill-opacity": {
		type: "number",
		"default": 1,
		minimum: 0,
		maximum: 1,
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"fill-color": {
		type: "color",
		"default": "#000000",
		transition: true,
		requires: [
			{
				"!": "fill-pattern"
			}
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"fill-outline-color": {
		type: "color",
		transition: true,
		requires: [
			{
				"!": "fill-pattern"
			},
			{
				"fill-antialias": true
			}
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"fill-translate": {
		type: "array",
		value: "number",
		length: 2,
		"default": [
			0,
			0
		],
		transition: true,
		units: "pixels",
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"fill-translate-anchor": {
		type: "enum",
		values: {
			map: {
			},
			viewport: {
			}
		},
		"default": "map",
		requires: [
			"fill-translate"
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"fill-pattern": {
		type: "resolvedImage",
		transition: true,
		expression: {
			interpolated: false,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "cross-faded-data-driven"
	}
};
var paint_line = {
	"line-opacity": {
		type: "number",
		"default": 1,
		minimum: 0,
		maximum: 1,
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"line-color": {
		type: "color",
		"default": "#000000",
		transition: true,
		requires: [
			{
				"!": "line-pattern"
			}
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"line-translate": {
		type: "array",
		value: "number",
		length: 2,
		"default": [
			0,
			0
		],
		transition: true,
		units: "pixels",
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"line-translate-anchor": {
		type: "enum",
		values: {
			map: {
			},
			viewport: {
			}
		},
		"default": "map",
		requires: [
			"line-translate"
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"line-width": {
		type: "number",
		"default": 1,
		minimum: 0,
		transition: true,
		units: "pixels",
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"line-gap-width": {
		type: "number",
		"default": 0,
		minimum: 0,
		transition: true,
		units: "pixels",
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"line-offset": {
		type: "number",
		"default": 0,
		transition: true,
		units: "pixels",
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"line-blur": {
		type: "number",
		"default": 0,
		minimum: 0,
		transition: true,
		units: "pixels",
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"line-dasharray": {
		type: "array",
		value: "number",
		minimum: 0,
		transition: true,
		units: "line widths",
		requires: [
			{
				"!": "line-pattern"
			}
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "cross-faded"
	},
	"line-pattern": {
		type: "resolvedImage",
		transition: true,
		expression: {
			interpolated: false,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "cross-faded-data-driven"
	},
	"line-gradient": {
		type: "color",
		transition: false,
		requires: [
			{
				"!": "line-dasharray"
			},
			{
				"!": "line-pattern"
			},
			{
				source: "geojson",
				has: {
					lineMetrics: true
				}
			}
		],
		expression: {
			interpolated: true,
			parameters: [
				"line-progress"
			]
		},
		"property-type": "color-ramp"
	}
};
var paint_circle = {
	"circle-radius": {
		type: "number",
		"default": 5,
		minimum: 0,
		transition: true,
		units: "pixels",
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"circle-color": {
		type: "color",
		"default": "#000000",
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"circle-blur": {
		type: "number",
		"default": 0,
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"circle-opacity": {
		type: "number",
		"default": 1,
		minimum: 0,
		maximum: 1,
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"circle-translate": {
		type: "array",
		value: "number",
		length: 2,
		"default": [
			0,
			0
		],
		transition: true,
		units: "pixels",
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"circle-translate-anchor": {
		type: "enum",
		values: {
			map: {
			},
			viewport: {
			}
		},
		"default": "map",
		requires: [
			"circle-translate"
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"circle-pitch-scale": {
		type: "enum",
		values: {
			map: {
			},
			viewport: {
			}
		},
		"default": "map",
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"circle-pitch-alignment": {
		type: "enum",
		values: {
			map: {
			},
			viewport: {
			}
		},
		"default": "viewport",
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"circle-stroke-width": {
		type: "number",
		"default": 0,
		minimum: 0,
		transition: true,
		units: "pixels",
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"circle-stroke-color": {
		type: "color",
		"default": "#000000",
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"circle-stroke-opacity": {
		type: "number",
		"default": 1,
		minimum: 0,
		maximum: 1,
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	}
};
var paint_heatmap = {
	"heatmap-radius": {
		type: "number",
		"default": 30,
		minimum: 1,
		transition: true,
		units: "pixels",
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"heatmap-weight": {
		type: "number",
		"default": 1,
		minimum: 0,
		transition: false,
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"heatmap-intensity": {
		type: "number",
		"default": 1,
		minimum: 0,
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"heatmap-color": {
		type: "color",
		"default": [
			"interpolate",
			[
				"linear"
			],
			[
				"heatmap-density"
			],
			0,
			"rgba(0, 0, 255, 0)",
			0.1,
			"royalblue",
			0.3,
			"cyan",
			0.5,
			"lime",
			0.7,
			"yellow",
			1,
			"red"
		],
		transition: false,
		expression: {
			interpolated: true,
			parameters: [
				"heatmap-density"
			]
		},
		"property-type": "color-ramp"
	},
	"heatmap-opacity": {
		type: "number",
		"default": 1,
		minimum: 0,
		maximum: 1,
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	}
};
var paint_symbol = {
	"icon-opacity": {
		type: "number",
		"default": 1,
		minimum: 0,
		maximum: 1,
		transition: true,
		requires: [
			"icon-image"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"icon-color": {
		type: "color",
		"default": "#000000",
		transition: true,
		requires: [
			"icon-image"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"icon-halo-color": {
		type: "color",
		"default": "rgba(0, 0, 0, 0)",
		transition: true,
		requires: [
			"icon-image"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"icon-halo-width": {
		type: "number",
		"default": 0,
		minimum: 0,
		transition: true,
		units: "pixels",
		requires: [
			"icon-image"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"icon-halo-blur": {
		type: "number",
		"default": 0,
		minimum: 0,
		transition: true,
		units: "pixels",
		requires: [
			"icon-image"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"icon-translate": {
		type: "array",
		value: "number",
		length: 2,
		"default": [
			0,
			0
		],
		transition: true,
		units: "pixels",
		requires: [
			"icon-image"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"icon-translate-anchor": {
		type: "enum",
		values: {
			map: {
			},
			viewport: {
			}
		},
		"default": "map",
		requires: [
			"icon-image",
			"icon-translate"
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"text-opacity": {
		type: "number",
		"default": 1,
		minimum: 0,
		maximum: 1,
		transition: true,
		requires: [
			"text-field"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"text-color": {
		type: "color",
		"default": "#000000",
		transition: true,
		overridable: true,
		requires: [
			"text-field"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"text-halo-color": {
		type: "color",
		"default": "rgba(0, 0, 0, 0)",
		transition: true,
		requires: [
			"text-field"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"text-halo-width": {
		type: "number",
		"default": 0,
		minimum: 0,
		transition: true,
		units: "pixels",
		requires: [
			"text-field"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"text-halo-blur": {
		type: "number",
		"default": 0,
		minimum: 0,
		transition: true,
		units: "pixels",
		requires: [
			"text-field"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"text-translate": {
		type: "array",
		value: "number",
		length: 2,
		"default": [
			0,
			0
		],
		transition: true,
		units: "pixels",
		requires: [
			"text-field"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"text-translate-anchor": {
		type: "enum",
		values: {
			map: {
			},
			viewport: {
			}
		},
		"default": "map",
		requires: [
			"text-field",
			"text-translate"
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	}
};
var paint_raster = {
	"raster-opacity": {
		type: "number",
		"default": 1,
		minimum: 0,
		maximum: 1,
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"raster-hue-rotate": {
		type: "number",
		"default": 0,
		period: 360,
		transition: true,
		units: "degrees",
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"raster-brightness-min": {
		type: "number",
		"default": 0,
		minimum: 0,
		maximum: 1,
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"raster-brightness-max": {
		type: "number",
		"default": 1,
		minimum: 0,
		maximum: 1,
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"raster-saturation": {
		type: "number",
		"default": 0,
		minimum: -1,
		maximum: 1,
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"raster-contrast": {
		type: "number",
		"default": 0,
		minimum: -1,
		maximum: 1,
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"raster-resampling": {
		type: "enum",
		values: {
			linear: {
			},
			nearest: {
			}
		},
		"default": "linear",
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"raster-fade-duration": {
		type: "number",
		"default": 300,
		minimum: 0,
		transition: false,
		units: "milliseconds",
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	}
};
var paint_hillshade = {
	"hillshade-illumination-direction": {
		type: "number",
		"default": 335,
		minimum: 0,
		maximum: 359,
		transition: false,
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"hillshade-illumination-anchor": {
		type: "enum",
		values: {
			map: {
			},
			viewport: {
			}
		},
		"default": "viewport",
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"hillshade-exaggeration": {
		type: "number",
		"default": 0.5,
		minimum: 0,
		maximum: 1,
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"hillshade-shadow-color": {
		type: "color",
		"default": "#000000",
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"hillshade-highlight-color": {
		type: "color",
		"default": "#FFFFFF",
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"hillshade-accent-color": {
		type: "color",
		"default": "#000000",
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	}
};
var paint_background = {
	"background-color": {
		type: "color",
		"default": "#000000",
		transition: true,
		requires: [
			{
				"!": "background-pattern"
			}
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"background-pattern": {
		type: "resolvedImage",
		transition: true,
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "cross-faded"
	},
	"background-opacity": {
		type: "number",
		"default": 1,
		minimum: 0,
		maximum: 1,
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	}
};
var transition = {
	duration: {
		type: "number",
		"default": 300,
		minimum: 0,
		units: "milliseconds"
	},
	delay: {
		type: "number",
		"default": 0,
		minimum: 0,
		units: "milliseconds"
	}
};
var promoteId = {
	"*": {
		type: "string"
	}
};
var v8Spec = {
	$version: $version,
	$root: $root,
	sources: sources,
	source: source,
	source_vector: source_vector,
	source_raster: source_raster,
	source_raster_dem: source_raster_dem,
	source_geojson: source_geojson,
	source_video: source_video,
	source_image: source_image,
	layer: layer,
	layout: layout,
	layout_background: layout_background,
	layout_fill: layout_fill,
	layout_circle: layout_circle,
	layout_heatmap: layout_heatmap,
	"layout_fill-extrusion": {
	visibility: {
		type: "enum",
		values: {
			visible: {
			},
			none: {
			}
		},
		"default": "visible",
		"property-type": "constant"
	}
},
	layout_line: layout_line,
	layout_symbol: layout_symbol,
	layout_raster: layout_raster,
	layout_hillshade: layout_hillshade,
	filter: filter,
	filter_operator: filter_operator,
	geometry_type: geometry_type,
	"function": {
	expression: {
		type: "expression"
	},
	stops: {
		type: "array",
		value: "function_stop"
	},
	base: {
		type: "number",
		"default": 1,
		minimum: 0
	},
	property: {
		type: "string",
		"default": "$zoom"
	},
	type: {
		type: "enum",
		values: {
			identity: {
			},
			exponential: {
			},
			interval: {
			},
			categorical: {
			}
		},
		"default": "exponential"
	},
	colorSpace: {
		type: "enum",
		values: {
			rgb: {
			},
			lab: {
			},
			hcl: {
			}
		},
		"default": "rgb"
	},
	"default": {
		type: "*",
		required: false
	}
},
	function_stop: function_stop,
	expression: expression,
	light: light,
	sky: sky,
	terrain: terrain,
	projection: projection,
	paint: paint,
	paint_fill: paint_fill,
	"paint_fill-extrusion": {
	"fill-extrusion-opacity": {
		type: "number",
		"default": 1,
		minimum: 0,
		maximum: 1,
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"fill-extrusion-color": {
		type: "color",
		"default": "#000000",
		transition: true,
		requires: [
			{
				"!": "fill-extrusion-pattern"
			}
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"fill-extrusion-translate": {
		type: "array",
		value: "number",
		length: 2,
		"default": [
			0,
			0
		],
		transition: true,
		units: "pixels",
		expression: {
			interpolated: true,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"fill-extrusion-translate-anchor": {
		type: "enum",
		values: {
			map: {
			},
			viewport: {
			}
		},
		"default": "map",
		requires: [
			"fill-extrusion-translate"
		],
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	},
	"fill-extrusion-pattern": {
		type: "resolvedImage",
		transition: true,
		expression: {
			interpolated: false,
			parameters: [
				"zoom",
				"feature"
			]
		},
		"property-type": "cross-faded-data-driven"
	},
	"fill-extrusion-height": {
		type: "number",
		"default": 0,
		minimum: 0,
		units: "meters",
		transition: true,
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"fill-extrusion-base": {
		type: "number",
		"default": 0,
		minimum: 0,
		units: "meters",
		transition: true,
		requires: [
			"fill-extrusion-height"
		],
		expression: {
			interpolated: true,
			parameters: [
				"zoom",
				"feature",
				"feature-state"
			]
		},
		"property-type": "data-driven"
	},
	"fill-extrusion-vertical-gradient": {
		type: "boolean",
		"default": true,
		transition: false,
		expression: {
			interpolated: false,
			parameters: [
				"zoom"
			]
		},
		"property-type": "data-constant"
	}
},
	paint_line: paint_line,
	paint_circle: paint_circle,
	paint_heatmap: paint_heatmap,
	paint_symbol: paint_symbol,
	paint_raster: paint_raster,
	paint_hillshade: paint_hillshade,
	paint_background: paint_background,
	transition: transition,
	"property-type": {
	"data-driven": {
		type: "property-type"
	},
	"cross-faded": {
		type: "property-type"
	},
	"cross-faded-data-driven": {
		type: "property-type"
	},
	"color-ramp": {
		type: "property-type"
	},
	"data-constant": {
		type: "property-type"
	},
	constant: {
		type: "property-type"
	}
},
	promoteId: promoteId
};

function validateGlyphsUrl(options) {
    const value = options.value;
    const key = options.key;
    const errors = validateString(options);
    if (errors.length)
        return errors;
    if (value.indexOf('{fontstack}') === -1) {
        errors.push(new ValidationError(key, value, '"glyphs" url must include a "{fontstack}" token'));
    }
    if (value.indexOf('{range}') === -1) {
        errors.push(new ValidationError(key, value, '"glyphs" url must include a "{range}" token'));
    }
    return errors;
}

/**
 * Validate a MapLibre style against the style specification.
 * Use this when running in the browser.
 *
 * @param style - The style to be validated.
 * @param styleSpec - The style specification to validate against.
 * If omitted, the latest style spec is used.
 * @returns an array of errors, or an empty array if no errors are found.
 * @example
 *   const validate = require('@maplibre/maplibre-gl-style-spec/').validateStyleMin;
 *   const errors = validate(style);
 */
function validateStyleMin(style, styleSpec = v8Spec) {
    let errors = [];
    errors = errors.concat(validate({
        key: '',
        value: style,
        valueSpec: styleSpec.$root,
        styleSpec,
        style,
        validateSpec: validate,
        objectElementValidators: {
            glyphs: validateGlyphsUrl,
            '*'() {
                return [];
            }
        }
    }));
    if (style['constants']) {
        errors = errors.concat(validateConstants({
            key: 'constants',
            value: style['constants'],
            style,
            styleSpec,
            validateSpec: validate,
        }));
    }
    return sortErrors(errors);
}
validateStyleMin.source = wrapCleanErrors(injectValidateSpec(validateSource));
validateStyleMin.sprite = wrapCleanErrors(injectValidateSpec(validateSprite));
validateStyleMin.glyphs = wrapCleanErrors(injectValidateSpec(validateGlyphsUrl));
validateStyleMin.light = wrapCleanErrors(injectValidateSpec(validateLight));
validateStyleMin.sky = wrapCleanErrors(injectValidateSpec(validateSky));
validateStyleMin.terrain = wrapCleanErrors(injectValidateSpec(validateTerrain));
validateStyleMin.layer = wrapCleanErrors(injectValidateSpec(validateLayer));
validateStyleMin.filter = wrapCleanErrors(injectValidateSpec(validateFilter));
validateStyleMin.paintProperty = wrapCleanErrors(injectValidateSpec(validatePaintProperty));
validateStyleMin.layoutProperty = wrapCleanErrors(injectValidateSpec(validateLayoutProperty));
function injectValidateSpec(validator) {
    return function (options) {
        return validator({
            ...options,
            validateSpec: validate,
        });
    };
}
function sortErrors(errors) {
    return [].concat(errors).sort((a, b) => {
        return a.line - b.line;
    });
}
function wrapCleanErrors(inner) {
    return function (...args) {
        return sortErrors(inner.apply(this, args));
    };
}

// Note: Do not inherit from Error. It breaks when transpiling to ES5.
class ParsingError {
    constructor(error) {
        this.error = error;
        this.message = error.message;
        const match = error.message.match(/line (\d+)/);
        this.line = match ? parseInt(match[1], 10) : 0;
    }
}

const v8 = v8Spec;

function commonjsRequire(path) {
	throw new Error('Could not dynamically require "' + path + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
}

var jsonlint$1 = {};

/* parser generated by jison 0.4.15 */

var hasRequiredJsonlint;

function requireJsonlint () {
	if (hasRequiredJsonlint) return jsonlint$1;
	hasRequiredJsonlint = 1;
	(function (exports) {
		/*
		  Returns a Parser object of the following structure:

		  Parser: {
		    yy: {}
		  }

		  Parser.prototype: {
		    yy: {},
		    trace: function(),
		    symbols_: {associative list: name ==> number},
		    terminals_: {associative list: number ==> name},
		    productions_: [...],
		    performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),
		    table: [...],
		    defaultActions: {...},
		    parseError: function(str, hash),
		    parse: function(input),

		    lexer: {
		        EOF: 1,
		        parseError: function(str, hash),
		        setInput: function(input),
		        input: function(),
		        unput: function(str),
		        more: function(),
		        less: function(n),
		        pastInput: function(),
		        upcomingInput: function(),
		        showPosition: function(),
		        test_match: function(regex_match_array, rule_index),
		        next: function(),
		        lex: function(),
		        begin: function(condition),
		        popState: function(),
		        _currentRules: function(),
		        topState: function(),
		        pushState: function(condition),

		        options: {
		            ranges: boolean           (optional: true ==> token location info will include a .range[] member)
		            flex: boolean             (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)
		            backtrack_lexer: boolean  (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)
		        },

		        performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),
		        rules: [...],
		        conditions: {associative list: name ==> set},
		    }
		  }


		  token location info (@$, _$, etc.): {
		    first_line: n,
		    last_line: n,
		    first_column: n,
		    last_column: n,
		    range: [start_number, end_number]       (where the numbers are indexes into the input string, regular zero-based)
		  }


		  the parseError function receives a 'hash' object with these members for lexer and parser errors: {
		    text:        (matched text)
		    token:       (the produced terminal token, if any)
		    line:        (yylineno)
		  }
		  while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {
		    loc:         (yylloc)
		    expected:    (string describing the set of expected tokens)
		    recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
		  }
		*/
		var parser = (function(){
		var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,12],$V1=[1,13],$V2=[1,9],$V3=[1,10],$V4=[1,11],$V5=[1,14],$V6=[1,15],$V7=[14,18,22,24],$V8=[18,22],$V9=[22,24];
		var parser = {trace: function trace() { },
		yy: {},
		symbols_: {"error":2,"JSONString":3,"STRING":4,"JSONNumber":5,"NUMBER":6,"JSONNullLiteral":7,"NULL":8,"JSONBooleanLiteral":9,"TRUE":10,"FALSE":11,"JSONText":12,"JSONValue":13,"EOF":14,"JSONObject":15,"JSONArray":16,"{":17,"}":18,"JSONMemberList":19,"JSONMember":20,":":21,",":22,"[":23,"]":24,"JSONElementList":25,"$accept":0,"$end":1},
		terminals_: {2:"error",4:"STRING",6:"NUMBER",8:"NULL",10:"TRUE",11:"FALSE",14:"EOF",17:"{",18:"}",21:":",22:",",23:"[",24:"]"},
		productions_: [0,[3,1],[5,1],[7,1],[9,1],[9,1],[12,2],[13,1],[13,1],[13,1],[13,1],[13,1],[13,1],[15,2],[15,3],[20,3],[19,1],[19,3],[16,2],[16,3],[25,1],[25,3]],
		performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
		/* this == yyval */

		var $0 = $$.length - 1;
		switch (yystate) {
		case 1:
		 // replace escaped characters with actual character
		          this.$ = new String(yytext.replace(/\\(\\|")/g, "$"+"1")
		                     .replace(/\\n/g,'\n')
		                     .replace(/\\r/g,'\r')
		                     .replace(/\\t/g,'\t')
		                     .replace(/\\v/g,'\v')
		                     .replace(/\\f/g,'\f')
		                     .replace(/\\b/g,'\b'));
		          this.$.__line__ =  this._$.first_line;
		        
		break;
		case 2:

		            this.$ = new Number(yytext);
		            this.$.__line__ =  this._$.first_line;
		        
		break;
		case 3:

		            this.$ = null;
		        
		break;
		case 4:

		            this.$ = new Boolean(true);
		            this.$.__line__ = this._$.first_line;
		        
		break;
		case 5:

		            this.$ = new Boolean(false);
		            this.$.__line__ = this._$.first_line;
		        
		break;
		case 6:
		return this.$ = $$[$0-1];
		case 13:
		this.$ = {}; Object.defineProperty(this.$, '__line__', {
		            value: this._$.first_line,
		            enumerable: false
		        });
		break;
		case 14: case 19:
		this.$ = $$[$0-1]; Object.defineProperty(this.$, '__line__', {
		            value: this._$.first_line,
		            enumerable: false
		        });
		break;
		case 15:
		this.$ = [$$[$0-2], $$[$0]];
		break;
		case 16:
		this.$ = {}; this.$[$$[$0][0]] = $$[$0][1];
		break;
		case 17:
		this.$ = $$[$0-2]; $$[$0-2][$$[$0][0]] = $$[$0][1];
		break;
		case 18:
		this.$ = []; Object.defineProperty(this.$, '__line__', {
		            value: this._$.first_line,
		            enumerable: false
		        });
		break;
		case 20:
		this.$ = [$$[$0]];
		break;
		case 21:
		this.$ = $$[$0-2]; $$[$0-2].push($$[$0]);
		break;
		}
		},
		table: [{3:5,4:$V0,5:6,6:$V1,7:3,8:$V2,9:4,10:$V3,11:$V4,12:1,13:2,15:7,16:8,17:$V5,23:$V6},{1:[3]},{14:[1,16]},o($V7,[2,7]),o($V7,[2,8]),o($V7,[2,9]),o($V7,[2,10]),o($V7,[2,11]),o($V7,[2,12]),o($V7,[2,3]),o($V7,[2,4]),o($V7,[2,5]),o([14,18,21,22,24],[2,1]),o($V7,[2,2]),{3:20,4:$V0,18:[1,17],19:18,20:19},{3:5,4:$V0,5:6,6:$V1,7:3,8:$V2,9:4,10:$V3,11:$V4,13:23,15:7,16:8,17:$V5,23:$V6,24:[1,21],25:22},{1:[2,6]},o($V7,[2,13]),{18:[1,24],22:[1,25]},o($V8,[2,16]),{21:[1,26]},o($V7,[2,18]),{22:[1,28],24:[1,27]},o($V9,[2,20]),o($V7,[2,14]),{3:20,4:$V0,20:29},{3:5,4:$V0,5:6,6:$V1,7:3,8:$V2,9:4,10:$V3,11:$V4,13:30,15:7,16:8,17:$V5,23:$V6},o($V7,[2,19]),{3:5,4:$V0,5:6,6:$V1,7:3,8:$V2,9:4,10:$V3,11:$V4,13:31,15:7,16:8,17:$V5,23:$V6},o($V8,[2,17]),o($V8,[2,15]),o($V9,[2,21])],
		defaultActions: {16:[2,6]},
		parseError: function parseError(str, hash) {
		    if (hash.recoverable) {
		        this.trace(str);
		    } else {
		        throw new Error(str);
		    }
		},
		parse: function parse(input) {
		    var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, TERROR = 2, EOF = 1;
		    var args = lstack.slice.call(arguments, 1);
		    var lexer = Object.create(this.lexer);
		    var sharedState = { yy: {} };
		    for (var k in this.yy) {
		        if (Object.prototype.hasOwnProperty.call(this.yy, k)) {
		            sharedState.yy[k] = this.yy[k];
		        }
		    }
		    lexer.setInput(input, sharedState.yy);
		    sharedState.yy.lexer = lexer;
		    sharedState.yy.parser = this;
		    if (typeof lexer.yylloc == 'undefined') {
		        lexer.yylloc = {};
		    }
		    var yyloc = lexer.yylloc;
		    lstack.push(yyloc);
		    var ranges = lexer.options && lexer.options.ranges;
		    if (typeof sharedState.yy.parseError === 'function') {
		        this.parseError = sharedState.yy.parseError;
		    } else {
		        this.parseError = Object.getPrototypeOf(this).parseError;
		    }
		    
		        function lex() {
		            var token;
		            token = lexer.lex() || EOF;
		            if (typeof token !== 'number') {
		                token = self.symbols_[token] || token;
		            }
		            return token;
		        }
		    var symbol, state, action, r, yyval = {}, p, len, newState, expected;
		    while (true) {
		        state = stack[stack.length - 1];
		        if (this.defaultActions[state]) {
		            action = this.defaultActions[state];
		        } else {
		            if (symbol === null || typeof symbol == 'undefined') {
		                symbol = lex();
		            }
		            action = table[state] && table[state][symbol];
		        }
		                    if (typeof action === 'undefined' || !action.length || !action[0]) {
		                var errStr = '';
		                expected = [];
		                for (p in table[state]) {
		                    if (this.terminals_[p] && p > TERROR) {
		                        expected.push('\'' + this.terminals_[p] + '\'');
		                    }
		                }
		                if (lexer.showPosition) {
		                    errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\'';
		                } else {
		                    errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\'');
		                }
		                this.parseError(errStr, {
		                    text: lexer.match,
		                    token: this.terminals_[symbol] || symbol,
		                    line: lexer.yylineno,
		                    loc: yyloc,
		                    expected: expected
		                });
		            }
		        if (action[0] instanceof Array && action.length > 1) {
		            throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol);
		        }
		        switch (action[0]) {
		        case 1:
		            stack.push(symbol);
		            vstack.push(lexer.yytext);
		            lstack.push(lexer.yylloc);
		            stack.push(action[1]);
		            symbol = null;
		            {
		                yyleng = lexer.yyleng;
		                yytext = lexer.yytext;
		                yylineno = lexer.yylineno;
		                yyloc = lexer.yylloc;
		            }
		            break;
		        case 2:
		            len = this.productions_[action[1]][1];
		            yyval.$ = vstack[vstack.length - len];
		            yyval._$ = {
		                first_line: lstack[lstack.length - (len || 1)].first_line,
		                last_line: lstack[lstack.length - 1].last_line,
		                first_column: lstack[lstack.length - (len || 1)].first_column,
		                last_column: lstack[lstack.length - 1].last_column
		            };
		            if (ranges) {
		                yyval._$.range = [
		                    lstack[lstack.length - (len || 1)].range[0],
		                    lstack[lstack.length - 1].range[1]
		                ];
		            }
		            r = this.performAction.apply(yyval, [
		                yytext,
		                yyleng,
		                yylineno,
		                sharedState.yy,
		                action[1],
		                vstack,
		                lstack
		            ].concat(args));
		            if (typeof r !== 'undefined') {
		                return r;
		            }
		            if (len) {
		                stack = stack.slice(0, -1 * len * 2);
		                vstack = vstack.slice(0, -1 * len);
		                lstack = lstack.slice(0, -1 * len);
		            }
		            stack.push(this.productions_[action[1]][0]);
		            vstack.push(yyval.$);
		            lstack.push(yyval._$);
		            newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
		            stack.push(newState);
		            break;
		        case 3:
		            return true;
		        }
		    }
		    return true;
		}};
		/* generated by jison-lex 0.3.4 */
		var lexer = (function(){
		var lexer = ({

		EOF:1,

		parseError:function parseError(str, hash) {
		        if (this.yy.parser) {
		            this.yy.parser.parseError(str, hash);
		        } else {
		            throw new Error(str);
		        }
		    },

		// resets the lexer, sets new input
		setInput:function (input, yy) {
		        this.yy = yy || this.yy || {};
		        this._input = input;
		        this._more = this._backtrack = this.done = false;
		        this.yylineno = this.yyleng = 0;
		        this.yytext = this.matched = this.match = '';
		        this.conditionStack = ['INITIAL'];
		        this.yylloc = {
		            first_line: 1,
		            first_column: 0,
		            last_line: 1,
		            last_column: 0
		        };
		        if (this.options.ranges) {
		            this.yylloc.range = [0,0];
		        }
		        this.offset = 0;
		        return this;
		    },

		// consumes and returns one char from the input
		input:function () {
		        var ch = this._input[0];
		        this.yytext += ch;
		        this.yyleng++;
		        this.offset++;
		        this.match += ch;
		        this.matched += ch;
		        var lines = ch.match(/(?:\r\n?|\n).*/g);
		        if (lines) {
		            this.yylineno++;
		            this.yylloc.last_line++;
		        } else {
		            this.yylloc.last_column++;
		        }
		        if (this.options.ranges) {
		            this.yylloc.range[1]++;
		        }

		        this._input = this._input.slice(1);
		        return ch;
		    },

		// unshifts one char (or a string) into the input
		unput:function (ch) {
		        var len = ch.length;
		        var lines = ch.split(/(?:\r\n?|\n)/g);

		        this._input = ch + this._input;
		        this.yytext = this.yytext.substr(0, this.yytext.length - len);
		        //this.yyleng -= len;
		        this.offset -= len;
		        var oldLines = this.match.split(/(?:\r\n?|\n)/g);
		        this.match = this.match.substr(0, this.match.length - 1);
		        this.matched = this.matched.substr(0, this.matched.length - 1);

		        if (lines.length - 1) {
		            this.yylineno -= lines.length - 1;
		        }
		        var r = this.yylloc.range;

		        this.yylloc = {
		            first_line: this.yylloc.first_line,
		            last_line: this.yylineno + 1,
		            first_column: this.yylloc.first_column,
		            last_column: lines ?
		                (lines.length === oldLines.length ? this.yylloc.first_column : 0)
		                 + oldLines[oldLines.length - lines.length].length - lines[0].length :
		              this.yylloc.first_column - len
		        };

		        if (this.options.ranges) {
		            this.yylloc.range = [r[0], r[0] + this.yyleng - len];
		        }
		        this.yyleng = this.yytext.length;
		        return this;
		    },

		// When called from action, caches matched text and appends it on next action
		more:function () {
		        this._more = true;
		        return this;
		    },

		// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
		reject:function () {
		        if (this.options.backtrack_lexer) {
		            this._backtrack = true;
		        } else {
		            return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), {
		                text: "",
		                token: null,
		                line: this.yylineno
		            });

		        }
		        return this;
		    },

		// retain first n characters of the match
		less:function (n) {
		        this.unput(this.match.slice(n));
		    },

		// displays already matched input, i.e. for error messages
		pastInput:function () {
		        var past = this.matched.substr(0, this.matched.length - this.match.length);
		        return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
		    },

		// displays upcoming input, i.e. for error messages
		upcomingInput:function () {
		        var next = this.match;
		        if (next.length < 20) {
		            next += this._input.substr(0, 20-next.length);
		        }
		        return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
		    },

		// displays the character position where the lexing error occurred, i.e. for error messages
		showPosition:function () {
		        var pre = this.pastInput();
		        var c = new Array(pre.length + 1).join("-");
		        return pre + this.upcomingInput() + "\n" + c + "^";
		    },

		// test the lexed token: return FALSE when not a match, otherwise return token
		test_match:function (match, indexed_rule) {
		        var token,
		            lines,
		            backup;

		        if (this.options.backtrack_lexer) {
		            // save context
		            backup = {
		                yylineno: this.yylineno,
		                yylloc: {
		                    first_line: this.yylloc.first_line,
		                    last_line: this.last_line,
		                    first_column: this.yylloc.first_column,
		                    last_column: this.yylloc.last_column
		                },
		                yytext: this.yytext,
		                match: this.match,
		                matches: this.matches,
		                matched: this.matched,
		                yyleng: this.yyleng,
		                offset: this.offset,
		                _more: this._more,
		                _input: this._input,
		                yy: this.yy,
		                conditionStack: this.conditionStack.slice(0),
		                done: this.done
		            };
		            if (this.options.ranges) {
		                backup.yylloc.range = this.yylloc.range.slice(0);
		            }
		        }

		        lines = match[0].match(/(?:\r\n?|\n).*/g);
		        if (lines) {
		            this.yylineno += lines.length;
		        }
		        this.yylloc = {
		            first_line: this.yylloc.last_line,
		            last_line: this.yylineno + 1,
		            first_column: this.yylloc.last_column,
		            last_column: lines ?
		                         lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length :
		                         this.yylloc.last_column + match[0].length
		        };
		        this.yytext += match[0];
		        this.match += match[0];
		        this.matches = match;
		        this.yyleng = this.yytext.length;
		        if (this.options.ranges) {
		            this.yylloc.range = [this.offset, this.offset += this.yyleng];
		        }
		        this._more = false;
		        this._backtrack = false;
		        this._input = this._input.slice(match[0].length);
		        this.matched += match[0];
		        token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]);
		        if (this.done && this._input) {
		            this.done = false;
		        }
		        if (token) {
		            return token;
		        } else if (this._backtrack) {
		            // recover context
		            for (var k in backup) {
		                this[k] = backup[k];
		            }
		            return false; // rule action called reject() implying the next rule should be tested instead.
		        }
		        return false;
		    },

		// return next match in input
		next:function () {
		        if (this.done) {
		            return this.EOF;
		        }
		        if (!this._input) {
		            this.done = true;
		        }

		        var token,
		            match,
		            tempMatch,
		            index;
		        if (!this._more) {
		            this.yytext = '';
		            this.match = '';
		        }
		        var rules = this._currentRules();
		        for (var i = 0; i < rules.length; i++) {
		            tempMatch = this._input.match(this.rules[rules[i]]);
		            if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
		                match = tempMatch;
		                index = i;
		                if (this.options.backtrack_lexer) {
		                    token = this.test_match(tempMatch, rules[i]);
		                    if (token !== false) {
		                        return token;
		                    } else if (this._backtrack) {
		                        match = false;
		                        continue; // rule action called reject() implying a rule MISmatch.
		                    } else {
		                        // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
		                        return false;
		                    }
		                } else if (!this.options.flex) {
		                    break;
		                }
		            }
		        }
		        if (match) {
		            token = this.test_match(match, rules[index]);
		            if (token !== false) {
		                return token;
		            }
		            // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
		            return false;
		        }
		        if (this._input === "") {
		            return this.EOF;
		        } else {
		            return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), {
		                text: "",
		                token: null,
		                line: this.yylineno
		            });
		        }
		    },

		// return next match that has a token
		lex:function lex() {
		        var r = this.next();
		        if (r) {
		            return r;
		        } else {
		            return this.lex();
		        }
		    },

		// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
		begin:function begin(condition) {
		        this.conditionStack.push(condition);
		    },

		// pop the previously active lexer condition state off the condition stack
		popState:function popState() {
		        var n = this.conditionStack.length - 1;
		        if (n > 0) {
		            return this.conditionStack.pop();
		        } else {
		            return this.conditionStack[0];
		        }
		    },

		// produce the lexer rule set which is active for the currently active lexer condition state
		_currentRules:function _currentRules() {
		        if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
		            return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
		        } else {
		            return this.conditions["INITIAL"].rules;
		        }
		    },

		// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
		topState:function topState(n) {
		        n = this.conditionStack.length - 1 - Math.abs(n || 0);
		        if (n >= 0) {
		            return this.conditionStack[n];
		        } else {
		            return "INITIAL";
		        }
		    },

		// alias for begin(condition)
		pushState:function pushState(condition) {
		        this.begin(condition);
		    },

		// return the number of states currently on the stack
		stateStackSize:function stateStackSize() {
		        return this.conditionStack.length;
		    },
		options: {},
		performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
		switch($avoiding_name_collisions) {
		case 0:/* skip whitespace */
		break;
		case 1:return 6
		case 2:yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2); return 4
		case 3:return 17
		case 4:return 18
		case 5:return 23
		case 6:return 24
		case 7:return 22
		case 8:return 21
		case 9:return 10
		case 10:return 11
		case 11:return 8
		case 12:return 14
		case 13:return 'INVALID'
		}
		},
		rules: [/^(?:\s+)/,/^(?:(-?([0-9]|[1-9][0-9]+))(\.[0-9]+)?([eE][-+]?[0-9]+)?\b)/,/^(?:"(?:\\[\\"bfnrt/]|\\u[a-fA-F0-9]{4}|[^\\\0-\x09\x0a-\x1f"])*")/,/^(?:\{)/,/^(?:\})/,/^(?:\[)/,/^(?:\])/,/^(?:,)/,/^(?::)/,/^(?:true\b)/,/^(?:false\b)/,/^(?:null\b)/,/^(?:$)/,/^(?:.)/],
		conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13],"inclusive":true}}
		});
		return lexer;
		})();
		parser.lexer = lexer;
		function Parser () {
		  this.yy = {};
		}
		Parser.prototype = parser;parser.Parser = Parser;
		return new Parser;
		})();


		if (typeof commonjsRequire !== 'undefined' && 'object' !== 'undefined') {
		exports.parser = parser;
		exports.Parser = parser.Parser;
		exports.parse = function () { return parser.parse.apply(parser, arguments); };
		} 
	} (jsonlint$1));
	return jsonlint$1;
}

var jsonlintExports = requireJsonlint();
var jsonlint = /*@__PURE__*/getDefaultExportFromCjs(jsonlintExports);

function readStyle(style) {
    if (style instanceof String || typeof style === 'string' || style instanceof Buffer) {
        try {
            return jsonlint.parse(style.toString());
        }
        catch (e) {
            throw new ParsingError(e);
        }
    }
    return style;
}

/**
 * Validate a MapLibre GL style against the style specification.
 *
 * @param style - The style to be validated. If a `String` or `Buffer` is provided,
 * the returned errors will contain line numbers.
 * @param styleSpec - The style specification to validate against.
 * If omitted, the spec version is inferred from the stylesheet.
 * @returns an array of errors, or an empty array if no errors are found.
 * @example
 *   const validate = require('maplibre-gl-style-spec').validate;
 *   const style = fs.readFileSync('./style.json', 'utf8');
 *   const errors = validate(style);
 */
function validateStyle(style, styleSpec = v8) {
    let s = style;
    try {
        s = readStyle(s);
    }
    catch (e) {
        return [e];
    }
    return validateStyleMin(s, styleSpec);
}

const argv = minimist(process.argv.slice(2), {
    boolean: 'json',
});

if (argv.help || argv.h || (!argv._.length && process.stdin.isTTY)) {
    help();
} else {
    let status = 0;

    if (!argv._.length) {
        argv._.push('/dev/stdin');
    }

    argv._.forEach((file) => {
        const errors = validateStyle(rw.readFileSync(file, 'utf8'));
        if (errors.length) {
            if (argv.json) {
                process.stdout.write(JSON.stringify(errors, null, 2));
            } else {
                errors.forEach((e) => {
                    console.log('%s:%d: %s', file, e.line, e.message);
                });
            }
            status = 1;
        }
    });

    process.exit(status);
}

function help() {
    console.log('usage:');
    console.log('  gl-style-validate file.json');
    console.log('  gl-style-validate < file.json');
    console.log('');
    console.log('options:');
    console.log('--json  output errors as json');
}
//# sourceMappingURL=gl-style-validate.mjs.map
