"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSemanticHighlights = void 0;
const traverse_1 = require("@glas/traverse");
const ast_1 = require("./ast");
const parser_1 = __importDefault(require("./parser"));
const reservedWords_1 = __importDefault(require("./reservedWords"));
const parser = parser_1.default();
function add(position, offset) {
    return { line: position.line, column: position.column + offset };
}
const keywordTokenTypes = {
    let: ["macro"],
    var: ["macro"],
    type: ["struct"],
    struct: ["macro"],
    class: ["macro"],
    data: ["keyword"],
};
const declarationTokenTypes = ["variable", "readonly"];
const typeTokenTypes = ["type"];
function isType(name) {
    return name[0] === name[0].toUpperCase();
}
function getSemanticHighlights(text, fileName) {
    let results = new Array();
    function push(locationOrNode, ...tokenTypeAndModifiers) {
        var _a;
        let [tokenType, ...modifiers] = tokenTypeAndModifiers;
        let { start, end } = (_a = locationOrNode.location) !== null && _a !== void 0 ? _a : locationOrNode;
        let length = (start.line === end.line ? end.column : lines[start.line].length + 1) - start.column;
        //	TODO: add absolute positions to location information to simplify length calculation.
        // console.log(tokenType + ":" + modifiers.join(","))
        results.push({
            line: start.line - 1,
            column: start.column - 1,
            length,
            tokenType,
            modifiers
        });
    }
    function highlightStartingKeywords(line, max = 3) {
        let lineText = lines[line];
        let words = lineText.trim().split(/\s+/).slice(0, max);
        for (let word of words) {
            let tokenType = keywordTokenTypes[word];
            if (tokenType == null) {
                if (reservedWords_1.default.has(word)) {
                    tokenType = ["keyword"];
                }
            }
            if (Array.isArray(tokenType)) {
                let column = lineText.indexOf(word) + 1;
                push({ start: { line, column }, end: { line, column: column + word.length } }, tokenType[0], ...tokenType.slice(1));
            }
        }
    }
    let lines = ["", ...text.split(/\r\n|\n/)]; // we add "" to 0 so lines are indexed starting at 1
    let ast = parser.parse(text, fileName);
    traverse_1.traverse(ast, {
        enter(node, ancestors) {
            var _a, _b;
            // console.log("????????" + node.constructor.name, node)
            if (node.location == null) {
                return;
            }
            //  DO NOT highlight all declarators, their containers should highlight them.
            //  this also causes a problem because there is a 'Declarator' at the start of each module for it's module.id
            if (ast_1.Declarator.is(node)) {
                let parent = ancestors[ancestors.length - 1];
                let gparent = ancestors[ancestors.length - 2];
                if (ast_1.Parameter.is(parent) || ast_1.Pattern.is(ancestors[ancestors.length - 2])) {
                    push(node, ...(isType(node.name) ? typeTokenTypes : declarationTokenTypes));
                }
            }
            if (ast_1.ThisExpression.is(node)) {
                push(node, "keyword");
            }
            if (ast_1.BinaryExpression.is(node)) {
                if (reservedWords_1.default.has(node.operator)) {
                    push({ start: node.left.location.end, end: node.right.location.start }, "keyword");
                }
            }
            if (ast_1.ModuleSpecifier.is(node)) {
                highlightStartingKeywords(node.location.start.line, 2);
                push(node.local, ...(isType(node.local.name) ? typeTokenTypes : declarationTokenTypes));
            }
            if (ast_1.MemberExpression.is(node)) {
                if (ast_1.Identifier.is(node.property)) {
                    push(node.property, ...declarationTokenTypes);
                }
            }
            if (ast_1.Property.is(node)) {
                if (ast_1.Identifier.is(node.key)) {
                    push(node.key, ...declarationTokenTypes);
                }
            }
            if (ast_1.ImportDeclaration.is(node)) {
                highlightStartingKeywords(node.location.start.line, 1);
                if (node.path) {
                    const last = node.path[node.path.length - 1];
                    let isAutoImport = ast_1.Identifier.is(last) && node.specifiers.find(s => {
                        if ((ast_1.ImportNamespaceSpecifier.is(s) || ast_1.ImportDefaultSpecifier.is(s)) && s.local.name === last.name) {
                            return true;
                        }
                    });
                    node.specifiers.length === 1 && ((_a = node.specifiers[0].location) === null || _a === void 0 ? void 0 : _a.start.line) === ((_b = node.location) === null || _b === void 0 ? void 0 : _b.start.line);
                    for (let step of node.path) {
                        if (ast_1.Identifier.is(step)) {
                            let isLast = node.path[node.path.length - 1] === step;
                            push(step, ...(isLast && isAutoImport ? (isType(step.name) ? typeTokenTypes : declarationTokenTypes) : ["variable"]));
                        }
                    }
                }
                else {
                    // no path, highlight string
                    push(node.source, "string");
                }
            }
            if (ast_1.TypeExpression.is(node)) {
                push(node.value, ...typeTokenTypes);
            }
            if (ast_1.IfStatement.is(node)) {
                // colorize the if
                push({ start: node.location.start, end: add(node.location.start, "if".length) }, "keyword");
                if (node.alternate) {
                    highlightStartingKeywords(node.alternate.location.start.line, 1);
                }
            }
            if (ast_1.ForOfStatement.is(node) || ast_1.ForStatement.is(node)) {
                push({ start: node.location.start, end: add(node.location.start, "for".length) }, "keyword");
                if (ast_1.ForOfStatement.is(node)) {
                    // highlight the "in"
                    push({ start: add((node.count || node.left).id.location.end, 0), end: add(node.right.location.start, 0) }, "keyword");
                }
            }
            if (ast_1.BreakStatement.is(node) || ast_1.ContinueStatement.is(node)) {
                push(node, "keyword");
            }
            if (ast_1.ReturnStatement.is(node) || ast_1.YieldExpression.is(node) || ast_1.AwaitExpression.is(node)) {
                highlightStartingKeywords(node.location.start.line, 1);
            }
            if (ast_1.UnaryExpression.is(node) && node.operator === "delete") {
                highlightStartingKeywords(node.location.start.line, 1);
            }
            if (ast_1.FunctionExpression.is(node)) {
                if (node.async) {
                    // ideally this would be a token or location.
                    push({ start: node.location.start, end: add(node.location.start, "async".length) }, "keyword");
                }
                if (node.id) {
                    push(node.id, "function");
                }
            }
            if (ast_1.CallExpression.is(node)) {
                let callee = node.callee;
                if (node.new) {
                    push({ start: node.location.start, end: add(node.location.start, "new".length) }, "keyword");
                }
                if (ast_1.MemberExpression.is(callee)) {
                    callee = callee.property;
                }
                if (ast_1.Identifier.is(callee)) {
                    push(callee, "function");
                }
            }
            // if (Typed.is(node)) {
            //     if (node.type) {
            //         push(node.type, "type");
            //     }
            // }
            if (ast_1.VariableDeclaration.is(node)) {
                // let right = (node.export === 2 ? node.value : node.id)!.location;
                // this will keyword color the let/const and export [default]
                highlightStartingKeywords(node.location.start.line);
                if (ast_1.Identifier.is(node.id)) {
                    if (ast_1.FunctionExpression.is(node.value)) {
                        if (!reservedWords_1.default.has(node.id.name)) {
                            push(node.id, "function");
                        }
                    }
                    else {
                        push(node.id, ...declarationTokenTypes);
                    }
                }
            }
            if (ast_1.ThrowStatement.is(node)) {
                highlightStartingKeywords(node.location.start.line, 1);
            }
            if (ast_1.TryStatement.is(node)) {
                highlightStartingKeywords(node.location.start.line, 1);
                if (node.handler) {
                    highlightStartingKeywords(node.handler.location.start.line, 1);
                }
                if (node.finalizer) {
                    highlightStartingKeywords(node.finalizer.location.start.line, 1);
                }
            }
            if (ast_1.ClassDeclaration.is(node)) {
                highlightStartingKeywords(node.location.start.line, 4);
                push(node.id, "struct");
            }
            if (ast_1.ElementExpression.is(node)) {
                push(node.kind, "struct");
                if (node.close) {
                    push(node.close, "struct");
                }
            }
            if (ast_1.Literal.is(node) && !ast_1.ImportDeclaration.is(ancestors[ancestors.length - 1])) {
                if (typeof node.value === "string") {
                    push(node, "string");
                    // could be an outline string.
                    let parent = ancestors[ancestors.length - 1];
                    if (node.location.start.line < node.location.end.line) {
                        for (let line = node.location.start.line + 1; line < node.location.end.line; line++) {
                            let lineText = lines[line];
                            let lineTrimmed = lineText.trimStart();
                            let indent = lineText.length - lineTrimmed.length;
                            if (indent <= 1 || ast_1.VariableDeclaration.is(parent) && indent <= parent.location.start.column) {
                                // outdent occurred as comments but our greedy parser consumed the comments.
                                break;
                            }
                            push({ start: { line, column: 1 }, end: { line, column: 100 } }, "string");
                        }
                    }
                }
                else if (typeof node.value === "number") {
                    push(node, "number");
                }
                else if (typeof node.value === "boolean") {
                    push(node, "number");
                }
                else if (node.value === null) {
                    push(node, "variable");
                }
            }
            if (ast_1.RegularExpression.is(node)) {
                push(node, "regexp");
            }
            if (ast_1.Reference.is(node)) {
                push(node, ...(isType(node.name) ? typeTokenTypes : ["variable"]));
            }
        }
    });
    // finally, let's highlight comments.
    for (let line = 1; line < lines.length; line++) {
        let lineText = lines[line];
        if (lineText.trim().startsWith("//")) {
            push({ start: { line, column: 1 }, end: { line, column: lineText.length + 1 } }, "comment");
        }
    }
    return results;
}
exports.getSemanticHighlights = getSemanticHighlights;
