import { compose, castArray, parseFloat as parseFloat$1, matchPercent } from '@react-pdf/fns';
import matchMedia from 'media-engine';
import hlsToHex from 'hsl-to-hex';
import colorString from 'color-string';
import parse$1 from 'postcss-value-parser/lib/parse.js';
import parseUnit from 'postcss-value-parser/lib/unit.js';

/**
 * Remove nil values from array
 *
 * @param array - Style array
 * @returns Style array without nils
 */
const compact = (array) => array.filter(Boolean);
/**
 * Merges style objects array
 *
 * @param styles - Style array
 * @returns Merged style object
 */
const mergeStyles = (styles) => styles.reduce((acc, style) => {
    const s = Array.isArray(style) ? flatten(style) : style;
    Object.keys(s).forEach((key) => {
        if (s[key] !== null && s[key] !== undefined) {
            acc[key] = s[key];
        }
    });
    return acc;
}, {});
/**
 * Flattens an array of style objects, into one aggregated style object.
 *
 * @param styles - Style or style array
 * @returns Flattened style object
 */
const flatten = compose(mergeStyles, compact, (castArray));

/**
 * Resolves media queries in styles object
 *
 * @param container - Container for which styles are resolved
 * @param style - Style description
 * @returns Resolved style object
 */
const resolveMediaQueries = (container, style) => {
    return Object.keys(style).reduce((acc, key) => {
        if (/@media/.test(key)) {
            return {
                ...acc,
                ...matchMedia({ [key]: style[key] }, container),
            };
        }
        return { ...acc, [key]: style[key] };
    }, {});
};

const isRgb = (value) => /rgba?/g.test(value);
const isHsl = (value) => /hsla?/g.test(value);
/**
 * Transform rgb color to hexa
 *
 * @param value - Styles value
 * @returns Transformed value
 */
const parseRgb = (value) => {
    const rgb = colorString.get.rgb(value);
    return colorString.to.hex(rgb);
};
/**
 * Transform Hsl color to hexa
 *
 * @param value - Styles value
 * @returns Transformed value
 */
const parseHsl = (value) => {
    const hsl = colorString.get.hsl(value).map(Math.round);
    const hex = hlsToHex(...hsl);
    return hex.toUpperCase();
};
/**
 * Transform given color to hexa
 *
 * @param value - Styles value
 * @returns Transformed value
 */
const transformColor = (value) => {
    if (isRgb(value))
        return parseRgb(value);
    if (isHsl(value))
        return parseHsl(value);
    return value;
};

/**
 * Parses scalar value in value and unit pairs
 *
 * @param value - Scalar value
 * @returns Parsed value
 */
const parseValue = (value) => {
    if (typeof value === 'number')
        return { value, unit: undefined };
    const match = /^(-?\d*\.?\d+)(in|mm|cm|pt|vh|vw|px|rem)?$/g.exec(value);
    return match
        ? { value: parseFloat(match[1]), unit: match[2] || 'pt' }
        : { value, unit: undefined };
};
/**
 * Transform given scalar value
 *
 * @param container
 * @param value - Styles value
 * @returns Transformed value
 */
const transformUnit = (container, value) => {
    const scalar = parseValue(value);
    const outputDpi = 72;
    const inputDpi = container.dpi || 72;
    const mmFactor = (1 / 25.4) * outputDpi;
    const cmFactor = (1 / 2.54) * outputDpi;
    if (typeof scalar.value !== 'number')
        return scalar.value;
    switch (scalar.unit) {
        case 'rem':
            return scalar.value * (container.remBase || 18);
        case 'in':
            return scalar.value * outputDpi;
        case 'mm':
            return scalar.value * mmFactor;
        case 'cm':
            return scalar.value * cmFactor;
        case 'vh':
            return scalar.value * (container.height / 100);
        case 'vw':
            return scalar.value * (container.width / 100);
        case 'px':
            return Math.round(scalar.value * (outputDpi / inputDpi));
        default:
            return scalar.value;
    }
};

