import { h, Host, } from "@stencil/core";
import { isTypeAccepted } from "../../util/files";
import { getIconName } from "../icon/get-icon-props";
import translate from "../../global/translations";
import { createRandomString } from "../../util/random-string";
import { resizeImage } from "../../util/image-resize";
/**
 * This component displays a profile picture, while allowing the user
 * to change it via a file input or drag-and-drop.
 *
 * It supports client-side image resizing and conversion,
 * as well as a simple lazy-loading mechanism.
 *
 * @exampleComponent limel-example-profile-picture-basic
 * @exampleComponent limel-example-profile-picture-helper-text
 * @exampleComponent limel-example-profile-picture-icon
 * @exampleComponent limel-example-profile-picture-with-value
 * @exampleComponent limel-example-profile-picture-loading
 * @exampleComponent limel-example-profile-picture-image-fit
 * @exampleComponent limel-example-profile-picture-composite
 * @exampleComponent limel-example-profile-picture-resize-contain
 * @exampleComponent limel-example-profile-picture-resize-cover
 * @exampleComponent limel-example-profile-picture-resize-fallback
 * @exampleComponent limel-example-profile-picture-styling
 * @beta
 */
export class ProfilePicture {
    constructor() {
        /**
         * Defines the language for translations.
         * Will translate the translatable strings on the components.
         */
        this.language = 'en';
        /**
         * Placeholder icon of the component, displayed when no image is present.
         */
        this.icon = 'user';
        /**
         * Disables user interaction.
         * Prevents uploading new pictures or removing existing ones.
         */
        this.disabled = false;
        /**
         * Readonly prevents changing the value but allows interaction like focus.
         */
        this.readonly = false;
        /**
         * Marks the control as required.
         */
        this.required = false;
        /**
         * Marks the control as invalid.
         */
        this.invalid = false;
        /**
         * Set to `true` to put the component in the `loading` state,
         * and render an indeterminate progress indicator inside.
         * This does _not_ disable the interactivity of the component!
         */
        this.loading = false;
        /**
         * How the image should fit within the container.
         * - `cover` will fill the container and crop excess parts.
         * - `contain` will scale the image to fit within the container without cropping.
         */
        this.imageFit = 'cover';
        /**
         * A comma-separated list of accepted file types.
         */
        this.accept = 'image/jpeg,image/png,image/heic,.jpg,.jpeg,.png,.heic';
        this.imageError = false;
        this.isErrorMessagePopoverOpen = false;
        this.removeButtonId = createRandomString();
        this.browseButtonId = createRandomString();
        this.renderHelperText = () => {
            if (!this.helperText) {
                return;
            }
            return (h("limel-tooltip", { elementId: this.browseButtonId, label: this.helperText }));
        };
        this.handleNewFiles = async (event) => {
            var _a, _b;
            event.stopPropagation();
            if (this.disabled) {
                return;
            }
            const file = (_a = event.detail) === null || _a === void 0 ? void 0 : _a[0];
            if (!file) {
                return;
            }
            if (!isTypeAccepted(file, this.accept)) {
                this.filesRejected.emit([file]);
                return;
            }
            this.revokeObjectUrl();
            this.imageError = false;
            let out = file;
            // Optional client-side resize
            if (this.resize && file.fileContent instanceof File) {
                try {
                    const processed = await resizeImage(file.fileContent, Object.assign(Object.assign({}, this.resize), { fit: (_b = this.resize.fit) !== null && _b !== void 0 ? _b : this.imageFit }));
                    out = Object.assign(Object.assign({}, file), { filename: processed.name, size: processed.size, contentType: processed.type, fileContent: processed });
                }
                catch (_c) {
                    // Fall back to original file if resize fails
                    out = file;
                }
            }
            // Create an object URL for immediate preview if no href present
            if (!out.href && out.fileContent instanceof File) {
                this.objectUrl = URL.createObjectURL(out.fileContent);
            }
            this.change.emit(out);
        };
        this.handleRejectedFiles = (event) => {
            event.stopPropagation();
            this.filesRejected.emit(event.detail);
        };
        this.handleClear = (event) => {
            event.stopPropagation();
            this.revokeObjectUrl();
            this.imageError = false;
            this.change.emit(undefined);
        };
        this.onImageError = () => {
            this.imageError = true;
        };
        this.openPopover = (event) => {
            event.stopPropagation();
            this.isErrorMessagePopoverOpen = true;
        };
        this.onPopoverClose = (event) => {
            event.stopPropagation();
            this.isErrorMessagePopoverOpen = false;
        };
        this.getTranslation = (key) => {
            return translate.get(key, this.language);
        };
    }
    disconnectedCallback() {
        this.revokeObjectUrl();
    }
    handleValueChange() {
        // Clear previously created object URL when value changes
        this.revokeObjectUrl();
        this.imageError = false;
        // If a new File without href is provided, create an object URL for preview
        const currentValue = this.value;
        if (currentValue &&
            typeof currentValue !== 'string' &&
            !currentValue.href &&
            currentValue.fileContent instanceof File) {
            this.objectUrl = URL.createObjectURL(currentValue.fileContent);
        }
    }
    render() {
        const hostClassNames = {
            'has-image-error': this.imageError,
        };
        if (this.readonly) {
            return h(Host, { class: hostClassNames }, this.renderAvatar());
        }
        return (h(Host, { class: hostClassNames }, h("limel-file-dropzone", { disabled: this.disabled, accept: this.accept, onFilesSelected: this.handleNewFiles, onFilesRejected: this.handleRejectedFiles }, h("limel-file-input", { accept: this.accept, disabled: this.disabled, "aria-required": this.required ? 'true' : undefined, "aria-invalid": this.invalid ? 'true' : undefined }, this.renderBrowseButton())), this.renderClearButton(), this.renderSpinner(), this.renderErrorMessage(), this.renderHelperText()));
    }
    get hasValue() {
        if (typeof this.value === 'string') {
            return !!this.value;
        }
        if (this.value && (this.value.href || this.value.fileContent)) {
            return true;
        }
        return !!this.objectUrl;
    }
    renderBrowseButton() {
        return (h("button", { id: this.browseButtonId, type: "button", class: "avatar", disabled: this.disabled, "aria-label": this.label, "aria-busy": this.loading ? 'true' : 'false', "aria-live": "polite" }, this.renderAvatar()));
    }
    renderAvatar() {
        const src = this.getImageSrc();
        if (src) {
            return (h("img", { src: src, alt: "", style: {
                    '--limel-profile-picture-object-fit': this.imageFit,
                }, loading: "lazy", onError: this.onImageError }));
        }
        return this.renderIcon();
    }
    renderIcon() {
        var _a, _b;
        const icon = getIconName(this.icon);
        return (h("limel-icon", { name: icon, style: {
                color: `${(_a = this.icon) === null || _a === void 0 ? void 0 : _a.color}`,
                'background-color': `${(_b = this.icon) === null || _b === void 0 ? void 0 : _b.backgroundColor}`,
            } }));
    }
    renderClearButton() {
        if (!this.hasValue || this.disabled) {
            return;
        }
        return [
            h("button", { class: "remove", type: "button", id: this.removeButtonId, onClick: this.handleClear }),
            h("limel-tooltip", { label: this.getTranslation('profile-picture.remove'), elementId: this.removeButtonId }),
        ];
    }
    renderSpinner() {
        if (!this.loading) {
            return;
        }
        return h("limel-spinner", null);
    }
    // Collects derived flags used for deciding whether to show the unsupported preview message
    getUnsupportedPreviewContext() {
        const currentValue = this.value;
        const hasNoSrc = !this.getImageSrc();
        const hasLocalFile = !!(currentValue &&
            typeof currentValue !== 'string' &&
            currentValue.fileContent instanceof File &&
            !currentValue.href);
        const isResizeConfigured = !!this.resize;
        return { hasNoSrc, hasLocalFile, isResizeConfigured };
    }
    shouldShowErrorMessage() {
        const { hasNoSrc, hasLocalFile, isResizeConfigured } = this.getUnsupportedPreviewContext();
        return ((hasNoSrc || this.imageError) && hasLocalFile && isResizeConfigured);
    }
    // Shows a non-intrusive note when there is a File without href and no object URL, which
    // can happen if the browser failed to decode the source (e.g., HEIC in Chromium).
    renderErrorMessage() {
        if (!this.shouldShowErrorMessage()) {
            return;
        }
        const errorIcon = {
            name: 'error',
            color: 'rgb(var(--color-orange-dark))',
        };
        const errorMessageStyles = {
            maxWidth: '20rem',
            borderRadius: '0.75rem',
        };
        return (h("limel-popover", { open: this.isErrorMessagePopoverOpen, onClick: this.openPopover, onClose: this.onPopoverClose }, h("limel-icon-button", { slot: "trigger", elevated: true, icon: errorIcon, "aria-live": "polite", label: this.getTranslation('profile-picture.unsupported-preview.title') }), h("limel-callout", { type: "warning", style: errorMessageStyles, heading: this.getTranslation('profile-picture.unsupported-preview.title') }, this.getTranslation('profile-picture.unsupported-preview.description'))));
    }
    getImageSrc() {
        if (!this.value) {
            return this.objectUrl; // Could be set from last selection before parent consumes
        }
        if (typeof this.value === 'string') {
            return this.value;
        }
        if (this.value.href) {
            return this.value.href;
        }
        if (this.value.fileContent instanceof File) {
            return this.objectUrl;
        }
        return undefined;
    }
    revokeObjectUrl() {
        if (this.objectUrl) {
            URL.revokeObjectURL(this.objectUrl);
            this.objectUrl = undefined;
        }
    }
    static get is() { return "limel-profile-picture"; }
    static get encapsulation() { return "shadow"; }
    static get originalStyleUrls() {
        return {
            "$": ["profile-picture.scss"]
        };
    }
    static get styleUrls() {
        return {
            "$": ["profile-picture.css"]
        };
    }
    static get properties() {
        return {
            "language": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "Languages",
                    "resolved": "\"da\" | \"de\" | \"en\" | \"fi\" | \"fr\" | \"nb\" | \"nl\" | \"no\" | \"sv\"",
                    "references": {
                        "Languages": {
                            "location": "import",
                            "path": "../date-picker/date.types",
                            "id": "src/components/date-picker/date.types.ts::Languages",
                            "referenceLocation": "Languages"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Defines the language for translations.\nWill translate the translatable strings on the components."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "language",
                "defaultValue": "'en'"
            },
            "label": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Accessible label for the the browse button."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "label"
            },
            "icon": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string | Icon",
                    "resolved": "Icon | string",
                    "references": {
                        "Icon": {
                            "location": "import",
                            "path": "../../global/shared-types/icon.types",
                            "id": "src/global/shared-types/icon.types.ts::Icon",
                            "referenceLocation": "Icon"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Placeholder icon of the component, displayed when no image is present."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "icon",
                "defaultValue": "'user'"
            },
            "helperText": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "Helper text shown as a tooltip on hover or focus."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "helper-text"
            },
            "disabled": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Disables user interaction.\nPrevents uploading new pictures or removing existing ones."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "disabled",
                "defaultValue": "false"
            },
            "readonly": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Readonly prevents changing the value but allows interaction like focus."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "readonly",
                "defaultValue": "false"
            },
            "required": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Marks the control as required."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "required",
                "defaultValue": "false"
            },
            "invalid": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Marks the control as invalid."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "invalid",
                "defaultValue": "false"
            },
            "loading": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Set to `true` to put the component in the `loading` state,\nand render an indeterminate progress indicator inside.\nThis does _not_ disable the interactivity of the component!"
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "loading",
                "defaultValue": "false"
            },
            "value": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string | FileInfo",
                    "resolved": "FileInfo | string",
                    "references": {
                        "FileInfo": {
                            "location": "import",
                            "path": "../../global/shared-types/file.types",
                            "id": "src/global/shared-types/file.types.ts::FileInfo",
                            "referenceLocation": "FileInfo"
                        }
                    }
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "Current image to display. Either a URL string or a `FileInfo` with an href."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "value"
            },
            "imageFit": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "'cover' | 'contain'",
                    "resolved": "\"contain\" | \"cover\"",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "How the image should fit within the container.\n- `cover` will fill the container and crop excess parts.\n- `contain` will scale the image to fit within the container without cropping."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "image-fit",
                "defaultValue": "'cover'"
            },
            "accept": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "A comma-separated list of accepted file types."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "accept",
                "defaultValue": "'image/jpeg,image/png,image/heic,.jpg,.jpeg,.png,.heic'"
            },
            "resize": {
                "type": "unknown",
                "mutable": false,
                "complexType": {
                    "original": "ResizeOptions",
                    "resolved": "{ width: number; height: number; fit?: \"cover\" | \"contain\"; type?: \"image/jpeg\" | \"image/png\"; quality?: number; rename?: (originalName: string) => string; }",
                    "references": {
                        "ResizeOptions": {
                            "location": "import",
                            "path": "../../util/image-resize",
                            "id": "src/util/image-resize.ts::ResizeOptions",
                            "referenceLocation": "ResizeOptions"
                        }
                    }
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "Optional client-side resize before emitting the file.\nIf provided, the selected image will be resized on the client device.\n:::note\nHEIC may not decode in all browsers; when decoding fails, the original\nfile will be emitted. See the examples for more info.\n:::"
                },
                "getter": false,
                "setter": false
            }
        };
    }
    static get states() {
        return {
            "objectUrl": {},
            "imageError": {},
            "isErrorMessagePopoverOpen": {}
        };
    }
    static get events() {
        return [{
                "method": "change",
                "name": "change",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when the picture changes (first FileInfo only)."
                },
                "complexType": {
                    "original": "FileInfo | undefined",
                    "resolved": "FileInfo",
                    "references": {
                        "FileInfo": {
                            "location": "import",
                            "path": "../../global/shared-types/file.types",
                            "id": "src/global/shared-types/file.types.ts::FileInfo",
                            "referenceLocation": "FileInfo"
                        }
                    }
                }
            }, {
                "method": "filesRejected",
                "name": "filesRejected",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when a file is rejected by accept filter."
                },
                "complexType": {
                    "original": "FileInfo[]",
                    "resolved": "FileInfo[]",
                    "references": {
                        "FileInfo": {
                            "location": "import",
                            "path": "../../global/shared-types/file.types",
                            "id": "src/global/shared-types/file.types.ts::FileInfo",
                            "referenceLocation": "FileInfo"
                        }
                    }
                }
            }];
    }
    static get watchers() {
        return [{
                "propName": "value",
                "methodName": "handleValueChange"
            }];
    }
}
