<template>
    <PxSvgRender v-if="isSvgString" :class="bem()" :svg="svgString" v-bind="bindAttrs" />
    <inline-svg
        v-else
        :class="bem()"
        v-bind="bindAttrs"
        @loaded="$emit('loaded')"
        @unloaded="$emit('unloaded')"
        @error="$emit('error')"
    />
</template>

<script>
import InlineSvg from 'vue-inline-svg';
import PxBaseSvg from '../bases/PxBaseSvg.vue';
import PxSvgRender from './PxSvgRender.vue';
import { cssNum, cssEmFallback, cssPx } from '../utils/cssStr.js';
import propsFilter from '../utils/propsFilter.js';

/**

Extends PxSvg with functionality related to icons. This includes the ability to reference app icons
by name, default replacements collection that converts the color black to `currentColor` and
properties for customizing stroke width, offset and spacing.

To make named app icons available, create a plugin in the project that calls
`app.provide('PxIcon.icons', appIcons)` where `appIcons` is a collection of icons like `{ iconOne: srcOne, iconTwo: srcTwo }`.

PxVue contains the full set of [feather icons](https://feathericons.com/) and a helper for making
those available by name within the application. See the plugin reference below.

```js

// icons.js (add to plugins folder)

import { defineNuxtPlugin } from '#app';
import getIcons from '@thinkpixellab-public/px-vue/utils/getIcons.js';
import getFeatherIcons from '@thinkpixellab-public/px-vue/utils/getFeatherIcons.js';

export default defineNuxtPlugin(nuxtApp => {
    nuxtApp.vueApp?.provide('PxIcon.icons', {

        // use getFeatherIcons to get a filtered map of px-vue feather icons
        ...getFeatherIcons([
            'arrow-left',
            'arrow-right',
            'folder',
        ]),

        // use getIcons to get a map of custom icons
        ...getIcons(import.meta.glob('~/assets/icons/*.svg', { eager: true })),
    });
});


```
 */

const PxIcon = {
    name: 'px-icon',
    mixins: [PxBaseSvg],
    components: { InlineSvg, PxSvgRender },
    props: {
        /**
         * A string that either represents the key (name) of an app-level icon or the svg src for
         * the icon.
         *
         * Note: to define app-level icons, create a plugin that calls 'provide' with an object that
         * maps names to icon sources. See docs for details.
         */
        src: { type: String, default: null },

        /**
         * String replacements to be made on the svg. By default, black in the svg is replaced with
         * currentColor so that black icons inherit the current text color.
         */
        replacements: {
            type: Object,
            default: () => {
                return { '#000000': 'currentColor', '#000': 'currentColor', black: 'currentColor' };
            },
        },

        /**
         * Overrides the natural size of the icon if non-null. Assumes 'em' as a unit unless one is
         * specified. Defaults to 1em so that inline icons match the current text size.
         */
        size: { type: [Number, String], default: '1em' },

        /**
         * The stroke width for the icon. Some icons are built with stroked paths and so
         * adjusthing this is similar to adjust the font-weight on text (it creates a lighter /
         * bolder version of the icon).
         */
        strokeWidth: { type: [Number, String], default: null },

        /**
         * When true, we set vector-effect="non-scaling-stroke" on all paths in the icon and remove
         * all internal stroke-width attributes. This will have the effect of making the stroke
         * width consistent regardless of the size of the icon. This is useful when you want to use
         * the same icon at different sizes and have the stroke width scale with the icon.
         */
        strokeWidthScales: { type: Boolean, default: true },

        /**
         * Convenience property that adjusts the vertical offset of the icon by setting a translate
         * transform on the svg. This is sometimes useful because the icon doesn't visually line up
         * with other text. Can be a string with units or a number that will be interpreted as ems.
         */
        offset: { default: null, type: [Number, String] },

        /**
         * Convenience property that adds inline spacing before and after the icon. Can be a string
         * with units or a number that will be interpreted as ems.
         */
        spacing: { default: null, type: [Number, String] },
    },

    inject: {
        icons: { from: 'PxIcon.icons', default: null },
    },

    computed: {
        calcSrc() {
            return this.srcLookup(this.src);
        },
        calcStyle() {
            return this.getIconStyle();
        },
    },

    methods: {
        getIconStyle() {
            const offset = cssNum(this.offset);
            const spacing = cssNum(this.spacing);

            if (offset || spacing || this.strokeWidth) {
                return {
                    transform: offset ? `translateY(${cssEmFallback(this.offset)})` : null,
                    margin: spacing ? `auto ${cssEmFallback(this.spacing)}` : null,
                    strokeWidth: cssPx(this.strokeWidth, false),
                };
            }
            return null;
        },

        srcLookup(src, fallback = ' ') {
            if (src) {
                if (this.icons && src in this.icons) {
                    return this.icons[src];
                }
            }
            return src || fallback;
        },

        transform(svg) {
            if (!this.strokeWidthScales) {
                svg.setAttribute('vector-effect', 'non-scaling-stroke');

                // set vector-effect="non-scaling-stroke" on all paths
                const paths = svg.querySelectorAll(
                    'circle, ellipse, line, path, polygon, polyline, rect',
                );
                for (const path of paths) {
                    path.setAttribute('vector-effect', 'non-scaling-stroke');
                    if (path.hasAttribute('stroke-width')) {
                        path.removeAttribute('stroke-width');
                    }
                }
            }

            return this.transformDefault(svg);
        },
    },
};

// helper function that filters out props that are not defined in the component when using v-bind to
// set icon props
function PxIconPropsFilter(attrs) {
    return propsFilter(PxIcon, ['class', 'style'])(attrs);
}

// helper function that makes that creates PxIcon attributes from a src string or attrs object
function PxIconSrcOrAttrs(srcOrAttrs, size = 1) {
    if (!srcOrAttrs) {
        return null;
    }

    // if the src is a string, we assume it is the src attribute
    if (typeof srcOrAttrs === 'string') {
        return { src: srcOrAttrs, size };
    }

    // if the src is an object, we assume it is the attrs object
    return PxIconPropsFilter(srcOrAttrs);
}

// defaut export
export default PxIcon;

// named exports
export { PxIcon, PxIconPropsFilter, PxIconSrcOrAttrs };
</script>

<style lang="scss">
.px-icon {
    width: auto;
    height: auto;
    position: relative;
    color: inherit;
    display: inline-block;
    line-height: inherit;
    vertical-align: baseline;
}
</style>