const processNumberValue = (key, value) => ({
    [key]: parseFloat$1(value),
});
const processUnitValue = (key, value, container) => ({
    [key]: transformUnit(container, value),
});
const processColorValue = (key, value) => {
    const result = { [key]: transformColor(value) };
    return result;
};
const processNoopValue = (key, value) => ({
    [key]: value,
});

const BORDER_SHORTHAND_REGEX = /(-?\d+(\.\d+)?(in|mm|cm|pt|vw|vh|px|rem)?)\s(\S+)\s(.+)/;
const matchBorderShorthand = (value) => value.match(BORDER_SHORTHAND_REGEX) || [];
const resolveBorderShorthand = (key, value, container) => {
    const match = matchBorderShorthand(`${value}`);
    if (match) {
        const widthMatch = match[1] || value;
        const styleMatch = match[4] || value;
        const colorMatch = match[5] || value;
        const style = styleMatch;
        const color = colorMatch ? transformColor(colorMatch) : undefined;
        const width = widthMatch ? transformUnit(container, widthMatch) : undefined;
        if (key.match(/(Top|Right|Bottom|Left)$/)) {
            return {
                [`${key}Color`]: color,
                [`${key}Style`]: style,
                [`${key}Width`]: width,
            };
        }
        if (key.match(/Color$/)) {
            return {
                borderTopColor: color,
                borderRightColor: color,
                borderBottomColor: color,
                borderLeftColor: color,
            };
        }
        if (key.match(/Style$/)) {
            if (typeof style === 'number')
                throw new Error(`Invalid border style: ${style}`);
            return {
                borderTopStyle: style,
                borderRightStyle: style,
                borderBottomStyle: style,
                borderLeftStyle: style,
            };
        }
        if (key.match(/Width$/)) {
            if (typeof width !== 'number')
                throw new Error(`Invalid border width: ${width}`);
            return {
                borderTopWidth: width,
                borderRightWidth: width,
                borderBottomWidth: width,
                borderLeftWidth: width,
            };
        }
        if (key.match(/Radius$/)) {
            const radius = value ? transformUnit(container, value) : undefined;
            if (typeof radius !== 'number')
                throw new Error(`Invalid border radius: ${radius}`);
            return {
                borderTopLeftRadius: radius,
                borderTopRightRadius: radius,
                borderBottomRightRadius: radius,
                borderBottomLeftRadius: radius,
            };
        }
        if (typeof width !== 'number')
            throw new Error(`Invalid border width: ${width}`);
        if (typeof style === 'number')
            throw new Error(`Invalid border style: ${style}`);
        return {
            borderTopColor: color,
            borderTopStyle: style,
            borderTopWidth: width,
            borderRightColor: color,
            borderRightStyle: style,
            borderRightWidth: width,
            borderBottomColor: color,
            borderBottomStyle: style,
            borderBottomWidth: width,
            borderLeftColor: color,
            borderLeftStyle: style,
            borderLeftWidth: width,
        };
    }
    return { [key]: value };
};
const handlers$b = {
    border: (resolveBorderShorthand),
    borderBottom: (resolveBorderShorthand),
    borderBottomColor: (processColorValue),
    borderBottomLeftRadius: (processUnitValue),
    borderBottomRightRadius: (processUnitValue),
    borderBottomStyle: (processNoopValue),
    borderBottomWidth: (processUnitValue),
    borderColor: (resolveBorderShorthand),
    borderLeft: (resolveBorderShorthand),
    borderLeftColor: (processColorValue),
    borderLeftStyle: (processNoopValue),
    borderLeftWidth: (processUnitValue),
    borderRadius: (resolveBorderShorthand),
    borderRight: (resolveBorderShorthand),
    borderRightColor: (processColorValue),
    borderRightStyle: (processNoopValue),
    borderRightWidth: (processUnitValue),
    borderStyle: (resolveBorderShorthand),
    borderTop: (resolveBorderShorthand),
    borderTopColor: (processColorValue),
    borderTopLeftRadius: (processUnitValue),
    borderTopRightRadius: (processUnitValue),
    borderTopStyle: (processNoopValue),
    borderTopWidth: (processUnitValue),
    borderWidth: (resolveBorderShorthand),
};

