<script>
import tinycolor from 'tinycolor2';
import {
    hsvaEnsureObject,
    hsvaIsEqual,
    hsvaToHsla,
    hslaToStr,
    hsvaContrastColor,
} from '../utils/color.js';

const TINY_FORMATS = ['rgb', 'prgb', 'hex6', 'hex3', 'hex8', 'name', 'hsl', 'hsv'];

export default {
    name: 'px-color-picker',

    props: {
        /**
         * The color value.
         */
        color: { default: '#FF0000' },

        /**
         * The format of the color value (used for emitting events, v-model, and the default format
         * for getColor). Allowed values are the same as those available from the tinycolor library
         * (rgb, prgb, hex6, hex3, hex8, name, hsl, hsv) as well as 'auto' (which will attempt to
         * use the same format as the last value set with the color prop) and 'tiny' (which will
         * return a tinycolor object) and 'hsva' which will return hsva values as an object. The
         * format has the advantage of not requiring a tinycolor object to be created when the color
         * changes.
         *
         * @values rgb, prgb, hex6, hex3, hex8, name, hsl, hsv, auto, tiny
         */
        format: { type: String, default: 'hsva' },

        /**
         * Whether alpha colors are enabled.
         */
        alphaEnabled: { type: Boolean, default: false },
    },

    data() {
        return { autoFormat: this.format, h: 0, s: 0, v: 0, a: 1 };
    },

    computed: {
        hsva() {
            return { h: this.h, s: this.s, v: this.v, a: this.alphaEnabled ? this.a : 1 };
        },
        hsla() {
            return hsvaToHsla(this.hsva);
        },
        hslStr() {
            return hslaToStr(this.hsla, false);
        },
        hslaStr() {
            return hslaToStr(this.hsla, true);
        },
        hslaTransparentStr() {
            return hslaToStr({ ...this.hsla, a: 0 }, true);
        },
        hueStr() {
            return `hsl(${this.h}, 100%, 50%)`;
        },
        contrastStr() {
            return hsvaContrastColor(this.hsva);
        },
        eyedropperSupported() {
            return window?.EyeDropper;
        },
    },

    watch: {
        color(nv) {
            // update colorValue when color changes
            this.setColor(nv);
        },
        hsla() {
            // emit change when this changes as a proxy for changes to hsva as individual props
            this.emitChange();
        },
    },

    mounted() {
        if (this.color) {
            this.setColor(this.color);
        }

        if (this.format) {
            if (!TINY_FORMATS.includes(this.format)) {
                console.warn(
                    `[PxBaseColor] Invalid format: "${this.format}". See comments in PxBaseColor.vue`,
                );
            }
        }
    },
    methods: {
        setColor(strOrHsva) {
            if (typeof strOrHsva === 'string') {
                this.setColorStr(strOrHsva);
            }

            if (typeof strOrHsva === 'object') {
                this.setColorHsva(strOrHsva);
            }
        },

        setColorStr(str) {
            // this is for efficiency but also to prevent color creep as we convert between formats
            if (this.lastEmit == str) {
                return;
            }

            const tiny = tinycolor(str);
            this.autoFormat = tiny.getFormat();

            const hsva = tiny.toHsv();
            this.setColorHsva(hsva);
        },

        setColorHsva(hsva) {
            if (this.isCurrentHsva(hsva)) {
                return;
            }

            this.h = hsva.h;
            this.s = hsva.s;
            this.v = hsva.v;
            this.a = hsva.a;
        },

        emitChange() {
            const clr = this.getColor(this.format);

            this.lastEmit = clr;

            this.$emit('update:color', clr);
            this.$emit('change', clr);
        },

        getColor(format = null) {
            format = format || this.format;

            if (format == 'auto') {
                format = this.autoFormat;
            }

            let clr = { h: this.h, s: this.s, v: this.v, a: this.a };

            if (format == 'hsva') {
                return clr;
            }

            if (format && [...TINY_FORMATS, 'tiny'].includes(format)) {
                const tiny = tinycolor(clr);

                if (format == 'tiny') {
                    clr = tiny;
                } else {
                    clr = tiny.toString(format);
                }
            }

            return clr;
        },

        colorStrToHsva(str) {
            const tiny = tinycolor(str);
            return tiny.toHsv();
        },

        isCurrentHsva(hOrObj, s, v, a) {
            const hsvaCompare = hsvaEnsureObject(hOrObj, s, v, a);
            return hsvaIsEqual(this.hsva, hsvaCompare);
        },

        setColorFromEyedropper() {
            if (this.eyedropperSupported) {
                const eyeDropper = new EyeDropper();
                eyeDropper.open().then(result => {
                    this.setColorStr(result.sRGBHex);
                });
            }
        },
    },
};
</script>
