/**
 * @class Ext.qrcode.generator.QRCode
 * @extend Ext.qrcode.generator.GeneratorBase
 * @alias widget.qrcode
 * This class is a QR code generator component that renders a QR code based on the 
 * provided {@link #cfg-data}, it is dependent on qr code library[`qrcode.min.js`]( https://github.com/kazuhikoarase/qrcode-generator
 * which generates QR codes directly in the browser using an HTML `<canvas>` element.
 * 
 * @since 8.0.0
 *
 * ## Example
 * 
 * ```javascript
 * @example({ framework: 'extjs' })
 * Ext.application({
 *     name: 'QRCodeApp'
 *     launch: function() {
 *         Ext.create('Ext.qrcode.generator.QRCode', {
 *             renderTo: Ext.getBody(),
 *             height: 300,
 *             width: 300,
 *             padding: 20,
 *             data: 'Sample text from QR code'
 *         });
 *     }
 * });
 * ```
 *
 * It also supports generating various types of QR codes, such as email, SMS, and more.
 * Valid values for the {@link #cfg-type} config include: email, sms, vCard, meCard,
 * phoneNumber, calendar, paypal, bitcoin, WIFI, location, and upi.
 *
 * ## Example
 * ```javascript
 * @example({ framework: 'extjs' })
 * Ext.application({
 *     name: 'QRCodeApp',
 *     launch: function() {
 *         Ext.create('Ext.qrcode.generator.QRCode', {
 *             renderTo: Ext.getBody(),
 *             height: 300,
 *             width: 300,
 *             padding: 20,
 *             data: 'Sample text from QR code'
 *         });
 *     }
 * });
 * ```
 * 
  * ```javascript
 * @example({ framework: 'extjs' })
 * Ext.application({
 *    name: 'QRCodeApp',
 *    launch: function() {
 *         Ext.create('Ext.qrcode.generator.QRCode', {
 *             renderTo: Ext.getBody(),
 *             height: 300,
 *             width: 300,
 *             padding: 20,
 *             type: 'email',
 *             data: {
 *                 email: '[email protected]',
 *                 subject: 'Test Subject',
 *                 body: 'Test email body content'
 *             }
 *         });
 *    }
 * });
 * ```
 */
 