const handlers$a = {
    backgroundColor: (processColorValue),
    color: (processColorValue),
    opacity: (processNumberValue),
};

const handlers$9 = {
    height: (processUnitValue),
    maxHeight: (processUnitValue),
    maxWidth: (processUnitValue),
    minHeight: (processUnitValue),
    minWidth: (processUnitValue),
    width: (processUnitValue),
};

// https://developer.mozilla.org/en-US/docs/Web/CSS/flex#values
// TODO: change flex defaults to [0, 1, 'auto'] as in spec in next major release
const flexDefaults = [1, 1, 0];
const flexAuto = [1, 1, 'auto'];
const processFlexShorthand = (key, value, container) => {
    let defaults = flexDefaults;
    let matches = [];
    if (value === 'auto') {
        defaults = flexAuto;
    }
    else {
        matches = `${value}`.split(' ');
    }
    const flexGrow = parseFloat$1(matches[0] || defaults[0]);
    const flexShrink = parseFloat$1(matches[1] || defaults[1]);
    const flexBasis = transformUnit(container, matches[2] || defaults[2]);
    return { flexGrow, flexShrink, flexBasis };
};
const handlers$8 = {
    alignContent: (processNoopValue),
    alignItems: (processNoopValue),
    alignSelf: (processNoopValue),
    flex: (processFlexShorthand),
    flexBasis: (processUnitValue),
    flexDirection: (processNoopValue),
    flexFlow: (processNoopValue),
    flexGrow: (processNumberValue),
    flexShrink: (processNumberValue),
    flexWrap: (processNoopValue),
    justifyContent: (processNoopValue),
    justifySelf: (processNoopValue),
};

const processGapShorthand = (key, value, container) => {
    const match = `${value}`.split(' ');
    const rowGap = transformUnit(container, match?.[0] || value);
    const columnGap = transformUnit(container, match?.[1] || value);
    return { rowGap, columnGap };
};
const handlers$7 = {
    gap: (processGapShorthand),
    columnGap: (processUnitValue),
    rowGap: (processUnitValue),
};

const handlers$6 = {
    aspectRatio: (processNumberValue),
    bottom: (processUnitValue),
    display: (processNoopValue),
    left: (processUnitValue),
    position: (processNoopValue),
    right: (processUnitValue),
    top: (processUnitValue),
    overflow: (processNoopValue),
    zIndex: (processNumberValue),
};

const BOX_MODEL_UNITS = 'px,in,mm,cm,pt,%,vw,vh';
const logError = (style, value) => {
    const name = style.toString();
    // eslint-disable-next-line no-console
    console.error(`
    @react-pdf/stylesheet parsing error:
    ${name}: ${value},
    ${' '.repeat(name.length + 2)}^
    Unsupported ${name} value format
  `);
};
/**
 * @param options
 * @param [options.expandsTo]
 * @param [options.maxValues]
 * @param [options.autoSupported]
 */
const expandBoxModel = ({ expandsTo, maxValues = 1, autoSupported = false, } = {}) => (model, value, container) => {
    const nodes = parse$1(`${value}`);
    const parts = [];
    for (let i = 0; i < nodes.length; i++) {
        const node = nodes[i];
        // value contains `calc`, `url` or other css function
        // `,`, `/` or strings that unsupported by margin and padding
        if (node.type === 'function' ||
            node.type === 'string' ||
            node.type === 'div') {
            logError(model, value);
            return {};
        }
        if (node.type === 'word') {
            if (node.value === 'auto' && autoSupported) {
                parts.push(node.value);
            }
            else {
                const result = parseUnit(node.value);
                // when unit isn't specified this condition is true
                if (result && BOX_MODEL_UNITS.includes(result.unit)) {
                    parts.push(node.value);
                }
                else {
                    logError(model, value);
                    return {};
                }
            }
        }
    }
    // checks that we have enough parsed values
    if (parts.length > maxValues) {
        logError(model, value);
        return {};
    }
    const first = transformUnit(container, parts[0]);
    if (expandsTo) {
        const second = transformUnit(container, parts[1] || parts[0]);
        const third = transformUnit(container, parts[2] || parts[0]);
        const fourth = transformUnit(container, parts[3] || parts[1] || parts[0]);
        return expandsTo({ first, second, third, fourth });
    }
    return {
        [model]: first,
    };
};

