import { isNotWhitespace } from './ast.js';
import { isBeginningOfElement } from './commons.js';
import { isBeginningOfLine } from './commons.js';
import { getFirstAndLastTokens } from './commons.js';
import { isClosingParenToken, isOpeningParenToken } from '@eslint-community/eslint-utils';
const PREFORMATTED_ELEMENT_NAMES = ['pre', 'textarea', 'template'];
/**
 * Creates AST event handlers for svelte nodes.
 *
 * @param context The rule context.
 * @returns AST event handlers.
 */
export function defineVisitor(context) {
    const { sourceCode, offsets, options } = context;
    const visitor = {
        // ----------------------------------------------------------------------
        // ELEMENTS
        // ----------------------------------------------------------------------
        SvelteScriptElement(node) {
            offsets.setOffsetElementList(node.body, node.startTag, node.endTag, options.indentScript ? 1 : 0);
        },
        SvelteStyleElement(node) {
            node.children.forEach((n) => offsets.ignore(n));
        },
        SvelteElement(node) {
            if (node.name.type === 'Identifier' || node.name.type === 'SvelteName') {
                if (PREFORMATTED_ELEMENT_NAMES.includes(node.name.name)) {
                    const startTagToken = sourceCode.getFirstToken(node);
                    const endTagToken = node.endTag && sourceCode.getFirstToken(node.endTag);
                    offsets.setOffsetToken(endTagToken, 0, startTagToken);
                    node.children.forEach((n) => offsets.ignore(n));
                    return;
                }
                if (node.name.name === 'style') {
                    // Inline style tag
                    node.children.forEach((n) => offsets.ignore(n));
                    return;
                }
            }
            if (node.endTag) {
                offsets.setOffsetElementList(node.children.filter(isNotEmptyTextNode), node.startTag, node.endTag, 1);
            }
        },
        // ----------------------------------------------------------------------
        // TAGS
        // ----------------------------------------------------------------------
        SvelteStartTag(node) {
            const openToken = sourceCode.getFirstToken(node);
            const closeToken = sourceCode.getLastToken(node);
            offsets.setOffsetElementList(node.attributes, openToken, closeToken, 1, options.alignAttributesVertically);
            if (node.selfClosing) {
                const slash = sourceCode.getTokenBefore(closeToken);
                if (slash.value === '/') {
                    offsets.setOffsetToken(slash, 0, openToken);
                }
            }
        },
        SvelteEndTag(node) {
            const openToken = sourceCode.getFirstToken(node);
            const closeToken = sourceCode.getLastToken(node);
            offsets.setOffsetElementList([], openToken, closeToken, 1);
        },
        // ----------------------------------------------------------------------
        // ATTRIBUTES
        // ----------------------------------------------------------------------
        SvelteAttribute(node) {
            const keyToken = sourceCode.getFirstToken(node);
            const eqToken = sourceCode.getTokenAfter(node.key);
            if (eqToken != null && eqToken.range[1] <= node.range[1]) {
                offsets.setOffsetToken(eqToken, 1, keyToken);
                const valueStartToken = sourceCode.getTokenAfter(eqToken);
                if (valueStartToken != null && valueStartToken.range[1] <= node.range[1]) {
                    offsets.setOffsetToken(valueStartToken, 1, keyToken);
                    const values = node.type === 'SvelteAttribute' || node.type === 'SvelteStyleDirective'
                        ? node.value
                        : [];
                    // process quoted
                    let processedValues = false;
                    if (valueStartToken.type === 'Punctuator') {
                        const quoted = ['"', "'"].includes(valueStartToken.value);
                        const mustache = !quoted && valueStartToken.value === '{';
                        if (quoted || mustache) {
                            const last = sourceCode.getLastToken(node);
                            if (last.type === 'Punctuator' &&
                                ((quoted && last.value === valueStartToken.value) ||
                                    (mustache && last.value === '}'))) {
                                offsets.setOffsetToken(last, 0, valueStartToken);
                                offsets.setOffsetElementList(values, valueStartToken, last, 1);
                                processedValues = true;
                            }
                        }
                    }
                    if (!processedValues) {
                        for (const val of values) {
                            const token = sourceCode.getFirstToken(val);
                            offsets.setOffsetToken(token, 0, valueStartToken);
                        }
                    }
                }
            }
        },
        SvelteDirective(node) {
            visitor.SvelteAttribute(node);
        },
        SvelteStyleDirective(node) {
            visitor.SvelteAttribute(node);
        },
        SvelteSpecialDirective(node) {
            visitor.SvelteAttribute(node);
        },
        SvelteShorthandAttribute(node) {
            const openToken = sourceCode.getFirstToken(node);
            const closeToken = sourceCode.getLastToken(node);
            offsets.setOffsetElementList([], openToken, closeToken, 1);
        },
        SvelteSpreadAttribute(node) {
            visitor.SvelteShorthandAttribute(node);
        },
        // ----------------------------------------------------------------------
        // ATTRIBUTE KEYS
        // ----------------------------------------------------------------------
        SvelteDirectiveKey(_node) {
            // noop
        },
        SvelteSpecialDirectiveKey(_node) {
            // noop
        },
        // ----------------------------------------------------------------------
        // CONTENTS
        // ----------------------------------------------------------------------
        SvelteText(node) {
            const tokens = sourceCode.getTokens(node, {
                filter: isNotWhitespace,
                includeComments: false
            });
            const first = tokens.shift();
            if (!first) {
                return;
            }
            offsets.setOffsetToken(tokens, isBeginningOfLine(sourceCode, first) ? 0 : isBeginningOfElement(node) ? 1 : 0, first);
        },
        SvelteLiteral(node) {
            const tokens = sourceCode.getTokens(node, {
                filter: isNotWhitespace,
                includeComments: false
            });
            const first = tokens.shift();
            if (!first) {
                return;
            }
            offsets.setOffsetToken(tokens, isBeginningOfLine(sourceCode, first) ? 0 : 1, first);
        },
        // ----------------------------------------------------------------------
        // MUSTACHE TAGS
        // ----------------------------------------------------------------------
        SvelteMustacheTag(node) {
            const openToken = sourceCode.getFirstToken(node);
            const closeToken = sourceCode.getLastToken(node);
            offsets.setOffsetElementList([node.expression], openToken, closeToken, 1);
        },
        SvelteDebugTag(node) {
            const openToken = sourceCode.getFirstToken(node);
            const closeToken = sourceCode.getLastToken(node);
            offsets.setOffsetElementList(node.identifiers, openToken, closeToken, 1);
        },
        SvelteConstTag(node) {
            const openToken = sourceCode.getFirstToken(node);
            const constToken = sourceCode.getTokenAfter(openToken);
            const declarationToken = sourceCode.getFirstToken(node.declaration);
            const closeToken = sourceCode.getLastToken(node);
            offsets.setOffsetToken(constToken, 1, openToken);
            offsets.setOffsetToken(declarationToken, 1, openToken);
            offsets.setOffsetToken(closeToken, 0, openToken);
        },
        SvelteRenderTag(node) {
            const openToken = sourceCode.getFirstToken(node);
            const renderToken = sourceCode.getTokenAfter(openToken);
            offsets.setOffsetToken(renderToken, 1, openToken);
            offsets.setOffsetToken(node.expression, 1, renderToken);
        },
        // ----------------------------------------------------------------------
        // BLOCKS
        // ----------------------------------------------------------------------
        SvelteIfBlock(node) {
            const [openToken, ...ifTokens] = sourceCode.getFirstTokens(node, {
                count: node.elseif ? 3 : 2,
                includeComments: false
            });
            offsets.setOffsetToken(ifTokens, 1, openToken);
            const exp = getFirstAndLastTokens(sourceCode, node.expression);
            offsets.setOffsetToken(exp.firstToken, 1, ifTokens[0]);
            const closeOpenTagToken = sourceCode.getTokenAfter(exp.lastToken);
            offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
            for (const child of node.children) {
                const token = sourceCode.getFirstToken(child, {
                    includeComments: false,
                    filter: isNotWhitespace
                });
                offsets.setOffsetToken(token, 1, openToken);
            }
            if (node.else) {
                offsets.setOffsetToken(sourceCode.getFirstToken(node.else), 0, openToken);
                if (node.else.elseif) {
                    // else if
                    return;
                }
            }
            const [openCloseTagToken, endIfToken, closeCloseTagToken] = sourceCode.getLastTokens(node, {
                count: 3,
                includeComments: false
            });
            offsets.setOffsetToken(openCloseTagToken, 0, openToken);
            offsets.setOffsetToken(endIfToken, 1, openCloseTagToken);
            offsets.setOffsetToken(closeCloseTagToken, 0, openCloseTagToken);
        },
        SvelteElseBlock(node) {
            if (node.elseif) {
                return;
            }
            const [openToken, elseToken, closeToken] = sourceCode.getFirstTokens(node, {
                count: 3,
                includeComments: false
            });
            offsets.setOffsetToken(elseToken, 1, openToken);
            offsets.setOffsetToken(closeToken, 0, openToken);
            for (const child of node.children) {
                const token = sourceCode.getFirstToken(child, {
                    includeComments: false,
                    filter: isNotWhitespace
                });
                offsets.setOffsetToken(token, 1, openToken);
            }
        },
        SvelteEachBlock(node) {
            const [openToken, eachToken] = sourceCode.getFirstTokens(node, {
                count: 2,
                includeComments: false
            });
            offsets.setOffsetToken(eachToken, 1, openToken);
            offsets.setOffsetElementList([node.expression, node.context, node.index], eachToken, null, 1);
            if (node.key) {
                const key = getFirstAndLastTokens(sourceCode, node.key);
                offsets.setOffsetToken(key.firstToken, 1, eachToken);
                const closeOpenTagToken = sourceCode.getTokenAfter(key.lastToken);
                offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
            }
            else {
                const closeOpenTagToken = sourceCode.getTokenAfter(node.index || node.context || node.expression);
                offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
            }
            for (const child of node.children) {
                const token = sourceCode.getFirstToken(child, {
                    includeComments: false,
                    filter: isNotWhitespace
                });
                offsets.setOffsetToken(token, 1, openToken);
            }
            if (node.else) {
                offsets.setOffsetToken(sourceCode.getFirstToken(node.else), 0, openToken);
            }
            const [openCloseTagToken, endEachToken, closeCloseTagToken] = sourceCode.getLastTokens(node, {
                count: 3,
                includeComments: false
            });
            offsets.setOffsetToken(openCloseTagToken, 0, openToken);
            offsets.setOffsetToken(endEachToken, 1, openCloseTagToken);
            offsets.setOffsetToken(closeCloseTagToken, 0, openCloseTagToken);
        },
        SvelteAwaitBlock(node) {
            const [openToken, awaitToken] = sourceCode.getFirstTokens(node, {
                count: 2,
                includeComments: false
            });
            offsets.setOffsetToken(awaitToken, 1, openToken);
            const exp = getFirstAndLastTokens(sourceCode, node.expression);
            offsets.setOffsetToken(exp.firstToken, 1, awaitToken);
            if (node.pending) {
                const closeOpenTagToken = sourceCode.getTokenAfter(exp.lastToken);
                offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
                offsets.setOffsetToken(sourceCode.getFirstToken(node.pending, {
                    includeComments: false,
                    filter: isNotWhitespace
                }), 1, openToken);
            }
            if (node.then) {
                if (node.kind === 'await-then') {
                    // {#await expression then value}
                    const thenToken = sourceCode.getTokenAfter(exp.lastToken);
                    offsets.setOffsetToken(thenToken, 1, openToken);
                    if (node.then.value) {
                        offsets.setOffsetToken(sourceCode.getFirstToken(node.then.value), 1, thenToken);
                    }
                    const closeOpenTagToken = sourceCode.getTokenAfter(node.then.value || thenToken);
                    offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
                }
                else {
                    // {:then value}
                    offsets.setOffsetToken(sourceCode.getFirstToken(node.then), 0, openToken);
                }
            }
            if (node.catch) {
                if (node.kind === 'await-catch') {
                    // {#await expression catch error}
                    const catchToken = sourceCode.getTokenAfter(exp.lastToken);
                    offsets.setOffsetToken(catchToken, 1, openToken);
                    if (node.catch.error) {
                        offsets.setOffsetToken(sourceCode.getFirstToken(node.catch.error), 1, catchToken);
                    }
                    const closeOpenTagToken = sourceCode.getTokenAfter(node.catch.error || catchToken);
                    offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
                }
                else {
                    // {:catch value}
                    offsets.setOffsetToken(sourceCode.getFirstToken(node.catch), 0, openToken);
                }
            }
            const [openCloseTagToken, endAwaitToken, closeCloseTagToken] = sourceCode.getLastTokens(node, {
                count: 3,
                includeComments: false
            });
            offsets.setOffsetToken(openCloseTagToken, 0, openToken);
            offsets.setOffsetToken(endAwaitToken, 1, openCloseTagToken);
            offsets.setOffsetToken(closeCloseTagToken, 0, openCloseTagToken);
        },
        SvelteAwaitPendingBlock(node) {
            const openToken = sourceCode.getFirstToken(node);
            for (const child of node.children) {
                const token = sourceCode.getFirstToken(child, {
                    includeComments: false,
                    filter: isNotWhitespace
                });
                offsets.setOffsetToken(token, 1, openToken);
            }
        },
        SvelteAwaitThenBlock(node) {
            if (!node.awaitThen) {
                // {:then value}
                const [openToken, thenToken] = sourceCode.getFirstTokens(node, {
                    count: 2,
                    includeComments: false
                });
                offsets.setOffsetToken(thenToken, 1, openToken);
                if (node.value) {
                    const valueToken = sourceCode.getFirstToken(node.value);
                    offsets.setOffsetToken(valueToken, 1, thenToken);
                }
                const closeOpenTagToken = sourceCode.getTokenAfter(node.value || thenToken);
                offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
            }
            const openToken = sourceCode.getFirstToken(node);
            for (const child of node.children) {
                const token = sourceCode.getFirstToken(child, {
                    includeComments: false,
                    filter: isNotWhitespace
                });
                offsets.setOffsetToken(token, 1, openToken);
            }
        },
        SvelteAwaitCatchBlock(node) {
            if (!node.awaitCatch) {
                // {:catch error}
                const [openToken, catchToken] = sourceCode.getFirstTokens(node, {
                    count: 2,
                    includeComments: false
                });
                offsets.setOffsetToken(catchToken, 1, openToken);
                if (node.error) {
                    const errorToken = sourceCode.getFirstToken(node.error);
                    offsets.setOffsetToken(errorToken, 1, catchToken);
                }
                const closeOpenTagToken = sourceCode.getTokenAfter(node.error || catchToken);
                offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
            }
            const openToken = sourceCode.getFirstToken(node);
            for (const child of node.children) {
                const token = sourceCode.getFirstToken(child, {
                    includeComments: false,
                    filter: isNotWhitespace
                });
                offsets.setOffsetToken(token, 1, openToken);
            }
        },
        SvelteKeyBlock(node) {
            const [openToken, keyToken] = sourceCode.getFirstTokens(node, {
                count: 2,
                includeComments: false
            });
            offsets.setOffsetToken(keyToken, 1, openToken);
            const exp = getFirstAndLastTokens(sourceCode, node.expression);
            offsets.setOffsetToken(exp.firstToken, 1, keyToken);
            const closeOpenTagToken = sourceCode.getTokenAfter(exp.lastToken);
            offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
            for (const child of node.children) {
                const token = sourceCode.getFirstToken(child, {
                    includeComments: false,
                    filter: isNotWhitespace
                });
                offsets.setOffsetToken(token, 1, openToken);
            }
            const [openCloseTagToken, endKeyToken, closeCloseTagToken] = sourceCode.getLastTokens(node, {
                count: 3,
                includeComments: false
            });
            offsets.setOffsetToken(openCloseTagToken, 0, openToken);
            offsets.setOffsetToken(endKeyToken, 1, openCloseTagToken);
            offsets.setOffsetToken(closeCloseTagToken, 0, openCloseTagToken);
        },
        SvelteSnippetBlock(node) {
            const [openToken, snippetToken] = sourceCode.getFirstTokens(node, {
                count: 2,
                includeComments: false
            });
            offsets.setOffsetToken(snippetToken, 1, openToken);
            const snippetName = sourceCode.getTokenAfter(snippetToken);
            offsets.setOffsetToken(snippetName, 1, snippetToken);
            const leftParenToken = sourceCode.getTokenBefore(node.params[0] || sourceCode.getLastToken(node), {
                filter: isOpeningParenToken,
                includeComments: false
            });
            const rightParenToken = sourceCode.getTokenAfter(node.params[node.params.length - 1] || leftParenToken, {
                filter: isClosingParenToken,
                includeComments: false
            });
            offsets.setOffsetToken(leftParenToken, 1, snippetName);
            offsets.setOffsetElementList(node.params, leftParenToken, rightParenToken, 1);
            const closeOpenTagToken = sourceCode.getTokenAfter(rightParenToken);
            offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
            for (const child of node.children) {
                const token = sourceCode.getFirstToken(child, {
                    includeComments: false,
                    filter: isNotWhitespace
                });
                offsets.setOffsetToken(token, 1, openToken);
            }
            const [openCloseTagToken, endSnippetToken, closeCloseTagToken] = sourceCode.getLastTokens(node, { count: 3, includeComments: false });
            offsets.setOffsetToken(openCloseTagToken, 0, openToken);
            offsets.setOffsetToken(endSnippetToken, 1, openCloseTagToken);
            offsets.setOffsetToken(closeCloseTagToken, 0, openCloseTagToken);
        },
        // ----------------------------------------------------------------------
        // COMMENTS
        // ----------------------------------------------------------------------
        SvelteHTMLComment(_node) {
            // noop
        },
        // ----------------------------------------------------------------------
        // NAMES
        // ----------------------------------------------------------------------
        SvelteName(_node) {
            // noop
        },
        SvelteMemberExpressionName(_node) {
            // noop
        }
    };
    return visitor;
}
/**
 * Check whether the given node is not an empty text node.
 * @param node The node to check.
 * @returns `false` if the token is empty text node.
 */
function isNotEmptyTextNode(node) {
    return !(node.type === 'SvelteText' && node.value.trim() === '');
}