Ext.define('Ext.qrcode.generator.QRCode', {
    extend: 'Ext.qrcode.generator.GeneratorBase',
 
    xtype: 'qrcode',
 
    config: {
        /**
         * @cfg {Number} qrSize
         * Default size for the QR code canvas
         * 
         * @since 8.0.0
         */
        qrSize: 220,
 
        /**
         * @cfg {String} type
         * Type of QR code to be generated
         * valid types currently it supports are - vcard, email, vcard, wifi, sms, mecard, location,
         * location, upi, paypal, bitcoin, calendar, phone.
         * Each type has its own set keys to be passed in {@link #cfg-data} config 
         * in order to generate QR code 
         * for the type intend.
         * 
         * @since 8.0.0
         */
        type: 'text',
 
        /**
         * @cfg {Object/String} data
         * data for which QR code should be generated, can be a string
         * or an object with valid key value pairs.
         * each {@link #cfg-type} config has a set of different objects, 
         * below are the valid key value pairs to be passed for each type
         * 
         * email - email, subject, body
         * sms - phone, text
         * location - latitude, longititude
         * wifi - `encryptionType`, `ssid`, `password`
         * vcard - firstName, lastName, phone, email, company, address, title, website
         * mecard - firstName, lastName, phone, email, address, org, url, note, birthday
         * phone - phoneNumber
         * calendar - summary, description, location, start, end
         * upi - vpa, name, amount, currency
         * paypal - username, amount
         * bitcoin - address, amount, label
         * 
         * Defaults to `null`
         * 
         * @since 8.0.0
         */
        data: null,
 
        /**
         * @cfg {String} qrColor
         * Default color for QR code
         * 
         * @since 8.0.0
         */
        qrColor: '#000000',
 
        /**
         * @cfg {String} qrBackgroundColor
         * Default background color for QR code
         * 
         * @since 8.0.0
         */
        qrBackgroundColor: '#ffffff',
 
        /**
         * @cfg {String} errorCorrectionLevel
         * Controls how much error recovery is built in. Higher levels increase size.
         * Accepted values 'L', 'M', 'Q', 'H' where 'L' is lowest and 'H' is the heighest
         * 
         * @since 8.0.0
         */
        errorCorrectionLevel: 'M'
    },
 
    qrInstance: null,
 
    /**
     * @event qrgenerated
     * Fires when Qr Code is generated successfully
     * @param {Ext.qrcode.generator.QRCode} this - current instance of the component
     * @param {Object} data Object/String that is passed for QR code generation
     */
    afterRender: function() {
        var me = this,
            canvas = me.getCanvas(),
            size = me.getQrSize();
 
        me.callParent();
 
        if (canvas) {
            canvas.width = size;
            canvas.height = size;
 
            me.generate();
        }
    },
 
    getCanvas: function() {
        var me = this;
 
        return me.canvasEl ? me.canvasEl.dom : null;
    },
    /**
     * Returns a map of QR code data template handler functions based on the supported
     * `type` values.Each key corresponds to a QR code content type, and each value
     *  is a method responsible for generating the properly formatted data string for that type.
     */
    getDataTemplates: function() {
        var me = this;
 
        if (!me.qrDataTemplates) {
            me.qrDataTemplates = {
                'vcard': me.getVCardQRData,
                'mecard': me.getMeCardQRData,
                'wifi': me.getWIFIQRData,
                'email': me.getEmailQRData,
                'sms': me.getSMSQRData,
                'location': me.getLocationQRData,
                'phone': me.getPhoneNumberQRData,
                'calendar': me.getCalenderEventQRData,
                'upi': me.getUPIQRData,
                'paypal': me.getPayPalQRData,
                'bitcoin': me.getBitcoinQRData
            };
        }
 
        return me.qrDataTemplates;
    },
 
    /**
     * Generates a QR code based on the given `type` and `data`.  
     * If a recognized `type` is provided (e.g., `email`, `sms`, `wifi`), the corresponding
     * formatter method is used to transform the input `data` into a properly structured string
     * before generating the QR code. If no matching formatter is found for the given `type`, 
     * the raw data is used as-is.
     * 
     * This method uses the `qrcode.min.js` library to encode the data and then draws
     * the generated QR matrix onto an HTML5 `<canvas>` element.
     *
     * @param {String} data The data string to encode in the QR code.
     * @param {String} [type] The type of QR code to 
     * generate (e.g., `email`, `sms`, `wifi`, `vcard`, etc.).
     * @private
     */
    generate: function(data, type) {
        var me = this,
            canvas = me.getCanvas(),
            qrSize = me.getQrSize(),
            ctx, qr, moduleCount, tileW, tileH, i, j;
 
        if (!canvas) {
            return;
        }
 
        data = data || me.getData();
        type = type || me.getType();
 
        if (Ext.isEmpty(data)) {
            Ext.log.warn('Please provide some text to generate QR code');
 
            return;
        }
 
        if (!Ext.isEmpty(type)) {
            data = me.getDataTemplateByType(type, data);
        }
 
        canvas.width = qrSize;
        canvas.height = qrSize;
 
        // eslint-disable-next-line no-undef
        if (!qrcode) {
            Ext.log.error("qrcode is not defined");
        }
 
        // eslint-disable-next-line no-undef
        qr = qrcode(0, me.getErrorCorrectionLevel());
        qr.addData(data);
        qr.make();
 
        me.qrInstance = qr;
 
        // Get canvas context
        ctx = canvas.getContext('2d');
        moduleCount = qr.getModuleCount();
        tileW = canvas.width / moduleCount;
        tileH = canvas.height / moduleCount;
 
        ctx.fillStyle = me.getQrBackgroundColor();
        ctx.fillRect(0, 0, canvas.width, canvas.height);
 
        // Draw QR modules
        for (= 0; i < moduleCount; i++) {
            for (= 0; j < moduleCount; j++) {
                if (qr.isDark(i, j)) {
                    ctx.fillStyle = me.getQrColor();
                }
                else {
                    ctx.fillStyle = me.getQrBackgroundColor();
                }
 
                ctx.fillRect(
                    Math.round(* tileW),
                    Math.round(* tileH),
                    Math.ceil(tileW),
                    Math.ceil(tileH)
                );
            }
        }
 
        me.fireEvent('qrgenerated', me, data);
    },
 
    getDataTemplateByType: function(type, data) {
        var me = this,
            dataTemplatesObj = me.getDataTemplates();
 
        if (dataTemplatesObj) {
            type = type.toLowerCase();
 
            if (!Ext.isEmpty(dataTemplatesObj[type])) {
                data = dataTemplatesObj[type](data);
            }
        }
 
        return data;
    },
 
    /**
    * Copies the current canvas content to the system clipboard as a PNG image.
    *
    * This method converts the canvas to a blob and uses the Clipboard API to copy the image.
    * It checks for browser support and logs an error if the functionality is unavailable.
    *
    * **Note:** Clipboard API must be supported by the browser, 
    * and the page must be served over HTTPS.
    */
    copyToClipboard: function() {
        var me = this,
            canvas = me.getCanvas(),
            hasClipboardSupport, item;
 
        hasClipboardSupport = navigator.clipboard &&
                          Ext.isFunction(ClipboardItem) &&
                          Ext.isFunction(navigator.clipboard.write);
 
        if (!hasClipboardSupport) {
            Ext.log.error('Your browser does not support copying to the clipboard.');
 
            return;
        }
 
        // Create the ClipboardItem with a Promise IMMEDIATELY (during user gesture)
        item = new ClipboardItem({
            // eslint-disable-next-line no-undef
            'image/png': new Promise(function(resolve, reject) {
                canvas.toBlob(function(blob) {
                    if (blob) {
                        resolve(blob);
                    }
                    else {
                        reject(new Error('Could not create image blob.'));
                    }
                }, 'image/png');
            })
        });
 
        // Write to clipboard while still in user gesture context
        navigator.clipboard.write([item])
        .then(function() {
            // <debug>
            Ext.log('Image copied to clipboard successfully.');
            // </debug>
        })
        .catch(function(error) {
            // <debug>
            Ext.log.error('Failed to copy image to clipboard:', error);
            // </debug>
        });
    },
 
    /**
     * Prompts the user to enter a filename and downloads the current canvas content
     * as a PNG image file. The filename is sanitized to remove invalid characters,
     * and a `.png` extension is added if not provided.
     */
    downloadAsPNG: function() {
        var me = this,
            canvas = me.getCanvas(),
            link, filename;
 
        if (canvas) {
            Ext.Msg.prompt('Download As PNG', 'Enter filename (without extension):',
                           function(btn, text) {
                               if (btn === 'ok' && text) {
                                   filename = text.trim();
 
                                   if (!filename) {
                                       Ext.Msg.alert('Error', 'Please enter a valid filename.');
 
                                       return;
                                   }
 
                                   // Add .png extension if not present
                                   if (!filename.toLowerCase().endsWith('.png')) {
                                       filename += '.png';
                                   }
 
                                   // Remove invalid characters for filename
                                   filename = filename.replace(/[<>:"/\\|?*]/g, '_');
 
                                   // Create and trigger download
                                   link = document.createElement('a');
                                   link.href = canvas.toDataURL('image/png');
                                   link.download = filename;
 
                                   document.body.appendChild(link);
                                   link.click();
                                   document.body.removeChild(link);
                               }
                           }, me, false, 'qr-code');
        }
    },
 
    /**
     * Prompts the user to enter a filename and downloads the current QR code as an SVG file.
     *
     * **Note:** Some characters are removed from the filename to 
     * ensure compatibility across file systems.
     */
    downloadAsSVG: function() {
        var me = this,
            canvas = me.getCanvas(),
            qr = me.qrInstance,
            svg, blob, url, link, filename, cleanedAttrs;
 
        if (qr && canvas) {
            Ext.Msg.prompt('Download SVG',
                           'Enter filename (without extension):', function(btn, text) {
                               if (btn === 'ok' && text) {
                                   filename = text.trim();
                                   filename = filename.replace(/[<>:"/\\|?*]/g, '_');
 
                                   svg = qr.createSvgTag({ scalable: true });
                                   svg = svg.replace(/fill="black"/g, 'fill="' + me.getQrColor() + '"');
                                   svg = svg.replace(/fill="white"/g, 'fill="' + me.getQrBackgroundColor() + '"');
 
                                   svg = svg.replace(/<svg([^>]*?)>/, function(match, attrs) {
                                       cleanedAttrs = attrs.replace(/width="[^"]*"/g, '').replace(/height="[^"]*"/g, '');
 
                                       return '<svg width="' + canvas.width + '" height="' +
                                            canvas.height + '"' + cleanedAttrs + '>';
                                   });
 
                                   blob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' });
                                   url = URL.createObjectURL(blob);
 
                                   link = document.createElement('a');
                                   link.href = url;
                                   link.download = filename + '.svg';
                                   link.click();
 
                                   URL.revokeObjectURL(url);
                               }
                           }, me, false, 'qr-code');
 
        }
    },
 
    /**
     *  Generates a `mailto:` URL string from the given email object,
     * suitable for use in QR codes or direct email links.
     * 
     * @param {Object} emailObj An object containing email details.
     *  valid keys to be provided - email, subject, body.
     * 
     * @return {String} A `mailto:` URL string including encoded query
     * parameters for subject and body if provided.
     * @private
     */
    getEmailQRData: function(emailObj) {
        var mailtoLink = 'mailto:' + emailObj.email,
            params = [];
 
        if (emailObj.subject) {
            params.push('subject=' + encodeURIComponent(emailObj.subject));
        }
 
        if (emailObj.body) {
            params.push('body=' + encodeURIComponent(emailObj.body));
        }
 
        if (params.length > 0) {
            mailtoLink += '?' + params.join('&');
        }
 
        return mailtoLink;
 
    },
 
    /**
     * Generates a vCard 3.0 formatted string from the given vCard object,
     * suitable for use in QR codes or contact sharing.
     *
     * @param {Object} vCardObj An object containing contact details.
     * valid keys to be passed in this object - firstName, lastName, address, 
     * phone, email, company, title.
     *
     * @return {String} A vCard formatted string representing the contact.
     */
    getVCardQRData: function(vCardObj) {
        var fieldMap = {
                phone: 'TEL;TYPE=CELL',
                email: 'EMAIL',
                company: 'ORG',
                title: 'TITLE',
                website: 'URL'
            },
            vCard = 'BEGIN:VCARD\nVERSION:3.0\n',
            lastName,
            firstName,
            vCardKey;
 
        // Handle name fields separately since they require special formatting
        if (vCardObj.firstName || vCardObj.lastName) {
            lastName = vCardObj.lastName || '';
            firstName = vCardObj.firstName || '';
 
            vCard += 'N:' + lastName + ';' + firstName + ';;;\n';
            vCard += 'FN:' + firstName + ' ' + lastName + '\n';
        }
 
        // Handle address separately since it has special formatting
        if (vCardObj.address) {
            vCard += 'ADR:;;' + vCardObj.address + ';;;;\n';
        }
 
        // Process other fields using the mapping
        for (vCardKey in fieldMap) {
            if (vCardObj[vCardKey]) {
                vCard += fieldMap[vCardKey] + ':' + vCardObj[vCardKey] + '\n';
            }
        }
 
        vCard += 'END:VCARD';
 
        return vCard;
    },
 
    /**
     * Generates an `sms:` URI string for use in QR codes or clickable SMS links.
     *
     * @param {Object} smsObj An object containing SMS details.
     *  valid keys to be passed in this object - phone, text.
     *
     * @return {String} An `sms:` URI string formatted as `sms:<phone>?body=<text>`.
     * 
     * @private
     */
    getSMSQRData: function(smsObj) {
        return 'sms:' + smsObj.phone + '?body=' + smsObj.text;
    },
 
    /**
     * @private
     */
    getLocationQRData: function(locationObj) {
        return 'geo:' + locationObj.latitude + ',' + locationObj.longitude;
    },
 
    /**
     * @private
     */
    getPhoneNumberQRData: function(phoneObj) {
        return 'tel:' + phoneObj.phoneNumber;
    },
 
    /**
     * @private
     */
    getWIFIQRData: function(wifiObj) {
        return 'WIFI:T:' + wifiObj.encryptionType + ';S:' + wifiObj.ssid +
                    ';P:' + wifiObj.password + ';';
    },
 
    /**
     * @private
     */
    getCalenderEventQRData: function(eventObj) {
        var vCalendar =
                'BEGIN:VCALENDAR\n' +
                'VERSION:2.0\n' +
                'BEGIN:VEVENT\n' +
                'SUMMARY:' + eventObj.summary + '\n' +
                'DESCRIPTION:' + eventObj.description + '\n' +
                'LOCATION:' + eventObj.location + '\n' +
                'DTSTART:' + eventObj.start + '\n' +
                'DTEND:' + eventObj.end + '\n' +
                'END:VEVENT\n' +
                'END:VCALENDAR';
 
        return vCalendar;
    },
 
    /**
     * @private
     */
    getUPIQRData: function(upiObj) {
        var upiLink = 'upi://pay?pa=' + encodeURIComponent(upiObj.vpa) +
                          '&pn=' + encodeURIComponent(upiObj.name) +
                          '&am=' + encodeURIComponent(upiObj.amount) +
                          '&cu=' + upiObj.currency;
 
        return upiLink;
    },
 
    /**
     * @private
     */
    getPayPalQRData: function(payPalObj) {
        return 'https://www.paypal.me/' + encodeURIComponent(payPalObj.username) + '/' + encodeURIComponent(payPalObj.amount);
    },
 
    /**
     * @private 
     */
    getBitcoinQRData: function(bitCoinObj) {
        return 'bitcoin:' + bitCoinObj.address + '?amount=' + bitCoinObj.amount +
                    '&label=' + encodeURIComponent(bitCoinObj.label);
    },
 
    /**
     * 
     * @param {Object} contact 
     * @private 
     */
    getMeCardQRData: function(contact) {
        var fieldMap = {
                phone: 'TEL',
                email: 'EMAIL',
                address: 'ADR',
                org: 'ORG',
                url: 'URL',
                note: 'NOTE',
                birthday: 'BDAY'
            },
            mecard = 'MECARD:',
            contactKey;
 
        if (contact.firstName || contact.lastName) {
            mecard += 'N:' + (contact.lastName || '') + ',' + (contact.firstName || '') + ';';
        }
 
        if (contact.address) {
            mecard += 'ADR:;;' + contact.address + ';;;;\n';
        }
 
        for (contactKey in fieldMap) {
            if (contact[contactKey]) {
                mecard += fieldMap[contactKey] + ':' + contact[contactKey] + ';';
            }
        }
 
        mecard += ';';
 
        return mecard;
    },
 
    /**
     * Regenerates the QR code using the current {@link #cfg-data} and 
     * {@link #cfg-type} configs.
     * it re-generates the QR code only if a QR instance has already been created.
     */
    applyData: function(data) {
        var me = this;
 
        if (me.qrInstance) {
            me.generate(data, me.getType());
        }
 
        return data;
    },
 
    /**
     * Resets the QR code's foreground and background colors to their initial configured values.
     * It uses the values provided in the `initialConfig` or from the current `config` values.
     * After resetting the colors, it triggers QR code regeneration using the current type and 
     * data.
     */
    resetQRColor: function() {
        var me = this,
            color = me.initialConfig.color || me.config.color,
            bgColor = me.initialConfig.qrBackgroundColor || me.config.qrBackgroundColor;
 
        me.setQrBackgroundColor(bgColor);
        me.setColor(color);
        me.generate();
    },
 
    /**
     * @param {String} color 
     * regenerates the QR with the new color passed
     */
    updateQrColor: function(color) {
        var me = this;
 
        if (me.qrInstance) {
            me.generate();
        }
    },
 
    /**
     * @param {String} color 
     * regenerates the QR code with the new background color passed
     */
    updateQrBackgroundColor: function(color) {
        var me = this;
 
        if (me.qrInstance) {
            me.generate();
        }
    },
 
    /**
     * @param {Number} size 
     * 
     * regenerates the QR code with the new size passed
     */
    updateQrSize: function(size) {
        var me = this;
 
        if (me.qrInstance) {
            me.generate();
        }
    }
});