const processMargin = expandBoxModel({
    expandsTo: ({ first, second, third, fourth }) => ({
        marginTop: first,
        marginRight: second,
        marginBottom: third,
        marginLeft: fourth,
    }),
    maxValues: 4,
    autoSupported: true,
});
const processMarginVertical = expandBoxModel({
    expandsTo: ({ first, second }) => ({
        marginTop: first,
        marginBottom: second,
    }),
    maxValues: 2,
    autoSupported: true,
});
const processMarginHorizontal = expandBoxModel({
    expandsTo: ({ first, second }) => ({
        marginRight: first,
        marginLeft: second,
    }),
    maxValues: 2,
    autoSupported: true,
});
const processMarginSingle = expandBoxModel({
    autoSupported: true,
});
const handlers$5 = {
    margin: (processMargin),
    marginBottom: (processMarginSingle),
    marginHorizontal: (processMarginHorizontal),
    marginLeft: (processMarginSingle),
    marginRight: (processMarginSingle),
    marginTop: (processMarginSingle),
    marginVertical: (processMarginVertical),
};

const processPadding = expandBoxModel({
    expandsTo: ({ first, second, third, fourth }) => ({
        paddingTop: first,
        paddingRight: second,
        paddingBottom: third,
        paddingLeft: fourth,
    }),
    maxValues: 4,
});
const processPaddingVertical = expandBoxModel({
    expandsTo: ({ first, second }) => ({
        paddingTop: first,
        paddingBottom: second,
    }),
    maxValues: 2,
});
const processPaddingHorizontal = expandBoxModel({
    expandsTo: ({ first, second }) => ({
        paddingRight: first,
        paddingLeft: second,
    }),
    maxValues: 2,
});
const processPaddingSingle = expandBoxModel();
const handlers$4 = {
    padding: (processPadding),
    paddingBottom: (processPaddingSingle),
    paddingHorizontal: (processPaddingHorizontal),
    paddingLeft: (processPaddingSingle),
    paddingRight: (processPaddingSingle),
    paddingTop: (processPaddingSingle),
    paddingVertical: (processPaddingVertical),
};

const offsetKeyword = (value) => {
    switch (value) {
        case 'top':
        case 'left':
            return '0%';
        case 'right':
        case 'bottom':
            return '100%';
        case 'center':
            return '50%';
        default:
            return value;
    }
};

const processObjectPosition = (key, value, container) => {
    const match = `${value}`.split(' ');
    const objectPositionX = offsetKeyword(transformUnit(container, match?.[0] || value));
    const objectPositionY = offsetKeyword(transformUnit(container, match?.[1] || value));
    return { objectPositionX, objectPositionY };
};
const processObjectPositionValue = (key, value, container) => ({
    [key]: offsetKeyword(transformUnit(container, value)),
});
const handlers$3 = {
    objectPosition: (processObjectPosition),
    objectPositionX: (processObjectPositionValue),
    objectPositionY: (processObjectPositionValue),
    objectFit: (processNoopValue),
};

const castInt = (value) => {
    if (typeof value === 'number')
        return value;
    return parseInt(value, 10);
};

