from enum import Enum, auto


class By(str, Enum):
    CSS_SELECTOR = 'css'
    XPATH = 'xpath'
    CLASS_NAME = 'class_name'
    ID = 'id'
    TAG_NAME = 'tag_name'
    NAME = 'name'


class PageLoadState(str, Enum):
    COMPLETE = 'complete'
    INTERACTIVE = 'interactive'
    LOADING = 'loading'


class ScrollPosition(str, Enum):
    UP = 'up'
    DOWN = 'down'
    LEFT = 'left'
    RIGHT = 'right'


class Scripts:
    ELEMENT_VISIBLE = """
    function() {
        const rect = this.getBoundingClientRect();
        return (
            rect.width > 0 && rect.height > 0
            && getComputedStyle(this).visibility !== 'hidden'
            && getComputedStyle(this).display !== 'none'
        )
    }
    """

    ELEMENT_ON_TOP = """
    function() {
        const rect = this.getBoundingClientRect();
        const x = rect.x + rect.width / 2;
        const y = rect.y + rect.height / 2;
        const elementFromPoint = document.elementFromPoint(x, y);
        if (!elementFromPoint) {
            return false;
        }
        return elementFromPoint === this || this.contains(elementFromPoint);
    }
    """

    ELEMENT_INTERACTIVE = """
    function() {
        const style = window.getComputedStyle(this);
        const rect = this.getBoundingClientRect();
        if (
            rect.width <= 0 ||
            rect.height <= 0 ||
            style.visibility === 'hidden' ||
            style.display === 'none' ||
            style.pointerEvents === 'none'
        ) {
            return false;
        }
        const x = rect.x + rect.width / 2;
        const y = rect.y + rect.height / 2;
        const elementFromPoint = document.elementFromPoint(x, y);
        if (!elementFromPoint || (elementFromPoint !== this && !this.contains(elementFromPoint))) {
            return false;
        }
        if (this.disabled) {
            return false;
        }
        return true;
    }
    """

    CLICK = """
    function(){
        clicked = false;
        this.addEventListener('click', function(){
            clicked = true;
        });
        this.click();
        return clicked;
    }
    """

    CLICK_OPTION_TAG = """
    function() {
        var select = this && this.parentElement ? this.parentElement.closest('select') : null;
        if (!select) { return false; }
        for (var i = 0; i < select.options.length; i++) {
            select.options[i].selected = false;
        }
        this.selected = true;
        select.value = this.value;
        select.dispatchEvent(new Event('input', { bubbles: true }));
        select.dispatchEvent(new Event('change', { bubbles: true }));
        return true;
    }
    """

    BOUNDS = """
    function() {
        return JSON.stringify(this.getBoundingClientRect());
    }
    """

    FIND_RELATIVE_XPATH_ELEMENT = """
        function() {
            return document.evaluate(
                "{escaped_value}", this, null,
                XPathResult.FIRST_ORDERED_NODE_TYPE, null
            ).singleNodeValue;
        }
    """

    FIND_XPATH_ELEMENT = """
        var element = document.evaluate(
            "{escaped_value}", document, null,
            XPathResult.FIRST_ORDERED_NODE_TYPE, null
        ).singleNodeValue;
        element;
    """

    FIND_RELATIVE_XPATH_ELEMENTS = """
        function() {
            var elements = document.evaluate(
                "{escaped_value}", this, null,
                XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
            );
            var results = [];
            for (var i = 0; i < elements.snapshotLength; i++) {
                results.push(elements.snapshotItem(i));
            }
            return results;
        }
    """

    FIND_XPATH_ELEMENTS = """
        var elements = document.evaluate(
            "{escaped_value}", document, null,
            XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
        );
        var results = [];
        for (var i = 0; i < elements.snapshotLength; i++) {
            results.push(elements.snapshotItem(i));
        }
        results;
    """

    QUERY_SELECTOR = 'document.querySelector("{selector}");'

    RELATIVE_QUERY_SELECTOR = """
        function() {
            return this.querySelector("{selector}");
        }
    """

    QUERY_SELECTOR_ALL = 'document.querySelectorAll("{selector}");'

    RELATIVE_QUERY_SELECTOR_ALL = """
        function() {
            return this.querySelectorAll("{selector}");
        }
    """

    GET_TEXT_BY_XPATH = """
        (() => {
            const node = document.evaluate(
                "{escaped_value}",
                document,
                null,
                XPathResult.FIRST_ORDERED_NODE_TYPE,
                null
            ).singleNodeValue;
            return node ? (node.textContent || "") : "";
        })()
    """

    GET_TEXT_BY_CSS = """
        (() => {
            const el = document.querySelector("{selector}");
            return el ? (el.textContent || "") : "";
        })()
    """

    GET_PARENT_NODE = """
        function() {
            return this.parentElement;
        }
    """

    GET_CHILDREN_NODE = """
        function() {{
            function getChildrenUntilDepth(element, maxDepth, tagFilter = [], currentDepth = 1)
            {{
                if (currentDepth > maxDepth) return [];

                const children = Array.from(element.children);
                let filtered = tagFilter.length === 0
                    ? children
                : children.filter(child => tagFilter.includes(child.tagName.toLowerCase()));

                let allDescendants = [...filtered];

                for (let child of children)
                {{
                    allDescendants.push(
                    ...getChildrenUntilDepth(child, maxDepth, tagFilter, currentDepth + 1)
                    );
                }}

                return allDescendants;
            }}

            return getChildrenUntilDepth(this, {max_depth}, {tag_filter});
        }}
    """

    GET_SIBLINGS_NODE = """
        function() {{
            function getSiblingsUntilDepth(element, tagFilter = [])
            {{
                const parent = element.parentElement;
                const siblings = Array.from(parent.children);
                let filtered = tagFilter.length === 0
                    ? siblings.filter(child => child !== element)
                : siblings.filter(child =>
                    tagFilter.includes(child.tagName.toLowerCase()) && child !== element);

                let allDescendants = [...filtered];

                return allDescendants;
            }}

            return getSiblingsUntilDepth(this, {tag_filter});
        }}
    """

    MAKE_REQUEST = """
(async function() {{
    async function makeRequest(url, options) {{
        try {{
            const response = await fetch(url, options, {{
                credentials: 'include',
            }});
            const headers = {{}};
            response.headers.forEach((value, key) => {{
                headers[key] = value;
            }});

            // Extract cookies from set-cookie header
            const cookies = document.cookie;
            let text = await response.text();
            const possiblePrefixes = [")]}}'\\n", ")]}}'\\n", ")]}}\\n"];
            for (let prefix of possiblePrefixes) {{
                if (text.startsWith(prefix)) {{
                    text = text.substring(prefix.length);
                    break;
                }}
            }}
            let content, jsonData;
            const contentType = response.headers.get('content-type') || '';

            if (contentType.includes('application/json')) {{
                try {{
                    jsonData = JSON.parse(text);
                    text = JSON.stringify(jsonData);
                }} catch (e) {{
                    jsonData = null;
                    // Keep original text if parsing fails
                }}
                content = new TextEncoder().encode(text).buffer;
            }} else {{
                // For non-JSON, keep original text handling
                content = new TextEncoder().encode(text).buffer;
                jsonData = null;
            }}

            return {{
                status: response.status,
                ok: response.ok,
                url: response.url,
                headers: headers,
                cookies: cookies,
                content: Array.from(new Uint8Array(content)),
                text: text,
                json: jsonData
            }};
        }} catch (error) {{
            return {{
                error: error.toString(),
                status: 0
            }};
        }}
    }}

    const url = {url};
    const options = {options};
    return await makeRequest(url, options);
}})();
"""

    SCROLL_BY = """
new Promise((resolve) => {{
    const behavior = '{behavior}';
    if (behavior === 'auto') {{
        window.scrollBy({{
            {axis}: {distance},
            behavior: 'auto'
        }});
        resolve();
    }} else {{
        const onScrollEnd = () => {{
            window.removeEventListener('scrollend', onScrollEnd);
            resolve();
        }};
        window.addEventListener('scrollend', onScrollEnd);
        window.scrollBy({{
            {axis}: {distance},
            behavior: 'smooth'
        }});
        setTimeout(() => {{
            window.removeEventListener('scrollend', onScrollEnd);
            resolve();
        }}, 2000);
    }}
}});
"""

    SCROLL_TO_TOP = """
new Promise((resolve) => {{
    const behavior = '{behavior}';
    if (behavior === 'auto') {{
        window.scrollTo({{
            top: 0,
            behavior: 'auto'
        }});
        resolve();
    }} else {{
        const onScrollEnd = () => {{
            window.removeEventListener('scrollend', onScrollEnd);
            resolve();
        }};
        window.addEventListener('scrollend', onScrollEnd);
        window.scrollTo({{
            top: 0,
            behavior: 'smooth'
        }});
        setTimeout(() => {{
            window.removeEventListener('scrollend', onScrollEnd);
            resolve();
        }}, 2000);
    }}
}});
"""

    SCROLL_TO_BOTTOM = """
new Promise((resolve) => {{
    const behavior = '{behavior}';
    if (behavior === 'auto') {{
        window.scrollTo({{
            top: document.body.scrollHeight,
            behavior: 'auto'
        }});
        resolve();
    }} else {{
        const onScrollEnd = () => {{
            window.removeEventListener('scrollend', onScrollEnd);
            resolve();
        }};
        window.addEventListener('scrollend', onScrollEnd);
        window.scrollTo({{
            top: document.body.scrollHeight,
            behavior: 'smooth'
        }});
        setTimeout(() => {{
            window.removeEventListener('scrollend', onScrollEnd);
            resolve();
        }}, 2000);
    }}
}});
"""

    GET_SCROLL_Y = 'window.scrollY || window.pageYOffset || 0'

    GET_REMAINING_SCROLL_TO_BOTTOM = """
(function() {
    const scrollHeight = Math.max(
        document.body.scrollHeight,
        document.documentElement.scrollHeight
    );
    const clientHeight = window.innerHeight;
    const scrollTop = window.scrollY || window.pageYOffset || 0;
    return Math.max(0, scrollHeight - clientHeight - scrollTop);
})()
"""

    GET_VIEWPORT_CENTER = 'JSON.stringify([window.innerWidth / 2, window.innerHeight / 2])'

    INSERT_TEXT = """
    function() {
        const el = this;
        const text = arguments[0];

        // Standard input/textarea
        if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
            const start = el.selectionStart || el.value.length;
            const end = el.selectionEnd || el.value.length;
            const before = el.value.substring(0, start);
            const after = el.value.substring(end);
            el.value = before + text + after;
            el.selectionStart = el.selectionEnd = start + text.length;
            el.dispatchEvent(new Event('input', { bubbles: true }));
            el.dispatchEvent(new Event('change', { bubbles: true }));
            return true;
        }

        // ContentEditable elements
        if (el.isContentEditable) {
            el.focus();
            const selection = window.getSelection();
            const range = selection.getRangeAt(0);
            range.deleteContents();
            const textNode = document.createTextNode(text);
            range.insertNode(textNode);
            range.setStartAfter(textNode);
            range.setEndAfter(textNode);
            selection.removeAllRanges();
            selection.addRange(range);
            el.dispatchEvent(new Event('input', { bubbles: true }));
            return true;
        }

        return false;
    }
    """

    CLEAR_INPUT = """
    function() {
        const el = this;

        // Standard input/textarea
        if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
            el.value = '';
            el.dispatchEvent(new Event('input', { bubbles: true }));
            el.dispatchEvent(new Event('change', { bubbles: true }));
            return true;
        }

        // ContentEditable elements
        if (el.isContentEditable) {
            el.focus();
            el.innerHTML = '';
            el.dispatchEvent(new Event('input', { bubbles: true }));
            return true;
        }

        return false;
    }
    """

    IS_EDITABLE = """
    function() {
        const el = this;

        // Check standard input elements
        if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
            return !el.disabled && !el.readOnly;
        }

        // Check contenteditable (including inherited)
        let current = el;
        while (current) {
            if (current.isContentEditable) {
                return true;
            }
            current = current.parentElement;
        }

        return false;
    }
    """

    IS_OPTION_TAG = """
    function() {
        return !!(this && this.tagName && this.tagName.toLowerCase() === 'option');
    }
    """


