/**
 * Copyright Aquera Inc 2023
 *
 * This source code is licensed under the BSD-3-Clause license found in the
 * LICENSE file in the root directory of this source tree.
 */
import { __decorate } from "tslib";
import { html } from 'lit';
import { customElement, property, query } from 'lit/decorators.js';
import { styles } from './nile-qr-code.css.js';
import { generateQR } from './nile-qr-code-utils.js';
import NileElement from '../internal/nile-element.js';
/**
 * Nile QR Code component.
 *
 
 * @tag nile-qr-code
 *
 * @csspart base - The canvas element used to draw the QR code.
 *
 * @fires nile-qr-image-error - Emitted when the center image fails to load.
 *
 * @example
 
 *
 
 */
let NileQrCode = class NileQrCode extends NileElement {
    constructor() {
        super(...arguments);
        // -------------------------------------------------------------------------
        // Properties
        // -------------------------------------------------------------------------
        /**
         * The QR code's value — the text or URL to encode.
         */
        this.value = '';
        /**
         * The size of the QR code, in pixels.
         */
        this.size = 128;
        /**
         * The fill color. This can be any valid CSS color, but not a CSS custom property.
         */
        this.fill = 'black';
        /**
         * The background color. This can be any valid CSS color or `'transparent'`.
         * It cannot be a CSS custom property.
         */
        this.background = 'white';
        /**
         * The edge radius of each module. Must be between 0 and 0.5.
         * Use this to create a rounded effect.
         */
        this.radius = 0;
        /**
         * The level of error correction to use.
         * - `'L'` — Low (~7%)
         * - `'M'` — Medium (~15%)
         * - `'Q'` — Quartile (~25%)
         * - `'H'` — High (~30%)
         *
         * When using an image overlay, `'H'` is recommended so the QR remains scannable.
         */
        this.errorCorrection = 'H';
        /**
         * The label for assistive devices to announce.
         * If unspecified, the `value` will be used instead.
         */
        this.label = '';
        /**
         * URL of an image to overlay in the center of the QR code (e.g. a logo).
         * The image is drawn on top of the QR modules, so use `error-correction="H"`
         * to ensure the code remains scannable.
         */
        this.image = '';
        /**
         * Size of the center image as a fraction of the overall QR code size
         * (0.1 – 0.4). Defaults to 0.25 (25% of the QR code).
         */
        this.imageSize = 0.25;
        /**
         * Padding around the center image in pixels. Creates a clear area
         * between the image and the surrounding QR modules.
         */
        this.imagePadding = 4;
        /**
         * Optional border radius for the center image in pixels.
         * Set to a high value for a circular mask.
         */
        this.imageRadius = 4;
        /**
         * Apply a linear gradient to the QR modules instead of a flat color.
         * Format: `"direction, color1, color2[, ...]"` where direction is an
         * angle in degrees (e.g. `"135, #6366f1, #ec4899"`).
         * When set, this overrides the `fill` property for module rendering.
         */
        this.fillGradient = '';
        // -------------------------------------------------------------------------
        // Internal state
        // -------------------------------------------------------------------------
        this._loadedImage = null;
        this._imageLoadPromise = null;
    }
    /**
     * The styles for QR Code.
     * @remarks If you are extending this class you can extend the base styles with super. Eg `return [super(), myCustomStyles]`
     */
    static get styles() {
        return [styles];
    }
    // -------------------------------------------------------------------------
    // Lifecycle
    // -------------------------------------------------------------------------
    updated(changedProperties) {
        super.updated(changedProperties);
        if (changedProperties.has('image')) {
            this._loadedImage = null;
            this._imageLoadPromise = null;
            if (this.image) {
                this.loadImage(this.image);
                return;
            }
        }
        const relevantProps = [
            'value', 'size', 'fill', 'background', 'radius', 'errorCorrection',
            'imageSize', 'imagePadding', 'imageRadius', 'fillGradient',
        ];
        const needsRedraw = relevantProps.some(p => changedProperties.has(p));
        if (needsRedraw) {
            this.drawQrCode();
        }
    }
    // -------------------------------------------------------------------------
    // Rendering
    // -------------------------------------------------------------------------
    render() {
        const ariaLabel = this.label || this.value || 'QR Code';
        return html `
      <canvas
        part="base"
        role="img"
        aria-label=${ariaLabel}
        width=${this.size}
        height=${this.size}
      ></canvas>
    `;
    }
    // -------------------------------------------------------------------------
    // Public methods
    // -------------------------------------------------------------------------
    /**
     * Downloads the QR code as a PNG image.
     * @param filename - The name of the downloaded file (defaults to `'qr-code.png'`).
     */
    download(filename = 'qr-code.png') {
        if (!this.canvas)
            return;
        const link = document.createElement('a');
        link.download = filename;
        link.href = this.canvas.toDataURL('image/png');
        link.click();
    }
    /**
     * Returns the QR code canvas content as a data URL.
     * @param type - MIME type (defaults to `'image/png'`).
     */
    toDataURL(type = 'image/png') {
        if (!this.canvas)
            return '';
        return this.canvas.toDataURL(type);
    }
    // -------------------------------------------------------------------------
    // Private methods
    // -------------------------------------------------------------------------
    loadImage(src) {
        const img = new Image();
        img.crossOrigin = 'anonymous';
        this._imageLoadPromise = new Promise((resolve) => {
            img.onload = () => {
                this._loadedImage = img;
                this._imageLoadPromise = null;
                this.drawQrCode();
                resolve();
            };
            img.onerror = () => {
                this._loadedImage = null;
                this._imageLoadPromise = null;
                this.emit('nile-qr-image-error', { src });
                this.drawQrCode();
                resolve();
            };
        });
        img.src = src;
    }
    drawQrCode() {
        if (!this.canvas)
            return;
        const ctx = this.canvas.getContext('2d');
        if (!ctx)
            return;
        const { size, fill, background, radius: rawRadius, errorCorrection, value } = this;
        const clampedRadius = Math.max(0, Math.min(0.5, rawRadius));
        const matrix = generateQR(value, errorCorrection);
        const moduleCount = matrix.length;
        const moduleSize = size / moduleCount;
        ctx.clearRect(0, 0, size, size);
        if (background && background !== 'transparent') {
            ctx.fillStyle = background;
            ctx.fillRect(0, 0, size, size);
        }
        const imgFraction = Math.max(0.1, Math.min(0.4, this.imageSize));
        const imgPixelSize = this._loadedImage ? size * imgFraction : 0;
        const padTotal = this._loadedImage ? this.imagePadding * 2 : 0;
        const exclusionSize = imgPixelSize + padTotal;
        const exclusionStart = (size - exclusionSize) / 2;
        const exclusionEnd = exclusionStart + exclusionSize;
        const useFill = this.resolveModuleFill(ctx, size);
        ctx.fillStyle = useFill;
        for (let row = 0; row < moduleCount; row++) {
            for (let col = 0; col < moduleCount; col++) {
                if (matrix[row][col] !== 1)
                    continue;
                const x = col * moduleSize;
                const y = row * moduleSize;
                if (this._loadedImage) {
                    const modRight = x + moduleSize;
                    const modBottom = y + moduleSize;
                    if (x >= exclusionStart && modRight <= exclusionEnd &&
                        y >= exclusionStart && modBottom <= exclusionEnd) {
                        continue;
                    }
                }
                if (clampedRadius > 0) {
                    this.drawRoundedModule(ctx, x, y, moduleSize, moduleSize, clampedRadius * moduleSize);
                }
                else {
                    ctx.fillRect(x, y, moduleSize, moduleSize);
                }
            }
        }
        if (this._loadedImage) {
            this.drawCenterImage(ctx, size, imgPixelSize);
        }
    }
    /**
     * Returns the fill style for QR modules — either a gradient or the flat fill color.
     */
    resolveModuleFill(ctx, size) {
        if (!this.fillGradient)
            return this.fill;
        const parts = this.fillGradient.split(',').map(s => s.trim());
        if (parts.length < 3)
            return this.fill;
        const angleDeg = parseFloat(parts[0]);
        if (Number.isNaN(angleDeg))
            return this.fill;
        const angleRad = (angleDeg * Math.PI) / 180;
        const cx = size / 2;
        const cy = size / 2;
        const len = size / 2;
        const x0 = cx - Math.cos(angleRad) * len;
        const y0 = cy - Math.sin(angleRad) * len;
        const x1 = cx + Math.cos(angleRad) * len;
        const y1 = cy + Math.sin(angleRad) * len;
        const gradient = ctx.createLinearGradient(x0, y0, x1, y1);
        const colors = parts.slice(1);
        for (let i = 0; i < colors.length; i++) {
            gradient.addColorStop(i / (colors.length - 1), colors[i]);
        }
        return gradient;
    }
    drawCenterImage(ctx, size, imgPixelSize) {
        if (!this._loadedImage)
            return;
        const x = (size - imgPixelSize) / 2;
        const y = (size - imgPixelSize) / 2;
        const r = Math.min(this.imageRadius, imgPixelSize / 2);
        const pad = this.imagePadding;
        if (pad > 0 && this.background && this.background !== 'transparent') {
            ctx.fillStyle = this.background;
            const bx = x - pad;
            const by = y - pad;
            const bw = imgPixelSize + pad * 2;
            const bh = imgPixelSize + pad * 2;
            const br = r + pad * 0.5;
            this.drawRoundedRect(ctx, bx, by, bw, bh, br);
            ctx.fill();
        }
        ctx.save();
        this.clipRoundedRect(ctx, x, y, imgPixelSize, imgPixelSize, r);
        ctx.drawImage(this._loadedImage, x, y, imgPixelSize, imgPixelSize);
        ctx.restore();
    }
    drawRoundedModule(ctx, x, y, w, h, r) {
        ctx.beginPath();
        ctx.moveTo(x + r, y);
        ctx.lineTo(x + w - r, y);
        ctx.arcTo(x + w, y, x + w, y + r, r);
        ctx.lineTo(x + w, y + h - r);
        ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
        ctx.lineTo(x + r, y + h);
        ctx.arcTo(x, y + h, x, y + h - r, r);
        ctx.lineTo(x, y + r);
        ctx.arcTo(x, y, x + r, y, r);
        ctx.closePath();
        ctx.fill();
    }
    drawRoundedRect(ctx, x, y, w, h, r) {
        ctx.beginPath();
        ctx.moveTo(x + r, y);
        ctx.lineTo(x + w - r, y);
        ctx.arcTo(x + w, y, x + w, y + r, r);
        ctx.lineTo(x + w, y + h - r);
        ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
        ctx.lineTo(x + r, y + h);
        ctx.arcTo(x, y + h, x, y + h - r, r);
        ctx.lineTo(x, y + r);
        ctx.arcTo(x, y, x + r, y, r);
        ctx.closePath();
    }
    clipRoundedRect(ctx, x, y, w, h, r) {
        ctx.beginPath();
        ctx.moveTo(x + r, y);
        ctx.lineTo(x + w - r, y);
        ctx.arcTo(x + w, y, x + w, y + r, r);
        ctx.lineTo(x + w, y + h - r);
        ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
        ctx.lineTo(x + r, y + h);
        ctx.arcTo(x, y + h, x, y + h - r, r);
        ctx.lineTo(x, y + r);
        ctx.arcTo(x, y, x + r, y, r);
        ctx.closePath();
        ctx.clip();
    }
};
__decorate([
    property({ type: String, reflect: true })
], NileQrCode.prototype, "value", void 0);
__decorate([
    property({ type: Number, reflect: true })
], NileQrCode.prototype, "size", void 0);
__decorate([
    property({ type: String, reflect: true })
], NileQrCode.prototype, "fill", void 0);
__decorate([
    property({ type: String, reflect: true })
], NileQrCode.prototype, "background", void 0);
__decorate([
    property({ type: Number, reflect: true })
], NileQrCode.prototype, "radius", void 0);
__decorate([
    property({ attribute: 'error-correction', reflect: true })
], NileQrCode.prototype, "errorCorrection", void 0);
__decorate([
    property({ type: String, reflect: true })
], NileQrCode.prototype, "label", void 0);
__decorate([
    property({ type: String, reflect: true })
], NileQrCode.prototype, "image", void 0);
__decorate([
    property({ attribute: 'image-size', type: Number, reflect: true })
], NileQrCode.prototype, "imageSize", void 0);
__decorate([
    property({ attribute: 'image-padding', type: Number, reflect: true })
], NileQrCode.prototype, "imagePadding", void 0);
__decorate([
    property({ attribute: 'image-radius', type: Number, reflect: true })
], NileQrCode.prototype, "imageRadius", void 0);
__decorate([
    property({ attribute: 'fill-gradient', type: String, reflect: true })
], NileQrCode.prototype, "fillGradient", void 0);
__decorate([
    query('canvas')
], NileQrCode.prototype, "canvas", void 0);
NileQrCode = __decorate([
    customElement('nile-qr-code')
], NileQrCode);
export { NileQrCode };
export default NileQrCode;
//# sourceMappingURL=nile-qr-code.js.map