/*!
 * Copyright (c) what3words http://what3words.com
 */
import { DEFAULTS, W3W_REGEX, W3W_TEXTAREA_PROPERTIES, W3W_TEXTAREA_REGEX, } from "../../lib/constants";
import { sdk, what3wordsClients } from "../../lib/sdk";
import { resolveLanguageCode, t } from "../../lib/translation";
import { convertToCoordinates, isEmpty, parseCoordinates, } from "../../lib/utils";
import { h, Host, } from "@stencil/core";
import { fetchTransport } from "@what3words/api";
import { version } from "../../../package.json";
import { Status } from "./components/status";
import { Suggestions } from "./components/suggestions";
import { Tooltip } from "./components/tooltip";
const tag = "what3words-notes";
/**
 * @slot input - The textarea or input element to get suggestions from when typing
 * ```
 * <what3words-notes api-key="YOUR_API_KEY">
 *  <textarea slot="input"></textarea>
 *  <!-- OR -->
 *  <input slot="input" type="text" />
 * </what3words-notes>
 * ```
 * @slot label - The label element for the input slot element
 * ```
 * <what3words-notes api-key="YOUR_API_KEY">
 *  <label slot="label" for="delivery-notes">Delivery Notes/label>
 *  <textarea slot="input" name="delivery-notes"></textarea>
 * </what3words-notes>
 * ```
 * @slot tooltip - The tooltip content to display when the status icon is clicked
 * ```
 * <what3words-notes api-key="YOUR_API_KEY">
 *  <textarea slot="input"></textarea>
 *  <div slot="tooltip">
 *    <h1>Custom title</h1>
 *    <p>custom content</p>
 *  </div>
 * </what3words-notes>
 * ```
 */