class Key(tuple[str, int], Enum):
    BACKSPACE = ('Backspace', 8)
    TAB = ('Tab', 9)
    ENTER = ('Enter', 13)
    SHIFT = ('Shift', 16)
    CONTROL = ('Control', 17)
    ALT = ('Alt', 18)
    PAUSE = ('Pause', 19)
    CAPSLOCK = ('CapsLock', 20)
    ESCAPE = ('Escape', 27)
    SPACE = ('Space', 32)
    PAGEUP = ('PageUp', 33)
    PAGEDOWN = ('PageDown', 34)
    END = ('End', 35)
    HOME = ('Home', 36)
    ARROWLEFT = ('ArrowLeft', 37)
    ARROWUP = ('ArrowUp', 38)
    ARROWRIGHT = ('ArrowRight', 39)
    ARROWDOWN = ('ArrowDown', 40)
    PRINTSCREEN = ('PrintScreen', 44)
    INSERT = ('Insert', 45)
    DELETE = ('Delete', 46)

    DIGIT0 = ('0', 48)
    DIGIT1 = ('1', 49)
    DIGIT2 = ('2', 50)
    DIGIT3 = ('3', 51)
    DIGIT4 = ('4', 52)
    DIGIT5 = ('5', 53)
    DIGIT6 = ('6', 54)
    DIGIT7 = ('7', 55)
    DIGIT8 = ('8', 56)
    DIGIT9 = ('9', 57)

    A = ('A', 65)
    B = ('B', 66)
    C = ('C', 67)
    D = ('D', 68)
    E = ('E', 69)
    F = ('F', 70)
    G = ('G', 71)
    H = ('H', 72)
    I = ('I', 73)  # noqa: E741
    J = ('J', 74)
    K = ('K', 75)
    L = ('L', 76)
    M = ('M', 77)
    N = ('N', 78)
    O = ('O', 79)  # noqa: E741
    P = ('P', 80)
    Q = ('Q', 81)
    R = ('R', 82)
    S = ('S', 83)
    T = ('T', 84)
    U = ('U', 85)
    V = ('V', 86)
    W = ('W', 87)
    X = ('X', 88)
    Y = ('Y', 89)
    Z = ('Z', 90)

    META = ('Meta', 91)
    METARIGHT = ('MetaRight', 92)
    CONTEXTMENU = ('ContextMenu', 93)

    NUMPAD0 = ('Numpad0', 96)
    NUMPAD1 = ('Numpad1', 97)
    NUMPAD2 = ('Numpad2', 98)
    NUMPAD3 = ('Numpad3', 99)
    NUMPAD4 = ('Numpad4', 100)
    NUMPAD5 = ('Numpad5', 101)
    NUMPAD6 = ('Numpad6', 102)
    NUMPAD7 = ('Numpad7', 103)
    NUMPAD8 = ('Numpad8', 104)
    NUMPAD9 = ('Numpad9', 105)
    NUMPADMULTIPLY = ('NumpadMultiply', 106)
    NUMPADADD = ('NumpadAdd', 107)
    NUMPADSUBTRACT = ('NumpadSubtract', 109)
    NUMPADDECIMAL = ('NumpadDecimal', 110)
    NUMPADDIVIDE = ('NumpadDivide', 111)

    F1 = ('F1', 112)
    F2 = ('F2', 113)
    F3 = ('F3', 114)
    F4 = ('F4', 115)
    F5 = ('F5', 116)
    F6 = ('F6', 117)
    F7 = ('F7', 118)
    F8 = ('F8', 119)
    F9 = ('F9', 120)
    F10 = ('F10', 121)
    F11 = ('F11', 122)
    F12 = ('F12', 123)

    NUMLOCK = ('NumLock', 144)
    SCROLLLOCK = ('ScrollLock', 145)

    SEMICOLON = ('Semicolon', 186)
    EQUALSIGN = ('EqualSign', 187)
    COMMA = ('Comma', 188)
    MINUS = ('Minus', 189)
    PERIOD = ('Period', 190)
    SLASH = ('Slash', 191)
    GRAVEACCENT = ('GraveAccent', 192)
    BRACKETLEFT = ('BracketLeft', 219)
    BACKSLASH = ('Backslash', 220)
    BRACKETRIGHT = ('BracketRight', 221)
    QUOTE = ('Quote', 222)