const FONT_WEIGHTS = {
    thin: 100,
    hairline: 100,
    ultralight: 200,
    extralight: 200,
    light: 300,
    normal: 400,
    medium: 500,
    semibold: 600,
    demibold: 600,
    bold: 700,
    ultrabold: 800,
    extrabold: 800,
    heavy: 900,
    black: 900,
};
const transformFontWeight = (value) => {
    if (!value)
        return FONT_WEIGHTS.normal;
    if (typeof value === 'number')
        return value;
    const lv = value.toLowerCase();
    if (FONT_WEIGHTS[lv])
        return FONT_WEIGHTS[lv];
    return castInt(value);
};
const processFontWeight = (key, value) => {
    return { [key]: transformFontWeight(value) };
};
const transformLineHeight = (value, styles, container) => {
    if (value === '')
        return value;
    const fontSize = transformUnit(container, styles.fontSize || 18);
    const lineHeight = transformUnit(container, value);
    // Percent values: use this number multiplied by the element's font size
    const { percent } = matchPercent(lineHeight) || {};
    if (percent)
        return percent * fontSize;
    // Unitless values: use this number multiplied by the element's font size
    return isNaN(value) ? lineHeight : lineHeight * fontSize;
};
const processLineHeight = (key, value, container, styles) => {
    return {
        [key]: transformLineHeight(value, styles, container),
    };
};
const handlers$2 = {
    direction: (processNoopValue),
    fontFamily: (processNoopValue),
    fontSize: (processUnitValue),
    fontStyle: (processNoopValue),
    fontWeight: (processFontWeight),
    letterSpacing: (processUnitValue),
    lineHeight: (processLineHeight),
    maxLines: (processNumberValue),
    textAlign: (processNoopValue),
    textDecoration: (processNoopValue),
    textDecorationColor: (processColorValue),
    textDecorationStyle: (processNoopValue),
    textIndent: (processNoopValue),
    textOverflow: (processNoopValue),
    textTransform: (processNoopValue),
    verticalAlign: (processNoopValue),
};

const matchNumber = (value) => typeof value === 'string' && /^-?\d*\.?\d*$/.test(value);
const castFloat = (value) => {
    if (typeof value !== 'string')
        return value;
    if (matchNumber(value))
        return parseFloat(value);
    return value;
};

