<script>
import isServer from '../utils/isServer';
import { cssUnitScale, cssEmFallback } from '../utils/cssStr.js';

export default {
    emits: [
        /**
         * (vue-inline-svg) Called when SVG image is loaded and inlined. Inlined SVG element passed as argument into the listener’s callback function.
         * @event loaded
         **/
        'loaded',

        /**
         * (vue-inline-svg) Called when src prop was changed and another SVG starts loading.
         * @event unloaded
         **/
        'unloaded',

        /**
         * (vue-inline-svg) Called when SVG failed to load. Error object passed as argument into the listener’s callback function.
         * @event error
         **/
        'error',

        /**
         * Called when the size of the svg has been determined and includes an arg with the final size of the svg.
         * @event sized
         * @property {object} size - an object containing the natural and calculated with and height of the svg
         **/
        'sized',
    ],
    props: {
        /**
         * The source url for the svg.
         */
        src: { type: String, default: '' },

        /**
         * Overrides the natural size of the svg if non-null. Assumes 'em' as a unit unless one is
         * specified.
         */
        size: { type: [Number, String], default: null },

        /**
         * The size will be applied directly to one dimension (the one specified here) and the
         * other will be computed to maintain the aspect (as determined by the viewBox or
         * size/width)
         * @values width, height
         */
        sizeBias: {
            type: String,
            default: 'height',
            validator: val => ['width', 'height'].includes(val),
        },

        /**
         * String replacements to be made on the svg.
         */
        replacements: { type: Object, default: null },

        /** Set the title for accessibility */
        title: { type: String, default: null },

        /** The role applied for accessibility */
        role: { type: String, default: null },

        /** Focusable false by default for accessibility in older browsers */
        focusable: { type: Boolean, default: false },
    },
    data() {
        return {
            svgComponent: null,
            svgWidth: null,
            svgHeight: null,
            svgViewBox: null,
            sized: false,
        };
    },

    computed: {
        bindAttrs() {
            const attrs = {
                viewBox: this.svgViewBox,
                style: this.calcStyle,
                role: this.role,
                title: this.title,
                ariaLabel: this.title,
                focusable: this.focusable,
            };

            if (this.calcWidth) {
                attrs.width = this.calcWidth;
            }

            if (this.calcHeight) {
                attrs.height = this.calcHeight;
            }

            if (!this.svgComponent) {
                attrs.src = this.calcSrc;
                attrs.transformSource = this.transform;
            }

            return attrs;
        },
        calcSrc() {
            return this.src;
        },
        calcAspectRatio() {
            if (this.svgViewBox) {
                const viewBox = this.svgViewBox.split(' ');
                const width = parseFloat(viewBox[2]);
                const height = parseFloat(viewBox[3]);
                if (width && height) {
                    return width / height;
                }
            }
            if (this.svgWidth && this.svgHeight) {
                return this.svgWidth / this.svgHeight;
            }

            // fallback is 1:1
            return 1 / 1;
        },
        calcHeight() {
            if (!this.size && this.svgHeight) {
                return this.svgHeight;
            }

            if (this.sizeBias == 'height') {
                return cssEmFallback(this.size);
            }

            if (this.size && this.calcAspectRatio) {
                return cssUnitScale(cssEmFallback(this.size), 1 / this.calcAspectRatio);
            }

            //
            return '100';
        },
        calcWidth() {
            if (!this.size && this.svgWidth) {
                return this.svgWidth;
            }

            if (this.sizeBias == 'width') {
                return cssEmFallback(this.size);
            }

            if (this.size && this.calcAspectRatio) {
                return cssUnitScale(cssEmFallback(this.size), this.calcAspectRatio);
            }

            // equivalent of 'auto' (but needs to be a number or else the parse shows console errors)
            return '100';
        },
        calcStyle() {
            return null;
        },
        isSvgString() {
            return this.src && this.src.includes('<svg');
        },
        svgString() {
            if (this.isSvgString) {
                return this.transformSvgString(this.src);
            }
        },
    },

    methods: {
        transformSvgString(svg) {
            if (isServer() || DOMParser === undefined) {
                return svg;
            }

            const parser = new DOMParser();
            const svgElement = parser.parseFromString(svg, 'image/svg+xml').querySelector('svg');
            const transformed = this.transform(svgElement);

            return transformed.outerHTML;
        },

        transformDefault(svg) {
            if (!svg) {
                return;
            }

            if (svg.viewBox) {
                // only a valid viewbox if it has both width/height
                let viewBox =
                    svg.viewBox.baseVal.width && svg.viewBox.baseVal.height
                        ? svg.viewBox.baseVal
                        : null;

                // we only support width/height set in px (unitType = 1 or 5)
                let width =
                    svg.width.baseVal.unitType == 1 || svg.width.baseVal.unitType == 5
                        ? parseFloat(svg.width.baseVal.value)
                        : svg.viewBox?.width || 0;
                let height =
                    svg.height.baseVal.unitType == 1 || svg.height.baseVal.unitType == 5
                        ? parseFloat(svg.height.baseVal.value)
                        : svg.viewBox?.height || 0;

                // save as data values
                if (viewBox?.width && viewBox?.height) {
                    this.svgViewBox = viewBox
                        ? `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`
                        : `0 0 ${width} ${height}`;
                    this.svgWidth = width;
                    this.svgHeight = height;
                }
            }

            // string replace
            if (this.replacements) {
                let inner = svg.innerHTML;
                for (let str in this.replacements) {
                    inner = inner.replaceAll(str, this.replacements[str]);
                }
                svg.innerHTML = inner;
            }

            this.$emit('sized', {
                svgWidth: this.svgWidth,
                svgHeight: this.svgHeight,
                calcWidth: this.calcWidth,
                calcHeight: this.calcHeight,
            });

            this.sized = true;

            return svg;
        },

        transform(svg) {
            return this.transformDefault(svg);
        },
    },
};
</script>