class BrowserType(Enum):
    CHROME = auto()
    EDGE = auto()


class TypoType(str, Enum):
    """Types of realistic typing errors."""

    ADJACENT = 'adjacent'
    TRANSPOSE = 'transpose'
    DOUBLE = 'double'
    SKIP = 'skip'
    MISSED_SPACE = 'missed_space'


DEFAULT_TYPO_PROBABILITY = 0.02


# Mapping from typeable character to (key, code, keycode).
# key:     DOM KeyboardEvent.key value
# code:    DOM KeyboardEvent.code (physical key on US QWERTY)
# keycode: legacy KeyboardEvent.keyCode / virtual key code
#
# Lowercase and uppercase letters share the same code/keycode; only `key` differs.
# Shifted symbol variants (e.g. '!' from '1') use the base key's code/keycode.
CHAR_TO_KEY_INFO: dict[str, tuple[str, str, int]] = {
    # Letters (lowercase)
    'a': ('a', 'KeyA', 65),
    'b': ('b', 'KeyB', 66),
    'c': ('c', 'KeyC', 67),
    'd': ('d', 'KeyD', 68),
    'e': ('e', 'KeyE', 69),
    'f': ('f', 'KeyF', 70),
    'g': ('g', 'KeyG', 71),
    'h': ('h', 'KeyH', 72),
    'i': ('i', 'KeyI', 73),
    'j': ('j', 'KeyJ', 74),
    'k': ('k', 'KeyK', 75),
    'l': ('l', 'KeyL', 76),
    'm': ('m', 'KeyM', 77),
    'n': ('n', 'KeyN', 78),
    'o': ('o', 'KeyO', 79),
    'p': ('p', 'KeyP', 80),
    'q': ('q', 'KeyQ', 81),
    'r': ('r', 'KeyR', 82),
    's': ('s', 'KeyS', 83),
    't': ('t', 'KeyT', 84),
    'u': ('u', 'KeyU', 85),
    'v': ('v', 'KeyV', 86),
    'w': ('w', 'KeyW', 87),
    'x': ('x', 'KeyX', 88),
    'y': ('y', 'KeyY', 89),
    'z': ('z', 'KeyZ', 90),
    # Letters (uppercase)
    'A': ('A', 'KeyA', 65),
    'B': ('B', 'KeyB', 66),
    'C': ('C', 'KeyC', 67),
    'D': ('D', 'KeyD', 68),
    'E': ('E', 'KeyE', 69),
    'F': ('F', 'KeyF', 70),
    'G': ('G', 'KeyG', 71),
    'H': ('H', 'KeyH', 72),
    'I': ('I', 'KeyI', 73),
    'J': ('J', 'KeyJ', 74),
    'K': ('K', 'KeyK', 75),
    'L': ('L', 'KeyL', 76),
    'M': ('M', 'KeyM', 77),
    'N': ('N', 'KeyN', 78),
    'O': ('O', 'KeyO', 79),
    'P': ('P', 'KeyP', 80),
    'Q': ('Q', 'KeyQ', 81),
    'R': ('R', 'KeyR', 82),
    'S': ('S', 'KeyS', 83),
    'T': ('T', 'KeyT', 84),
    'U': ('U', 'KeyU', 85),
    'V': ('V', 'KeyV', 86),
    'W': ('W', 'KeyW', 87),
    'X': ('X', 'KeyX', 88),
    'Y': ('Y', 'KeyY', 89),
    'Z': ('Z', 'KeyZ', 90),
    # Digits
    '0': ('0', 'Digit0', 48),
    '1': ('1', 'Digit1', 49),
    '2': ('2', 'Digit2', 50),
    '3': ('3', 'Digit3', 51),
    '4': ('4', 'Digit4', 52),
    '5': ('5', 'Digit5', 53),
    '6': ('6', 'Digit6', 54),
    '7': ('7', 'Digit7', 55),
    '8': ('8', 'Digit8', 56),
    '9': ('9', 'Digit9', 57),
    # Shifted digits (symbols on number row)
    ')': (')', 'Digit0', 48),
    '!': ('!', 'Digit1', 49),
    '@': ('@', 'Digit2', 50),
    '#': ('#', 'Digit3', 51),
    '$': ('$', 'Digit4', 52),
    '%': ('%', 'Digit5', 53),
    '^': ('^', 'Digit6', 54),
    '&': ('&', 'Digit7', 55),
    '*': ('*', 'Digit8', 56),
    '(': ('(', 'Digit9', 57),
    # Punctuation and symbols (unshifted)
    ' ': (' ', 'Space', 32),
    '-': ('-', 'Minus', 189),
    '=': ('=', 'Equal', 187),
    '[': ('[', 'BracketLeft', 219),
    ']': (']', 'BracketRight', 221),
    '\\': ('\\', 'Backslash', 220),
    ';': (';', 'Semicolon', 186),
    "'": ("'", 'Quote', 222),
    '`': ('`', 'Backquote', 192),
    ',': (',', 'Comma', 188),
    '.': ('.', 'Period', 190),
    '/': ('/', 'Slash', 191),
    # Punctuation and symbols (shifted)
    '_': ('_', 'Minus', 189),
    '+': ('+', 'Equal', 187),
    '{': ('{', 'BracketLeft', 219),
    '}': ('}', 'BracketRight', 221),
    '|': ('|', 'Backslash', 220),
    ':': (':', 'Semicolon', 186),
    '"': ('"', 'Quote', 222),
    '~': ('~', 'Backquote', 192),
    '<': ('<', 'Comma', 188),
    '>': ('>', 'Period', 190),
    '?': ('?', 'Slash', 191),
    # Whitespace
    '\n': ('Enter', 'Enter', 13),
    '\t': ('Tab', 'Tab', 9),
}