export class What3wordsNotes {
    constructor() {
        var _a;
        /**
         * The format to display the what3words address in
         * - `slashes` - e.g. `///filled.count.soap` (default)
         * - `url` - e.g. `https://w3w.co/filled.count.soap`
         * @default slashes
         */
        this.addressFormat = "slashes";
        /**
         * Show the what3words hints tooltip when clicking on the what3words status icon
         * @default true
         */
        this.showHintsTooltip = true;
        /**
         * The callback function to execute when the component is loaded
         */
        this.callback = "";
        /**
         * The what3words API key to use for the what3words API requests
         */
        this.apiKey = "";
        /**
         * The headers to include in the what3words API requests
         */
        this.headers = "{}";
        /**
         * The base URL for the what3words API requests
         */
        this.baseUrl =
            // TODO: _config is a protected property, new access method should be created to reflect this value reference
            (_a = sdk.api.clients.autosuggest["_config"].host) !== null && _a !== void 0 ? _a : DEFAULTS.base_url;
        /**
         * The API version to use for the what3words API requests
         * @default "v3"
         */
        this.apiVersion = "v3";
        /**
         * The delay in milliseconds to wait after the user has finished typing before making an autosuggest request
         * @default 300
         */
        this.typeaheadDelay = 300;
        /**
         * The language to return the results in
         * @example "en"
         */
        this.language = "";
        /**
         * The number of results to return
         * @default 3
         */
        this.nFocusResults = 3;
        /**
         * Clip the results to a specific country
         * @example "GB"
         */
        this.clipToCountry = "";
        /**
         * Clip the results to a specific bounding box
         * @example "51.521,-0.205,51.576,-0.105"
         */
        this.clipToBoundingBox = "";
        /**
         * Clip the results to a specific circle
         * @example "51.521,-0.205,1000"
         */
        this.clipToCircle = "";
        /**
         * Clip the results to a specific polygon
         * @example "51.521,-0.205,51.576,-0.105,51.576,-0.205"
         */
        this.clipToPolygon = "";
        // Internal component state
        this.value = "";
        this.suggestions = [];
        this.selectedSuggestions = [];
        this.updatingSelectedSuggestions = false;
        this.loading = false;
        this.hoverIndex = -1;
        this.tooltipOpen = false;
        this.onSuggestionSelected = (suggestion) => () => {
            this.suggestionSelected.emit({ suggestion });
        };
        this.onMouseOver = (suggestion) => () => {
            var _a;
            const previousSuggestion = (_a = this.suggestions[this.hoverIndex]) !== null && _a !== void 0 ? _a : null;
            if (!previousSuggestion || previousSuggestion.words !== suggestion.words) {
                this.suggestionHover.emit({ suggestion });
            }
        };
        this.onMouseOut = () => {
            this.setHoverIndex(-1);
        };
    }
    /**
     * Watches for changes to the apiKey property
     * @param key The api key
     */
    setApiKey(key) {
        Object.values(this.clients).forEach((client) => client.apiKey(key));
    }
    /**
     * Watches for changes to the baseUrl property
     * @param base_url The base url
     */
    setBaseUrl(host) {
        Object.values(this.clients).forEach((client) => client.config({ host }));
    }
    /**
     * Watches for changes to the apiVersion property
     * @param apiVersion The version
     */
    setApiVersion(api_version) {
        Object.values(this.clients).forEach((client) => client.config({ apiVersion: api_version }));
    }
    /**
     * Watches for changes to the headers property
     * @param value The headers value
     */
    setHeaders(value) {
        const componentHeaders = this.getComponentHeaders();
        const injectedHeaders = (typeof value === "string" ? JSON.parse(value) : value);
        Object.values(this.clients).forEach((client) => client.config({
            headers: Object.assign(Object.assign({}, injectedHeaders), componentHeaders),
        }));
    }
    /**
     * Watches for changes to the loading state
     */
    async handleLoadingChange(loading) {
        if (loading && !this.suggestions.length) {
            await this.showSuggestions();
        }
    }
    /**
     * Watches for changes to the suggestions state
     */
    async handleSuggestionsChange(newSuggestions, suggestions) {
        if (newSuggestions.length !== suggestions.length &&
            newSuggestions.length === 0) {
            await this.hideSuggestions();
        }
    }
    getComponentHeaders() {
        const meta = JSON.stringify({
            typeahead_delay: this.typeaheadDelay,
            component_session_id: window.what3words_session_id,
            slot: this.getInputSlotElement().tagName.toLowerCase(),
            origin: window.location.origin,
        });
        return {
            "X-W3W-AS-Component": `what3words-Notes-JS/${version} (${meta})`,
        };
    }
    getRequestOptions(input) {
        const options = {
            input,
            language: this.language,
        };
        // validates the language
        options.language = resolveLanguageCode(this.language);
        if (this.searchFocus) {
            const [lat, lng] = this.searchFocus.split(",");
            if (lat && lng)
                options.focus = parseCoordinates(lat, lng);
        }
        if (!isEmpty(this.nFocusResults.toString())) {
            options.nFocusResults = this.nFocusResults;
        }
        if (!isEmpty(this.clipToCountry)) {
            options.clipToCountry = this.clipToCountry.split(",");
        }
        if (!isEmpty(this.clipToBoundingBox)) {
            const [southLat, westLng, northLat, eastLng] = this.clipToBoundingBox.split(",");
            if (northLat && eastLng && southLat && westLng)
                options.clipToBoundingBox = {
                    northeast: parseCoordinates(northLat, eastLng),
                    southwest: parseCoordinates(southLat, westLng),
                };
        }
        if (!isEmpty(this.clipToCircle)) {
            const [lat, lng, radius] = this.clipToCircle.split(",");
            if (lat && lng && radius)
                options.clipToCircle = {
                    center: parseCoordinates(lat, lng),
                    radius: parseFloat(radius),
                };
        }
        if (!isEmpty(this.clipToPolygon)) {
            options.clipToPolygon = convertToCoordinates(this.clipToPolygon);
        }
        return options;
    }
    /**
     * @returns { HTMLTextAreaElement | HTMLInputElement }
     */
    getInputSlotElement() {
        const inputSlot = this.el.querySelector("[slot=input]");
        const isTextArea = inputSlot instanceof HTMLTextAreaElement;
        const isInput = inputSlot instanceof HTMLInputElement;
        if (!isTextArea && !isInput) {
            throw new Error("[slot=input] must be an instance of HTMLTextAreaElement or HTMLInputElement");
        }
        return inputSlot;
    }
    /**
     * Formats a what3words address based on the specified format.
     *
     * @param value - The what3words address to format.
     * @param format - The format to use for the address. Possible values are 'url' and 'slashes'.
     * @returns The formatted what3words address.
     */
    formatW3wAddress(value, format) {
        if (format === "url") {
            return `https://w3w.co/${value.replace("///", "")}`;
        }
        return value;
    }
    /**
     * Look for a what3words address at the current cursor position in the input element and replace it with the selected suggestion
     *
     * @param { string } value The value to set
     */
    setInputValue(value) {
        const inputSlot = this.getInputSlotElement();
        const { w3w, start, end } = this.findW3wAtCursorPosition(inputSlot);
        if (w3w) {
            const formattedW3w = this.formatW3wAddress(value, this.addressFormat);
            const inputValue = inputSlot.value;
            const textBeforeW3w = inputValue.substring(0, start);
            const textAfterW3w = inputValue.substring(end, inputValue.length);
            const newVal = textBeforeW3w + formattedW3w + textAfterW3w;
            const newPos = (textBeforeW3w + formattedW3w).length;
            inputSlot.value = newVal;
            inputSlot.focus();
            inputSlot.selectionEnd = newPos;
            // remove from the suggestions list if text to replace exists
            const textToReplaceIndex = this.selectedSuggestions.indexOf(w3w);
            if (textToReplaceIndex !== -1)
                this.selectedSuggestions.splice(textToReplaceIndex, 1);
            const newSuggestions = [...this.selectedSuggestions, value];
            this.setSelectedSuggestions(newSuggestions, newSuggestions.length > 2);
        }
    }
    /**
     * Gets/sets the value state for the input element
     * @param { string } value The value to set
     */
    setValue(value) {
        this.value = value;
    }
    /**
     * Get/set the autosuggestions state value
     * @param { AutosuggestOption[] } suggestions The suggestions to set
     */
    setSuggestions(suggestions) {
        this.suggestions = suggestions;
    }
    /**
     * Sets selected w3w addresses
     * @param suggestions new list of selected w3w address
     * @param delayedUpdate simulates loading effect
     * @returns
     */
    setSelectedSuggestions(suggestions, delayedUpdate = false) {
        if (!delayedUpdate) {
            this.selectedSuggestions = suggestions;
            return;
        }
        this.updatingSelectedSuggestions = true;
        setTimeout(() => {
            this.selectedSuggestions = suggestions;
            this.updatingSelectedSuggestions = false;
        }, 500);
    }
    /**
     * Sets the loading state value
     * @param { boolean } loading The state to set
     */
    setLoading(loading) {
        this.loading = loading;
    }
    /**
     * Animation to show the suggestions list based on the the available space above the input
     */
    async showSuggestions() {
        const suggestions = this.el.querySelector(`.${tag}-suggestions`);
        if (!suggestions)
            return;
        const suggestionsHeight = suggestions.getBoundingClientRect().height;
        const hostBoundingRectTop = this.el.getBoundingClientRect().top;
        const fromTransform = hostBoundingRectTop > suggestionsHeight
            ? "translateY(10px)"
            : "translateY(-10px)";
        const keyframes = [
            { zIndex: -1, opacity: 0, transform: fromTransform },
            { zIndex: 100, opacity: 1, transform: "translateY(0)" },
        ];
        const options = {
            duration: 150,
            easing: "ease-in-out",
            fill: "forwards",
        };
        return suggestions.animate(keyframes, options).finished;
    }
    /**
     * Animation to hide the suggestions list based on the the available space above the input
     */
    async hideSuggestions() {
        const suggestions = this.el.querySelector(`.${tag}-suggestions`);
        if (!suggestions)
            return;
        const suggestionsHeight = suggestions.getBoundingClientRect().height;
        const hostBoundingRectTop = this.el.getBoundingClientRect().top;
        const toTransform = hostBoundingRectTop > suggestionsHeight
            ? "translateY(10px)"
            : "translateY(-10px)";
        const keyframes = [{ zIndex: -1, opacity: 0, transform: toTransform }];
        const options = {
            duration: 150,
            easing: "ease-in-out",
            fill: "forwards",
        };
        return suggestions.animate(keyframes, options).finished;
    }
    /**
     * Animation to show the tooltip based on the the available space above the input
     */
    async openTooltip() {
        const tooltip = this.el.querySelector(`.${tag}-tooltip`);
        if (!tooltip)
            return;
        const tooltipHeight = tooltip.getBoundingClientRect().height;
        const hostBoundingRectTop = this.el.getBoundingClientRect().top;
        const fromTransform = hostBoundingRectTop > tooltipHeight
            ? "translateY(10px)"
            : "translateY(-10px)";
        const top = hostBoundingRectTop > tooltipHeight ? "unset" : "2rem";
        const bottom = hostBoundingRectTop > tooltipHeight ? "calc(100% + 0.5rem)" : "unset";
        await tooltip.animate([
            {
                transform: fromTransform,
                opacity: 0,
                zIndex: -1,
                top,
                bottom,
            },
            {
                transform: "translateY(0)",
                opacity: 1,
                zIndex: 100,
                top,
                bottom,
            },
        ], {
            duration: 150,
            easing: "ease-in-out",
            fill: "forwards",
        }).finished;
        this.tooltipOpen = true;
    }
    /**
     * Animation to hide the tooltip based on the the available space above the input
     */
    async closeTooltip() {
        const tooltip = this.el.querySelector(`.${tag}-tooltip`);
        if (!tooltip)
            return;
        const tooltipHeight = tooltip.getBoundingClientRect().height;
        const hostBoundingRectTop = this.el.getBoundingClientRect().top;
        const toTransform = hostBoundingRectTop > tooltipHeight
            ? "translateY(10px)"
            : "translateY(-10px)";
        await tooltip.animate([
            {
                zIndex: -1,
                opacity: 0,
                transform: toTransform,
            },
        ], {
            duration: 150,
            easing: "ease-in-out",
            fill: "forwards",
        }).finished;
        this.tooltipOpen = false;
    }
    /**
     * Gets/sets the hover index value
     * @param { number } hoverIndex The hover index value to set
     */
    setHoverIndex(hoverIndex) {
        this.hoverIndex = hoverIndex;
    }
    /**
     * Emits the could not retrieve co-ordinates error
     */
    emitFatalError() {
        this.apiError.emit({
            error: new Error(t("error_message")),
        });
    }
    clearApiError() {
        this.apiError.emit({ error: null });
    }
    /**
     * Asynchronously retrieves autosuggestion results for a given search string and sets the results against the
     * component state. This method is idempotent and has a timeout delay to throttle requests, waiting for the user to
     * stop typing.
     * @param value The search string to retrieve autosuggestions for.
     */
    getAutosuggestions(value) {
        clearTimeout(this.apiRequestTimeout);
        const autosuggestClient = this.clients.autosuggest;
        const options = this.getRequestOptions(value);
        return autosuggestClient.run(options);
    }
    isValid3wa(value = "") {
        return W3W_REGEX.test(value);
    }
    extract3wa(value = "") {
        var _a;
        const [, words] = (_a = W3W_REGEX.exec(value)) !== null && _a !== void 0 ? _a : ["", ""];
        return words;
    }
    /**
     * Ensures that the what3words found on the input slot
     * Matches against the selectedSuggestions state
     */
    matchSelected3waFromInputSlot() {
        var _a;
        const inputSlot = this.getInputSlotElement();
        const w3wFromInput = inputSlot.value.trim().length === 0
            ? []
            : ((_a = inputSlot.value.match(W3W_TEXTAREA_REGEX)) !== null && _a !== void 0 ? _a : []);
        const fromCurrentSuggestions = (suggestion, index) => suggestion === this.selectedSuggestions[index] ||
            this.selectedSuggestions.includes(suggestion);
        const selectedSuggestionsHasChanged = w3wFromInput.filter(fromCurrentSuggestions).length !==
            this.selectedSuggestions.length;
        if (selectedSuggestionsHasChanged) {
            const newSelected3wa = w3wFromInput.filter(fromCurrentSuggestions);
            this.setSelectedSuggestions(newSelected3wa);
        }
    }
    handleValueChanged({ detail: { value = "" } }) {
        const valid = this.isValid3wa(value);
        this.matchSelected3waFromInputSlot();
        if (valid) {
            const words = this.extract3wa(value);
            this.setValue(words);
            this.valueValid.emit({
                value: DEFAULTS.prefix + words,
            });
        }
        else {
            if (this.apiRequestTimeout)
                clearTimeout(this.apiRequestTimeout);
            this.setValue(value);
            this.valueInvalid.emit({ value: value });
        }
    }
    handleValueValid({ detail: { value = "" } }) {
        clearTimeout(this.apiRequestTimeout);
        this.clearApiError();
        this.setLoading(true);
        this.apiRequestTimeout = setTimeout(() => {
            this.getAutosuggestions(value)
                .then(({ suggestions }) => {
                this.suggestionsChanged.emit({ suggestions });
            })
                .catch(() => {
                this.setSuggestions([]);
                this.emitFatalError();
            })
                .finally(() => {
                this.setLoading(false);
            });
        }, this.typeaheadDelay);
    }
    handleValueInvalid() {
        clearTimeout(this.apiRequestTimeout);
        this.setLoading(false);
        this.setSuggestions([]);
    }
    handleSuggestionsChanged({ detail: { suggestions = [] }, }) {
        this.setSuggestions(suggestions);
        this.setHoverIndex(-1);
    }
    async handleSuggestionSelected({ detail: { suggestion }, }) {
        if (!suggestion)
            return;
        await this.hideSuggestions();
        this.setInputValue(DEFAULTS.prefix + suggestion.words);
        this.setValue(suggestion.words);
        this.setSuggestions([]);
        this.setHoverIndex(-1);
        this.clearApiError();
        await this.clients.autosuggest
            .onSelected(suggestion)
            .catch((error) => this.apiError.emit({ error }));
    }
    handleSuggestionHover({ detail: { suggestion }, }) {
        if (!suggestion)
            return;
        const suggestionsHoverIndex = this.suggestions.findIndex((s) => s.words === suggestion.words);
        if (suggestionsHoverIndex > -1) {
            this.setHoverIndex(suggestionsHoverIndex);
        }
    }
    async handleBlur() {
        await Promise.all([this.closeTooltip(), this.hideSuggestions()]);
        this.setSuggestions([]);
    }
    // Event listeners
    handleInput(e) {
        const isNotLineBreak = !["insertLineBreak", "insertParagraph"].includes(e.inputType);
        const isTextArea = e.target instanceof HTMLTextAreaElement;
        const isInput = e.target instanceof HTMLInputElement;
        if (isNotLineBreak && (isTextArea || isInput)) {
            const { w3w } = this.findW3wAtCursorPosition(e.target);
            this.valueChanged.emit({
                value: w3w,
            });
        }
    }
    handleKeyDown(e) {
        var _a;
        const keys = [
            "Escape",
            "ArrowLeft",
            "ArrowRight",
            "ArrowDown",
            "ArrowUp",
            "Enter",
        ];
        if (!keys.includes(e.key))
            return;
        const { suggestions, loading, value = "", hoverIndex, suggestionSelected, } = this;
        const suggestion = suggestions.find((s) => s.words === value.replace(/$\/*/, ""));
        const currentSuggestion = (_a = suggestions[hoverIndex]) !== null && _a !== void 0 ? _a : suggestion;
        switch (e.key) {
            case "Escape":
                this.getInputSlotElement().blur();
                this.setHoverIndex(-1);
                break;
            case "ArrowUp":
                if (suggestions.length > 0) {
                    e.preventDefault();
                    if (suggestions[hoverIndex - 1]) {
                        this.setHoverIndex(hoverIndex - 1);
                    }
                    else
                        this.setHoverIndex(suggestions.length - 1);
                }
                break;
            case "ArrowDown":
                if (suggestions.length > 0) {
                    e.preventDefault();
                    if (suggestions[hoverIndex + 1]) {
                        this.setHoverIndex(hoverIndex + 1);
                    }
                    else
                        this.setHoverIndex(0);
                }
                break;
            case "ArrowRight":
            case "ArrowLeft":
                // Disable arrow right and arrow left when suggestions are shown
                // Since the the calculation for the suggestions list style is dependent
                // on where the cursor position is located
                if (loading || suggestions.length > 0) {
                    e.preventDefault();
                }
                break;
            case "Enter":
                // Prevent line breaks in textarea and form submission when selecting a suggestion
                if (suggestions.length > 0) {
                    e.preventDefault();
                }
                if (currentSuggestion) {
                    suggestionSelected.emit({
                        suggestion: currentSuggestion,
                    });
                }
                this.setHoverIndex(-1);
                break;
        }
    }
    handleContextMenu(e) {
        // Dismiss suggestions if context menu is triggered
        if (e.target !== this.el.querySelector(`.${tag}-suggestions`)) {
            this.hideSuggestions()
                .then(() => this.setSuggestions([]))
                .catch(() => {
                // TODO: Emit error if action fails
            });
        }
    }
    handleWindowClick(e) {
        if (!this.el.contains(e.target) ||
            e.target === this.getInputSlotElement()) {
            this.closeTooltip().catch(() => {
                // TODO: Emit error if action fails
            });
        }
        // Dismiss suggestions if the target is outside of the suggestions popup
        if (e.target !== this.el.querySelector(`.${tag}-suggestions`)) {
            this.hideSuggestions()
                .then(() => this.setSuggestions([]))
                .catch(() => {
                // TODO: Emit error if action fails
            });
        }
    }
    async handleScroll() {
        if (!this.loading && this.suggestions.length > 0) {
            await Promise.all([this.closeTooltip(), this.hideSuggestions()]);
            this.setSuggestions([]);
        }
    }
    getSuggestionsStyle(inputSlot, labelContainer) {
        if (!inputSlot || !labelContainer)
            return {};
        const { top: inputRectTop } = inputSlot.getBoundingClientRect();
        const hasSpaceAboveInputForSuggestions = inputRectTop > 145;
        const labelHeight = labelContainer.getBoundingClientRect().height;
        const { left: cursorLeft, right: cursorRight, top: cursorTop, bottom: cursorBottom, } = this.getCursorPosition(inputSlot);
        const bottom = hasSpaceAboveInputForSuggestions
            ? `calc(100% - ${cursorTop}px - ${labelHeight}px + 4px)`
            : undefined;
        const top = hasSpaceAboveInputForSuggestions
            ? undefined
            : `calc(${cursorBottom + labelHeight}px + 4px)`;
        const left = cursorLeft ? `${cursorLeft}px` : undefined;
        const right = cursorRight ? `${cursorRight}px` : undefined;
        return {
            bottom,
            top,
            right,
            left,
        };
    }
    /**
     * Finds the what3words address at the current cursor position in the textarea element.
     */
    findW3wAtCursorPosition(inputSlot) {
        var _a, _b;
        const cursorPos = (_a = inputSlot.selectionStart) !== null && _a !== void 0 ? _a : 0;
        const inputValue = inputSlot.value;
        const w3wFromInput = inputValue.match(W3W_TEXTAREA_REGEX);
        if (!w3wFromInput)
            return { w3w: "", start: cursorPos, end: cursorPos };
        const w3w = (_b = w3wFromInput.find((m) => {
            const start = cursorPos - m.length;
            const end = start + m.length;
            const value = inputValue.substring(start, end);
            return value === m;
        })) !== null && _b !== void 0 ? _b : "";
        const start = cursorPos - w3w.length;
        const end = start + w3w.length;
        return { start, end, w3w };
    }
    connectedCallback() {
        // Configure service clients
        const componentHeaders = this.getComponentHeaders();
        const headers = Object.assign(Object.assign({}, componentHeaders), (typeof this.headers === "string"
            ? JSON.parse(this.headers)
            : this.headers));
        const config = {
            apiVersion: this.apiVersion,
            headers,
            host: this.baseUrl,
        };
        const transport = fetchTransport();
        if (!this.apiKey) {
            throw new Error("a What3words API key is required, you can get one at https://accounts.what3words.com/select-plan");
        }
        this.clients = {
            autosuggest: what3wordsClients.autosuggest(this.apiKey, config, transport),
            convertToCoordinates: what3wordsClients.convertToCoordinates(this.apiKey, config, transport),
        };
    }
    disconnectedCallback() {
        clearTimeout(this.apiRequestTimeout);
    }
    async componentWillLoad() {
        const { value: initial_value, callback } = this;
        const callbackFn = window[callback];
        if (typeof callbackFn === "function") {
            callbackFn();
        }
        if (W3W_REGEX.test(initial_value)) {
            const { suggestions } = await this.getAutosuggestions(initial_value);
            return this.suggestionsChanged.emit({ suggestions });
        }
    }
    /**
     * Calculates the cursor position by creating a mirror (div) element which copies all common properties from InputSlot.
     * The InputSlot's string content will be the div's textContent and an HTMLSpanElement will be appended at the end of the text.
     * The span element is what's used to determine where the cursor position is.
     * @param inputSlot
     * @returns
     */
    getCursorPosition(inputSlot) {
        var _a;
        const inputSlotMirror = document.createElement("div");
        const inputSlotStyle = getComputedStyle(inputSlot);
        // Copy the input slot style to the mirror element
        // This provides accuracy of the measurement get from the mirror element
        W3W_TEXTAREA_PROPERTIES.forEach((prop) => {
            inputSlotMirror.style[prop] = inputSlotStyle[prop];
        });
        inputSlotMirror.style.position = "absolute";
        inputSlotMirror.style.whiteSpace = "pre-wrap";
        inputSlotMirror.style.wordBreak = "break-word";
        inputSlotMirror.style.visibility = "hidden";
        const position = (_a = inputSlot.selectionStart) !== null && _a !== void 0 ? _a : 0;
        const normalizedText = inputSlot.value.normalize().substring(0, position);
        inputSlotMirror.textContent = normalizedText;
        document.body.appendChild(inputSlotMirror);
        const cursorSpan = document.createElement("span");
        inputSlotMirror.appendChild(cursorSpan);
        const { offsetTop, offsetLeft } = cursorSpan;
        document.body.removeChild(inputSlotMirror);
        const inputWidth = parseFloat(inputSlotStyle.width);
        const lineHeight = this.getLineHeight(inputSlot);
        const left = offsetLeft - inputSlot.scrollLeft;
        const right = inputWidth - offsetLeft - inputSlot.scrollLeft;
        const bottom = offsetTop + lineHeight - inputSlot.scrollTop;
        const top = bottom - lineHeight;
        const isRtl = inputSlotStyle.direction === "rtl";
        const result = {
            left: isRtl ? undefined : left,
            right: isRtl ? right : undefined,
            top,
            bottom,
        };
        return result;
    }
    getLineHeight(element) {
        const elementStyle = getComputedStyle(element);
        const lineHeight = elementStyle.lineHeight;
        if (["normal", "initial"].includes(lineHeight)) {
            return parseFloat(elementStyle.fontSize) * 1.2;
        }
        if (lineHeight === "inherit") {
            const parent = element.parentElement;
            if (parent) {
                return this.getLineHeight(parent);
            }
        }
        return parseFloat(lineHeight);
    }
    render() {
        const { onSuggestionSelected, onMouseOver, onMouseOut } = this;
        const value = this.value;
        const isLoading = this.loading;
        const inputSlot = this.getInputSlotElement();
        const selected3waCount = this.selectedSuggestions.length;
        const isLoadingSelected3waCount = this.updatingSelectedSuggestions;
        const hasSelectedSuggestions = this.selectedSuggestions.length > 0;
        const labelContainer = this.el.querySelector(`.${tag}-label`);
        const suggestionsStyle = this.getSuggestionsStyle(inputSlot, labelContainer);
        return (h(Host, { key: 'c47d39f62770df500368a64c57400c7ef2d56653', class: {
                [tag]: !!tag,
                loading: isLoading,
                valid: hasSelectedSuggestions,
            } }, h("fieldset", { key: '65855ac942eb354adb6687627f40fbb3d2ff6d77', class: { [`${tag}-input-wrapper`]: !!tag }, "data-testid": "input-wrapper" }, h("div", { key: '24550572902a9b35f209c032d2e7391cc0c6253f', class: { [`${tag}-label`]: !!tag } }, h("slot", { key: '9c183c06da0e79ba2e5c943af6a024da968a3582', name: "label" }), h(Status, { key: 'eda164b8d27b3895415451132c58ce52d35ae546', tag: tag, isLoadingSelected3waCount: isLoadingSelected3waCount, selected3waCount: selected3waCount, showHintsTooltip: this.showHintsTooltip, toggleTooltip: () => this.tooltipOpen ? this.closeTooltip() : this.openTooltip() })), h("slot", { key: '41e978a0df4cff6e4ff8ff30955aa4f0ad5fc1f9', name: "input" }), h(Tooltip, { key: '7078df0a8f1f62e1be2d3de7505ab3ac30be417a', tag: tag, onClose: () => this.closeTooltip() })), h(Suggestions, { key: '89f8fe1a6a3695bcc06418f8738b818ddd797910', style: suggestionsStyle, tag: tag, value: value, hoverIndex: this.hoverIndex, loading: isLoading, suggestions: this.suggestions, onSuggestionSelected: onSuggestionSelected.bind(this), onMouseOver: onMouseOver.bind(this), onMouseOut: onMouseOut.bind(this), onClose: () => this.hideSuggestions() })));
    }
    static get is() { return "what3words-notes"; }
    static get originalStyleUrls() {
        return {
            "$": ["what3words-notes.scss"]
        };
    }
    static get styleUrls() {
        return {
            "$": ["what3words-notes.css"]
        };
    }
    static get properties() {
        return {
            "addressFormat": {
                "type": "string",
                "attribute": "address-format",
                "mutable": false,
                "complexType": {
                    "original": "W3wAddressFormat",
                    "resolved": "\"slashes\" | \"url\"",
                    "references": {
                        "W3wAddressFormat": {
                            "location": "import",
                            "path": "./domain",
                            "id": "src/components/what3words-notes/domain.ts::W3wAddressFormat"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "default",
                            "text": "slashes"
                        }],
                    "text": "The format to display the what3words address in\n- `slashes` - e.g. `///filled.count.soap` (default)\n- `url` - e.g. `https://w3w.co/filled.count.soap`"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "\"slashes\""
            },
            "showHintsTooltip": {
                "type": "boolean",
                "attribute": "show-hints-tooltip",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "default",
                            "text": "true"
                        }],
                    "text": "Show the what3words hints tooltip when clicking on the what3words status icon"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "true"
            },
            "callback": {
                "type": "string",
                "attribute": "callback",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The callback function to execute when the component is loaded"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "\"\""
            },
            "apiKey": {
                "type": "string",
                "attribute": "api-key",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The what3words API key to use for the what3words API requests"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "\"\""
            },
            "headers": {
                "type": "string",
                "attribute": "headers",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The headers to include in the what3words API requests"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "\"{}\""
            },
            "baseUrl": {
                "type": "string",
                "attribute": "base-url",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The base URL for the what3words API requests"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "sdk.api.clients.autosuggest[\"_config\"].host ?? DEFAULTS.base_url"
            },
            "apiVersion": {
                "type": "string",
                "attribute": "api-version",
                "mutable": false,
                "complexType": {
                    "original": "ApiVersion",
                    "resolved": "ApiVersion.Version1 | ApiVersion.Version2 | ApiVersion.Version3",
                    "references": {
                        "ApiVersion": {
                            "location": "import",
                            "path": "@what3words/api",
                            "id": "../../../node_modules/@what3words/api/dist/index.d.ts::ApiVersion"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "default",
                            "text": "\"v3\""
                        }],
                    "text": "The API version to use for the what3words API requests"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "\"v3\" as ApiVersion"
            },
            "typeaheadDelay": {
                "type": "number",
                "attribute": "typeahead-delay",
                "mutable": false,
                "complexType": {
                    "original": "number",
                    "resolved": "number",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "default",
                            "text": "300"
                        }],
                    "text": "The delay in milliseconds to wait after the user has finished typing before making an autosuggest request"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "300"
            },
            "language": {
                "type": "string",
                "attribute": "language",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "example",
                            "text": "\"en\""
                        }],
                    "text": "The language to return the results in"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "\"\""
            },
            "searchFocus": {
                "type": "string",
                "attribute": "search-focus",
                "mutable": false,
                "complexType": {
                    "original": "`${number},${number}`",
                    "resolved": "`${number},${number}` | undefined",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [{
                            "name": "example",
                            "text": "\"51.5412,-0.2477\""
                        }],
                    "text": "The focus point to prioritize results around. If not provided, the user's current location is used."
                },
                "getter": false,
                "setter": false,
                "reflect": false
            },
            "nFocusResults": {
                "type": "number",
                "attribute": "n-focus-results",
                "mutable": false,
                "complexType": {
                    "original": "number",
                    "resolved": "number",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "default",
                            "text": "3"
                        }],
                    "text": "The number of results to return"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "3"
            },
            "clipToCountry": {
                "type": "string",
                "attribute": "clip-to-country",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "example",
                            "text": "\"GB\""
                        }],
                    "text": "Clip the results to a specific country"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "\"\""
            },
            "clipToBoundingBox": {
                "type": "string",
                "attribute": "clip-to-bounding-box",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "example",
                            "text": "\"51.521,-0.205,51.576,-0.105\""
                        }],
                    "text": "Clip the results to a specific bounding box"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "\"\""
            },
            "clipToCircle": {
                "type": "string",
                "attribute": "clip-to-circle",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "example",
                            "text": "\"51.521,-0.205,1000\""
                        }],
                    "text": "Clip the results to a specific circle"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "\"\""
            },
            "clipToPolygon": {
                "type": "string",
                "attribute": "clip-to-polygon",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "example",
                            "text": "\"51.521,-0.205,51.576,-0.105,51.576,-0.205\""
                        }],
                    "text": "Clip the results to a specific polygon"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "\"\""
            }
        };
    }
    static get states() {
        return {
            "value": {},
            "suggestions": {},
            "selectedSuggestions": {},
            "updatingSelectedSuggestions": {},
            "apiRequestTimeout": {},
            "loading": {},
            "hoverIndex": {},
            "clients": {},
            "tooltipOpen": {}
        };
    }
    static get events() {
        return [{
                "method": "valueChanged",
                "name": "valueChanged",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [{
                            "name": "example",
                            "text": "{ value: \"filled.count.s\" }"
                        }],
                    "text": "Emitted when the input value changes"
                },
                "complexType": {
                    "original": "InputValuePayload",
                    "resolved": "InputValuePayload",
                    "references": {
                        "InputValuePayload": {
                            "location": "import",
                            "path": "../what3words-autosuggest/domain",
                            "id": "src/components/what3words-autosuggest/domain.ts::InputValuePayload"
                        }
                    }
                }
            }, {
                "method": "valueValid",
                "name": "valueValid",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [{
                            "name": "example",
                            "text": "{ value: \"filled.count.soap\" }"
                        }],
                    "text": "Emitted when the input value is a valid what3words address"
                },
                "complexType": {
                    "original": "InputValuePayload",
                    "resolved": "InputValuePayload",
                    "references": {
                        "InputValuePayload": {
                            "location": "import",
                            "path": "../what3words-autosuggest/domain",
                            "id": "src/components/what3words-autosuggest/domain.ts::InputValuePayload"
                        }
                    }
                }
            }, {
                "method": "valueInvalid",
                "name": "valueInvalid",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [{
                            "name": "example",
                            "text": "{ value: \"filled,count,s\" }"
                        }],
                    "text": "Emitted when the input value is not a valid what3words address"
                },
                "complexType": {
                    "original": "InputValuePayload",
                    "resolved": "InputValuePayload",
                    "references": {
                        "InputValuePayload": {
                            "location": "import",
                            "path": "../what3words-autosuggest/domain",
                            "id": "src/components/what3words-autosuggest/domain.ts::InputValuePayload"
                        }
                    }
                }
            }, {
                "method": "suggestionSelected",
                "name": "suggestionSelected",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [{
                            "name": "example",
                            "text": "{ suggestion: { words: \"filled.count.soap\", [...] } }"
                        }],
                    "text": "Emitted when a suggestion is selected"
                },
                "complexType": {
                    "original": "SuggestionPayload",
                    "resolved": "SuggestionPayload",
                    "references": {
                        "SuggestionPayload": {
                            "location": "import",
                            "path": "../what3words-autosuggest/domain",
                            "id": "src/components/what3words-autosuggest/domain.ts::SuggestionPayload"
                        }
                    }
                }
            }, {
                "method": "suggestionsChanged",
                "name": "suggestionsChanged",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when the suggestions change"
                },
                "complexType": {
                    "original": "SuggestionsPayload",
                    "resolved": "SuggestionsPayload",
                    "references": {
                        "SuggestionsPayload": {
                            "location": "import",
                            "path": "../what3words-autosuggest/domain",
                            "id": "src/components/what3words-autosuggest/domain.ts::SuggestionsPayload"
                        }
                    }
                }
            }, {
                "method": "suggestionHover",
                "name": "suggestionHover",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when a suggestion is hovered over"
                },
                "complexType": {
                    "original": "SuggestionPayload",
                    "resolved": "SuggestionPayload",
                    "references": {
                        "SuggestionPayload": {
                            "location": "import",
                            "path": "../what3words-autosuggest/domain",
                            "id": "src/components/what3words-autosuggest/domain.ts::SuggestionPayload"
                        }
                    }
                }
            }, {
                "method": "apiError",
                "name": "apiError",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when a what3words API error occurs"
                },
                "complexType": {
                    "original": "{ error: Error | null }",
                    "resolved": "{ error: Error | null; }",
                    "references": {
                        "Error": {
                            "location": "global",
                            "id": "global::Error"
                        }
                    }
                }
            }];
    }
    static get elementRef() { return "el"; }
    static get watchers() {
        return [{
                "propName": "apiKey",
                "methodName": "setApiKey"
            }, {
                "propName": "baseUrl",
                "methodName": "setBaseUrl"
            }, {
                "propName": "apiVersion",
                "methodName": "setApiVersion"
            }, {
                "propName": "headers",
                "methodName": "setHeaders"
            }, {
                "propName": "loading",
                "methodName": "handleLoadingChange"
            }, {
                "propName": "suggestions",
                "methodName": "handleSuggestionsChange"
            }];
    }
    static get listeners() {
        return [{
                "name": "valueChanged",
                "method": "handleValueChanged",
                "target": undefined,
                "capture": true,
                "passive": true
            }, {
                "name": "valueValid",
                "method": "handleValueValid",
                "target": undefined,
                "capture": true,
                "passive": true
            }, {
                "name": "valueInvalid",
                "method": "handleValueInvalid",
                "target": undefined,
                "capture": false,
                "passive": false
            }, {
                "name": "suggestionsChanged",
                "method": "handleSuggestionsChanged",
                "target": undefined,
                "capture": true,
                "passive": true
            }, {
                "name": "suggestionSelected",
                "method": "handleSuggestionSelected",
                "target": undefined,
                "capture": true,
                "passive": true
            }, {
                "name": "suggestionHover",
                "method": "handleSuggestionHover",
                "target": undefined,
                "capture": true,
                "passive": true
            }, {
                "name": "blur",
                "method": "handleBlur",
                "target": undefined,
                "capture": true,
                "passive": true
            }, {
                "name": "input",
                "method": "handleInput",
                "target": undefined,
                "capture": true,
                "passive": true
            }, {
                "name": "keydown",
                "method": "handleKeyDown",
                "target": undefined,
                "capture": true,
                "passive": false
            }, {
                "name": "contextmenu",
                "method": "handleContextMenu",
                "target": "window",
                "capture": true,
                "passive": true
            }, {
                "name": "click",
                "method": "handleWindowClick",
                "target": "window",
                "capture": true,
                "passive": true
            }, {
                "name": "scroll",
                "method": "handleScroll",
                "target": undefined,
                "capture": true,
                "passive": true
            }];
    }
}
