"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const chalk_1 = require("chalk");
const path = require("path");
/**
 * Returns the sourcefile containing the given TypeScript node.
 */
function getSourceFile(node) {
    while (node.parent) {
        node = node.parent;
    }
    return node;
}
function repeat(str, times) {
    let output = '';
    for (let i = 0; i < times; i++) {
        output += str;
    }
    return output;
}
function leftPad(str, length) {
    while (str.length < length) {
        str = ` ${str}`;
    }
    return str;
}
function posToLine(src, position) {
    const after = src.split('\n').map((content, n) => ({ content, number: n + 1 }));
    const before = [];
    while (after.length > 0 && position > after[0].content.length) {
        const line = after.shift();
        before.push(line);
        position -= line.content.length + 1;
    }
    return {
        before,
        after,
        char: position - 1,
        line: after.shift() || { content: '', number: before.length },
    };
}
function lineNo(i) {
    return chalk_1.default.gray(`${leftPad(String(i), 4)} | `);
}
function annotateError(node, message, config) {
    const source = getSourceFile(node);
    // ts can report the store of the node from the end of the last node, which
    // may contain whitespace and throw things off. Fix that.
    while (/\s/.test(source.text[node.pos])) {
        node.pos++;
    }
    const { after, before, line, char } = posToLine(source.text, node.pos);
    const end = node.end - (node.pos - char) + 1;
    const printLines = (lines) => {
        return lines.map(l => lineNo(l.number) + chalk_1.default.gray(l.content)).join('\n');
    };
    return [
        `Error at ${path.relative(config.relativePath, source.fileName)}:${before.length + 1}`,
        '',
        printLines(before.slice(-config.surroundingLines)),
        lineNo(line.number) +
            chalk_1.default.reset(line.content.slice(0, char)) +
            chalk_1.default.red(line.content.slice(char, Math.min(line.content.length, end))) +
            chalk_1.default.reset(line.content.slice(end)),
        lineNo('') + repeat(' ', char) + chalk_1.default.yellow(`^${repeat('-', end - char - 1)}`),
        printLines(after.slice(0, config.surroundingLines)),
        `\n${chalk_1.default.bold(chalk_1.default.red('ERROR:'))} ${message}`,
    ]
        .map(l => chalk_1.default.reset(l))
        .join('\n');
}
var ErrorCode;
(function (ErrorCode) {
    ErrorCode[ErrorCode["InvalidArgumentCount"] = 0] = "InvalidArgumentCount";
    ErrorCode[ErrorCode["InvalidArgumentType"] = 1] = "InvalidArgumentType";
    ErrorCode[ErrorCode["InvalidPropertyAssignment"] = 2] = "InvalidPropertyAssignment";
    ErrorCode[ErrorCode["InvalidEnumAccess"] = 3] = "InvalidEnumAccess";
    ErrorCode[ErrorCode["InvalidComplexExpression"] = 4] = "InvalidComplexExpression";
    ErrorCode[ErrorCode["RedeclaredControl"] = 5] = "RedeclaredControl";
    ErrorCode[ErrorCode["RequiredSceneID"] = 6] = "RequiredSceneID";
    ErrorCode[ErrorCode["MissingResourceDecorator"] = 7] = "MissingResourceDecorator";
    ErrorCode[ErrorCode["InvalidDecoratorTarget"] = 8] = "InvalidDecoratorTarget";
    ErrorCode[ErrorCode["CouldNotInferKind"] = 9] = "CouldNotInferKind";
    ErrorCode[ErrorCode["DangerousEval"] = 10] = "DangerousEval";
})(ErrorCode = exports.ErrorCode || (exports.ErrorCode = {}));
/**
 * DeclarationError is an Error type thrown when some invalid code is
 * encountered. It creates a pretty annotated error.
 */
class DeclarationError extends Error {
    constructor(code, node, originalMessage, config) {
        super(annotateError(node, originalMessage, config || DeclarationError.annotationConfig));
        this.code = code;
        this.node = node;
        this.originalMessage = originalMessage;
        this.dontColor = true;
    }
    /**
     * Converts the error to a JSON representation.
     */
    toJSON() {
        return {
            code: this.code,
            codeName: ErrorCode[this.code],
            file: this.node.getSourceFile().fileName,
            charStart: this.node.pos,
            chatEnd: this.node.end,
            message: this.originalMessage,
        };
    }
    /**
     * @override
     */
    getHumanMessage() {
        return this.message;
    }
    /**
     * Creates a new DeclarationError using a relative path for the source file.
     */
    makeRelative(relativePath) {
        return new DeclarationError(this.code, this.node, this.originalMessage, Object.assign({}, DeclarationError.annotationConfig, { relativePath }));
    }
}
DeclarationError.annotationConfig = {
    surroundingLines: 1,
    relativePath: '',
};
exports.DeclarationError = DeclarationError;
