"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSelectionRange = void 0;
exports.emitKeyboardEvent = emitKeyboardEvent;
exports.writeToActiveElement = writeToActiveElement;
exports.writeToElement = writeToElement;
exports.processKeydown = processKeydown;
exports.writeToInput = writeToInput;
exports.selectElementContents = selectElementContents;
exports.writeToContentEditable = writeToContentEditable;
exports.emitKeyDown = emitKeyDown;
exports.emitKeyUp = emitKeyUp;
const f_1 = require("../../../system/core/relation/Clamp/f");
const activeElement_1 = require("../../activeElement");
const keyCode_1 = require("./keyCode");
const isAlphaNumCharOrSpace = (str) => {
    return /[a-zA-Z\d\s:]/.test(str);
};
const isAlphaNumChar = (str) => {
    return /[a-zA-Z\d:]/.test(str);
};
const isSpace = (str) => {
    return /[\s]/.test(str);
};
const findPrevAltIndex = (value, selectionStart) => {
    let i = selectionStart;
    if (selectionStart > 0) {
        if (!isAlphaNumCharOrSpace(value[i - 1])) {
            while (!isAlphaNumCharOrSpace(value[i - 1]) && i > 0) {
                i--;
            }
        }
        else if (isSpace(value[i - 1])) {
            while (isSpace(value[i - 1]) && i > 0) {
                i--;
            }
            i = findPrevAltIndex(value, i);
        }
        else {
            while (isAlphaNumChar(value[i - 1]) && i > 0) {
                i--;
            }
        }
    }
    return i;
};
const findNextAltIndex = (value, selectionStart) => {
    let i = selectionStart;
    if (!isAlphaNumCharOrSpace(value[selectionStart])) {
        while (!isAlphaNumCharOrSpace(value[i]) && i < value.length) {
            i++;
        }
    }
    else if (isSpace(value[selectionStart])) {
        while (isSpace(value[i]) && i < value.length) {
            i++;
        }
        i = findNextAltIndex(value, i);
    }
    else {
        while (isAlphaNumChar(value[i]) && i < value.length) {
            i++;
        }
    }
    return i;
};
function emitKeyboardEvent(system, type, init) {
    const activeElement = (0, activeElement_1.getActiveElement)(system);
    if (activeElement) {
        const event = new KeyboardEvent(type, init);
        const _preventDefault = event.preventDefault.bind(event);
        let defaultPrevented = false;
        event.preventDefault = () => {
            defaultPrevented = true;
            return _preventDefault();
        };
        activeElement.dispatchEvent(event);
        if (event.type === 'keydown' && !defaultPrevented) {
            const { key } = event;
            writeToElement(system, activeElement, key, init);
        }
    }
}
function writeToActiveElement(system, key) {
    const activeElement = (0, activeElement_1.getActiveElement)(system);
    writeToElement(system, activeElement, key);
    return activeElement;
}
function writeToElement(system, element, key, modifier = {}) {
    const { tagName } = element;
    // https://stackoverflow.com/questions/26723648/check-whether-an-html-element-is-editable-or-not-using-js
    if ((tagName === 'INPUT' &&
        /^(?:text|email|search|tel|url|password)$/i.test(element.type)) ||
        tagName === 'TEXTAREA') {
        writeToInput(element, key, modifier);
    }
    else if (tagName === 'DIV' &&
        element.contentEditable === 'true') {
        writeToContentEditable(system, element, key, modifier);
    }
}
function processKeydown({ value, selectionStart = 0, selectionEnd = 0, selectionDirection = 'none', }, { ctrlKey, shiftKey, altKey }, key) {
    if (selectionStart === null) {
        selectionStart = 0;
    }
    if (selectionEnd === null) {
        selectionEnd = selectionStart;
    }
    let nextValue = value;
    let nextSelectionStart = selectionStart;
    let nextSelectionEnd = selectionEnd;
    let nextSelectionDirection = selectionDirection;
    if ((0, keyCode_1.isChar)(key)) {
        nextValue =
            value.substr(0, selectionStart) +
                key +
                value.substr(selectionEnd, value.length);
        nextSelectionStart = selectionStart + 1;
        nextSelectionEnd = nextSelectionStart;
    }
    else {
        if (key === 'Backspace') {
            if (selectionStart === selectionEnd) {
                nextValue =
                    value.substr(0, selectionStart - 1) +
                        value.substr(selectionStart, value.length);
            }
            else {
                nextValue =
                    value.substr(0, selectionStart) +
                        value.substr(selectionEnd, value.length);
            }
            nextSelectionStart = (0, f_1.clamp)(selectionStart - 1, 0, nextValue.length);
            nextSelectionEnd = nextSelectionStart;
        }
        else if (key === 'Enter') {
            nextValue =
                value.substr(0, selectionStart) +
                    '\n' +
                    value.substr(selectionEnd, value.length);
            nextSelectionStart = selectionStart + 1;
            nextSelectionEnd = nextSelectionStart;
        }
        else if (key === 'Space') {
            nextValue =
                value.substr(0, selectionStart) +
                    ' ' +
                    value.substr(selectionEnd, value.length);
            nextSelectionStart = selectionStart + 1;
            nextSelectionEnd = nextSelectionStart;
        }
        else if (key === 'ArrowLeft') {
            if (shiftKey && altKey) {
                if (selectionDirection === 'backward' ||
                    selectionDirection === 'none' ||
                    selectionStart === selectionEnd) {
                    nextSelectionStart = findPrevAltIndex(value, selectionStart);
                    nextSelectionDirection = 'backward';
                }
                else {
                    nextSelectionEnd = findPrevAltIndex(value, selectionEnd);
                    if (nextSelectionEnd === selectionStart) {
                        nextSelectionDirection = 'backward';
                    }
                }
            }
            else if (shiftKey) {
                if (selectionStart > 0) {
                    nextSelectionStart = selectionStart - 1;
                }
            }
            else if (altKey) {
                nextSelectionStart = findPrevAltIndex(value, selectionStart);
                nextSelectionEnd = nextSelectionStart;
            }
            else {
                if (selectionStart === selectionEnd) {
                    nextSelectionStart = selectionStart - 1;
                    nextSelectionEnd = nextSelectionStart;
                }
                else {
                    nextSelectionEnd = selectionStart;
                }
            }
        }
        else if (key === 'ArrowRight') {
            if (shiftKey && altKey) {
                if (selectionDirection === 'forward' ||
                    selectionDirection === 'none' ||
                    selectionStart === selectionEnd) {
                    nextSelectionEnd = findNextAltIndex(value, selectionEnd);
                    nextSelectionDirection = 'forward';
                }
                else {
                    nextSelectionStart = findNextAltIndex(value, selectionStart);
                    if (nextSelectionStart === selectionEnd) {
                        nextSelectionDirection = 'forward';
                    }
                }
            }
            else if (shiftKey) {
                if (selectionStart > 0) {
                    nextSelectionEnd = nextSelectionEnd + 1;
                }
            }
            else if (altKey) {
                nextSelectionStart = findNextAltIndex(value, selectionStart);
                nextSelectionEnd = nextSelectionStart;
            }
            else {
                if (selectionStart === selectionEnd) {
                    nextSelectionStart = selectionStart + 1;
                    nextSelectionEnd = nextSelectionStart;
                }
                else {
                    nextSelectionStart = selectionEnd;
                }
            }
        }
    }
    return {
        value: nextValue,
        selectionStart: nextSelectionStart,
        selectionEnd: nextSelectionEnd,
        selectionDirection: nextSelectionDirection,
    };
}
function writeToInput(input, key, { ctrlKey, shiftKey, altKey, } = {}) {
    const { value, selectionStart = 0, selectionEnd = 0, selectionDirection = 'none', } = input;
    const { value: nextValue, selectionStart: nextSelectionStart, selectionEnd: nextSelectionEnd, selectionDirection: nextSelectionDirection, } = processKeydown({
        value,
        selectionStart,
        selectionEnd,
        selectionDirection,
    }, { ctrlKey, shiftKey, altKey }, key);
    const { maxLength } = input;
    if (maxLength !== undefined && maxLength !== -1) {
        if (nextValue.length > maxLength) {
            return;
        }
    }
    function _setSelection() {
        if (nextSelectionStart !== selectionStart ||
            nextSelectionEnd !== selectionEnd) {
            input.setSelectionRange(nextSelectionStart, nextSelectionEnd, nextSelectionDirection);
            input.selectionDirection = nextSelectionDirection;
        }
    }
    if (nextValue !== value) {
        input.value = nextValue;
        // TODO
        // inputType: (Optional) A string specifying the type of change for editible content such as, for example, inserting, deleting, or formatting text.
        // data: (Optional) A string containing characters to insert. This may be an empty string if the change doesn't insert text (such as when deleting characters, for example).
        // dataTransfer: (Optional) A DataTransfer object containing information about richtext or plaintext data being added to or removed from editible content.
        // isComposing: (Optional) A boolean indicating that the event is part of a composition session, meaning it is after a compositionstart event but before a compositionend event.  The default is false.
        // ranges: (Optional) An array of static ranges that will be affected by a change to the DOM if the input event is not canceled.
        const inputEvent = new InputEvent('input', {});
        // selection must be set before dispatching input
        _setSelection();
        input.dispatchEvent(inputEvent);
    }
    else {
        _setSelection();
    }
}
const getSelectionRange = (system) => {
    const { api: { document: { getSelection }, }, } = system;
    const selection = getSelection();
    const range = selection.getRangeAt(0);
    return {
        selectionStart: range.startOffset,
        selectionEnd: range.endOffset,
        selectionDirection: range.startOffset === range.endOffset
            ? 'none'
            : range.startOffset < range.endOffset
                ? 'forward'
                : 'backward',
    };
};
exports.getSelectionRange = getSelectionRange;
// Selection is broken on Safari (iOS) (inside Shadow Root)
function selectElementContents(system, element, selectionStart, selectionEnd) {
    const { api: { document: { createRange, getSelection }, }, } = system;
    const range = createRange();
    if (!element.firstChild) {
        return;
    }
    try {
        range.setStart(element.firstChild, selectionStart);
        range.setEnd(element.firstChild, selectionEnd);
        const selection = getSelection();
        selection.removeAllRanges();
        selection.addRange(range);
    }
    catch (err) {
        // swallow
    }
}
function writeToContentEditable(system, input, key, { ctrlKey, shiftKey, altKey, } = {}) {
    const value = input.innerText;
    const { selectionStart = 0, selectionEnd = 0, selectionDirection = 'none', } = (0, exports.getSelectionRange)(system);
    const { value: nextValue, selectionStart: nextSelectionStart, selectionEnd: nextSelectionEnd, selectionDirection: nextSelectionDirection, } = processKeydown({
        value,
        selectionStart,
        selectionEnd,
        selectionDirection,
    }, { ctrlKey, shiftKey, altKey }, key);
    function _setSelection() {
        if (nextSelectionStart !== selectionStart ||
            nextSelectionEnd !== selectionEnd) {
            selectElementContents(system, input, nextSelectionStart, nextSelectionEnd);
        }
    }
    if (nextValue !== value) {
        input.innerText = nextValue;
        const inputEvent = new InputEvent('input', {});
        _setSelection();
        input.dispatchEvent(inputEvent);
    }
    else {
        _setSelection();
    }
}
function emitKeyDown(system, key) {
    const code = keyCode_1.keyToCode[key];
    const keyCode = keyCode_1.keyToKeyCode[key];
    emitKeyboardEvent(system, 'keydown', {
        key: code,
        // @ts-ignore
        keyCode,
        code,
        // TODO shiftKey, ctrlKey, ...
        shiftKey: false,
        ctrlKey: false,
        metaKey: false,
        bubbles: true,
    });
}
function emitKeyUp(system, key) {
    const code = keyCode_1.keyToCode[key];
    const keyCode = keyCode_1.keyToKeyCode[key];
    emitKeyboardEvent(system, 'keyup', {
        key: code,
        // @ts-ignore
        keyCode,
        code,
        // TODO shiftKey, ctrlKey, ...
        shiftKey: false,
        ctrlKey: false,
        metaKey: false,
        bubbles: true,
    });
}
//# sourceMappingURL=write.js.map