QWERTY_NEIGHBORS: dict[str, list[str]] = {
    '1': ['2', 'q'],
    '2': ['1', '3', 'q', 'w'],
    '3': ['2', '4', 'w', 'e'],
    '4': ['3', '5', 'e', 'r'],
    '5': ['4', '6', 'r', 't'],
    '6': ['5', '7', 't', 'y'],
    '7': ['6', '8', 'y', 'u'],
    '8': ['7', '9', 'u', 'i'],
    '9': ['8', '0', 'i', 'o'],
    '0': ['9', '-', 'o', 'p'],
    '-': ['0', '=', 'p', '['],
    '=': ['-', '[', ']'],
    'q': ['1', '2', 'w', 'a', 's'],
    'w': ['q', '2', '3', 'e', 'a', 's', 'd'],
    'e': ['w', '3', '4', 'r', 's', 'd', 'f'],
    'r': ['e', '4', '5', 't', 'd', 'f', 'g'],
    't': ['r', '5', '6', 'y', 'f', 'g', 'h'],
    'y': ['t', '6', '7', 'u', 'g', 'h', 'j'],
    'u': ['y', '7', '8', 'i', 'h', 'j', 'k'],
    'i': ['u', '8', '9', 'o', 'j', 'k', 'l'],
    'o': ['i', '9', '0', 'p', 'k', 'l', ';'],
    'p': ['o', '0', '-', '[', 'l', ';', "'"],
    '[': ['p', '-', '=', ']', ';', "'"],
    ']': ['[', '=', "'"],
    'a': ['q', 'w', 's', 'z', 'x'],
    's': ['q', 'w', 'e', 'a', 'd', 'z', 'x', 'c'],
    'd': ['w', 'e', 'r', 's', 'f', 'x', 'c', 'v'],
    'f': ['e', 'r', 't', 'd', 'g', 'c', 'v', 'b'],
    'g': ['r', 't', 'y', 'f', 'h', 'v', 'b', 'n'],
    'h': ['t', 'y', 'u', 'g', 'j', 'b', 'n', 'm'],
    'j': ['y', 'u', 'i', 'h', 'k', 'n', 'm', ','],
    'k': ['u', 'i', 'o', 'j', 'l', 'm', ',', '.'],
    'l': ['i', 'o', 'p', 'k', ';', ',', '.', '/'],
    ';': ['o', 'p', '[', 'l', "'", '.', '/'],
    "'": ['p', '[', ']', ';', '/'],
    'z': ['a', 's', 'x'],
    'x': ['z', 'a', 's', 'd', 'c'],
    'c': ['x', 's', 'd', 'f', 'v'],
    'v': ['c', 'd', 'f', 'g', 'b'],
    'b': ['v', 'f', 'g', 'h', 'n'],
    'n': ['b', 'g', 'h', 'j', 'm'],
    'm': ['n', 'h', 'j', 'k', ','],
    ',': ['m', 'j', 'k', 'l', '.'],
    '.': [',', 'k', 'l', ';', '/'],
    '/': ['.', 'l', ';', "'"],
    ' ': ['c', 'v', 'b', 'n', 'm'],
}