const parse = (transformString) => {
    const transforms = transformString.trim().split(/\)[ ,]|\)/);
    // Handle "initial", "inherit", "unset".
    if (transforms.length === 1) {
        return [[transforms[0], true]];
    }
    const parsed = [];
    for (let i = 0; i < transforms.length; i += 1) {
        const transform = transforms[i];
        if (transform) {
            const [name, rawValue] = transform.split('(');
            const splitChar = rawValue.indexOf(',') >= 0 ? ',' : ' ';
            const value = rawValue.split(splitChar).map((val) => val.trim());
            parsed.push({ operation: name.trim(), value });
        }
    }
    return parsed;
};
const parseAngle = (value) => {
    const unitsRegexp = /(-?\d*\.?\d*)(\w*)?/i;
    const [, angle, unit] = unitsRegexp.exec(value);
    const number = Number.parseFloat(angle);
    return unit === 'rad' ? (number * 180) / Math.PI : number;
};
const normalizeTransformOperation = ({ operation, value }) => {
    switch (operation) {
        case 'scale': {
            const [scaleX, scaleY = scaleX] = value.map((num) => Number.parseFloat(num));
            return { operation: 'scale', value: [scaleX, scaleY] };
        }
        case 'scaleX': {
            return { operation: 'scale', value: [Number.parseFloat(value), 1] };
        }
        case 'scaleY': {
            return { operation: 'scale', value: [1, Number.parseFloat(value)] };
        }
        case 'rotate': {
            return { operation: 'rotate', value: [parseAngle(value)] };
        }
        case 'translate': {
            return {
                operation: 'translate',
                value: value.map((num) => Number.parseFloat(num)),
            };
        }
        case 'translateX': {
            return {
                operation: 'translate',
                value: [Number.parseFloat(value), 0],
            };
        }
        case 'translateY': {
            return { operation: 'translate', value: [0, Number.parseFloat(value)] };
        }
        case 'skew': {
            return { operation: 'skew', value: value.map(parseAngle) };
        }
        case 'skewX': {
            return { operation: 'skew', value: [parseAngle(value), 0] };
        }
        case 'skewY': {
            return { operation: 'skew', value: [0, parseAngle(value)] };
        }
        default: {
            return { operation, value: value.map((num) => Number.parseFloat(num)) };
        }
    }
};
const normalize = (operations) => {
    return operations.map((operation) => normalizeTransformOperation(operation));
};
const processTransform = (key, value) => {
    if (typeof value !== 'string')
        return { [key]: value };
    return { [key]: normalize(parse(value)) };
};
const Y_AXIS_SHORTHANDS = { top: true, bottom: true };
const sortTransformOriginPair = (a, b) => {
    if (Y_AXIS_SHORTHANDS[a])
        return 1;
    if (Y_AXIS_SHORTHANDS[b])
        return -1;
    return 0;
};
const getTransformOriginPair = (values) => {
    if (!values || values.length === 0)
        return ['center', 'center'];
    const pair = values.length === 1 ? [values[0], 'center'] : values;
    return pair.sort(sortTransformOriginPair);
};
// Transforms shorthand transformOrigin values
const processTransformOriginShorthand = (key, value, container) => {
    const match = `${value}`.split(' ');
    const pair = getTransformOriginPair(match);
    const transformOriginX = transformUnit(container, pair[0]);
    const transformOriginY = transformUnit(container, pair[1]);
    return {
        transformOriginX: offsetKeyword(transformOriginX) || castFloat(transformOriginX),
        transformOriginY: offsetKeyword(transformOriginY) || castFloat(transformOriginY),
    };
};
const processTransformOriginValue = (key, value, container) => {
    const v = transformUnit(container, value);
    return { [key]: offsetKeyword(v) || castFloat(v) };
};
const handlers$1 = {
    transform: processTransform,
    gradientTransform: processTransform,
    transformOrigin: (processTransformOriginShorthand),
    transformOriginX: (processTransformOriginValue),
    transformOriginY: (processTransformOriginValue),
};

const handlers = {
    fill: (processColorValue),
    stroke: (processColorValue),
    strokeDasharray: (processNoopValue),
    strokeWidth: (processUnitValue),
    fillOpacity: (processNumberValue),
    strokeOpacity: (processNumberValue),
    fillRule: (processNoopValue),
    textAnchor: (processNoopValue),
    strokeLinecap: (processNoopValue),
    strokeLinejoin: (processNoopValue),
    visibility: (processNoopValue),
    clipPath: (processNoopValue),
    dominantBaseline: (processNoopValue),
};

const shorthands = {
    ...handlers$b,
    ...handlers$a,
    ...handlers$9,
    ...handlers$8,
    ...handlers$7,
    ...handlers$6,
    ...handlers$5,
    ...handlers$4,
    ...handlers$3,
    ...handlers$2,
    ...handlers$1,
    ...handlers,
};
/**
 * Expand the shorthand properties.
 *
 * @param style - Style object
 * @returns Expanded style object
 */
const resolve = (container) => (style) => {
    const propsArray = Object.keys(style);
    const resolvedStyle = {};
    for (let i = 0; i < propsArray.length; i += 1) {
        const key = propsArray[i];
        const value = style[key];
        if (!shorthands[key]) {
            resolvedStyle[key] = value;
            continue;
        }
        const resolved = shorthands[key](key, value, container, style);
        const keys = Object.keys(resolved);
        for (let j = 0; j < keys.length; j += 1) {
            const propName = keys[j];
            const propValue = resolved[propName];
            resolvedStyle[propName] = propValue;
        }
    }
    return resolvedStyle;
};

/**
 * Resolves styles
 *
 * @param container
 * @param style - Style
 * @returns Resolved style
 */
const resolveStyles = (container, style) => {
    const computeMediaQueries = (value) => resolveMediaQueries(container, value);
    return compose(resolve(container), computeMediaQueries, flatten)(style);
};

export { resolveStyles as default, flatten, transformColor };
