

function parse(input, errors = [], options = {}) {
    const lines = input.length === 0 ? [] : input.split(/\r\n|\n/);
    const parser = ( new YamlParser(lines, errors, options));
    return parser.parse();
}
function createPosition(line, character) {
    return {
        line,
        character
    };
}
function createStringNode(value, start, end) {
    return {
        type: "string",
        value,
        start,
        end
    };
}
function createNumberNode(value, start, end) {
    return {
        type: "number",
        value,
        start,
        end
    };
}
function createBooleanNode(value, start, end) {
    return {
        type: "boolean",
        value,
        start,
        end
    };
}
function createNullNode(start, end) {
    return {
        type: "null",
        value: null,
        start,
        end
    };
}
function createObjectNode(properties, start, end) {
    return {
        type: "object",
        start,
        end,
        properties
    };
}
function createArrayNode(items, start, end) {
    return {
        type: "array",
        start,
        end,
        items
    };
}
function isWhitespace(char) {
    return char === " " || char === "\t";
}
function isValidNumber(value) {
    return /^-?\d*\.?\d+$/.test(value);
}
class YamlLexer {
    constructor(lines) {
        this.currentLine = 0;
        this.currentChar = 0;
        this.lines = lines;
    }
    getCurrentPosition() {
        return createPosition(this.currentLine, this.currentChar);
    }
    getCurrentLineNumber() {
        return this.currentLine;
    }
    getCurrentCharNumber() {
        return this.currentChar;
    }
    getCurrentLineText() {
        return this.currentLine < this.lines.length ? this.lines[this.currentLine] : "";
    }
    savePosition() {
        return {
            line: this.currentLine,
            char: this.currentChar
        };
    }
    restorePosition(pos) {
        this.currentLine = pos.line;
        this.currentChar = pos.char;
    }
    isAtEnd() {
        return this.currentLine >= this.lines.length;
    }
    getCurrentChar() {
        if (this.isAtEnd() || this.currentChar >= this.lines[this.currentLine].length) {
            return "";
        }
        return this.lines[this.currentLine][this.currentChar];
    }
    peek(offset = 1) {
        const newChar = this.currentChar + offset;
        if (this.currentLine >= this.lines.length || newChar >= this.lines[this.currentLine].length) {
            return "";
        }
        return this.lines[this.currentLine][newChar];
    }
    advance() {
        const char = this.getCurrentChar();
        if (this.currentChar >= this.lines[this.currentLine].length && this.currentLine < this.lines.length - 1) {
            this.currentLine++;
            this.currentChar = 0;
        } else {
            this.currentChar++;
        }
        return char;
    }
    advanceLine() {
        this.currentLine++;
        this.currentChar = 0;
    }
    skipWhitespace() {
        while (!this.isAtEnd() && this.currentChar < this.lines[this.currentLine].length && isWhitespace(this.getCurrentChar())) {
            this.advance();
        }
    }
    skipToEndOfLine() {
        this.currentChar = this.lines[this.currentLine].length;
    }
    getIndentation() {
        if (this.isAtEnd()) {
            return 0;
        }
        let indent = 0;
        for (let i = 0; i < this.lines[this.currentLine].length; i++) {
            if (this.lines[this.currentLine][i] === " ") {
                indent++;
            } else if (this.lines[this.currentLine][i] === "\t") {
                indent += 4;
            } else {
                break;
            }
        }
        return indent;
    }
    moveToNextNonEmptyLine() {
        while (this.currentLine < this.lines.length) {
            if (this.currentChar < this.lines[this.currentLine].length) {
                const remainingLine = this.lines[this.currentLine].substring(this.currentChar).trim();
                if (remainingLine.length > 0 && !remainingLine.startsWith("#")) {
                    this.skipWhitespace();
                    return;
                }
            }
            this.currentLine++;
            this.currentChar = 0;
            if (this.currentLine < this.lines.length) {
                const line = this.lines[this.currentLine].trim();
                if (line.length > 0 && !line.startsWith("#")) {
                    this.skipWhitespace();
                    return;
                }
            }
        }
    }
}
class YamlParser {
    constructor(lines, errors, options) {
        this.flowLevel = 0;
        this.lexer = ( new YamlLexer(lines));
        this.errors = errors;
        this.options = options;
    }
    addError(message, code, start, end) {
        this.errors.push({
            message,
            code,
            start,
            end
        });
    }
    parseValue(expectedIndent) {
        this.lexer.skipWhitespace();
        if (this.lexer.isAtEnd()) {
            const pos = this.lexer.getCurrentPosition();
            return createStringNode("", pos, pos);
        }
        const char = this.lexer.getCurrentChar();
        if (char === "\"" || char === `'`) {
            return this.parseQuotedString(char);
        }
        if (char === "[") {
            return this.parseInlineArray();
        }
        if (char === "{") {
            return this.parseInlineObject();
        }
        return this.parseUnquotedValue();
    }
    parseQuotedString(quote) {
        const start = this.lexer.getCurrentPosition();
        this.lexer.advance();
        let value = "";
        while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== "" && this.lexer.getCurrentChar() !== quote) {
            value += this.lexer.advance();
        }
        if (this.lexer.getCurrentChar() === quote) {
            this.lexer.advance();
        }
        const end = this.lexer.getCurrentPosition();
        return createStringNode(value, start, end);
    }
    parseUnquotedValue() {
        const start = this.lexer.getCurrentPosition();
        let value = "";
        let endPos = start;
        const isTerminator = char => {
            if (char === "#") {
                return true;
            }
            if (this.flowLevel > 0 && (char === "," || char === "]" || char === "}")) {
                return true;
            }
            return false;
        };
        const firstChar = this.lexer.getCurrentChar();
        if (firstChar === "\"" || firstChar === `'`) {
            value += this.lexer.advance();
            endPos = this.lexer.getCurrentPosition();
            while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== "") {
                const char = this.lexer.getCurrentChar();
                if (char === firstChar || isTerminator(char)) {
                    break;
                }
                value += this.lexer.advance();
                endPos = this.lexer.getCurrentPosition();
            }
        } else {
            while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== "") {
                const char = this.lexer.getCurrentChar();
                if (isTerminator(char)) {
                    break;
                }
                value += this.lexer.advance();
                endPos = this.lexer.getCurrentPosition();
            }
        }
        const trimmed = value.trimEnd();
        const diff = value.length - trimmed.length;
        if (diff) {
            endPos = createPosition(start.line, endPos.character - diff);
        }
        const finalValue = (firstChar === "\"" || firstChar === `'`) ? trimmed.substring(1) : trimmed;
        return this.createValueNode(finalValue, start, endPos);
    }
    createValueNode(value, start, end) {
        if (value === "") {
            return createStringNode("", start, start);
        }
        if (value === "true") {
            return createBooleanNode(true, start, end);
        }
        if (value === "false") {
            return createBooleanNode(false, start, end);
        }
        if (value === "null" || value === "~") {
            return createNullNode(start, end);
        }
        const numberValue = Number(value);
        if (!isNaN(numberValue) && isFinite(numberValue) && isValidNumber(value)) {
            return createNumberNode(numberValue, start, end);
        }
        return createStringNode(value, start, end);
    }
    parseInlineArray() {
        const start = this.lexer.getCurrentPosition();
        this.lexer.advance();
        this.flowLevel++;
        const items = [];
        while (!this.lexer.isAtEnd()) {
            this.lexer.skipWhitespace();
            if (this.lexer.getCurrentChar() === "]") {
                this.lexer.advance();
                break;
            }
            if (this.lexer.getCurrentChar() === "") {
                this.lexer.advanceLine();
                continue;
            }
            if (this.lexer.getCurrentChar() === "#") {
                this.lexer.skipToEndOfLine();
                this.lexer.advanceLine();
                continue;
            }
            const positionBefore = this.lexer.savePosition();
            const item = this.parseValue();
            if (!(item.type === "string" && item.value === "" && item.start.line === item.end.line && item.start.character === item.end.character)) {
                items.push(item);
            }
            const positionAfter = this.lexer.savePosition();
            if (positionBefore.line === positionAfter.line && positionBefore.char === positionAfter.char) {
                if (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== "") {
                    this.lexer.advance();
                } else {
                    break;
                }
            }
            this.lexer.skipWhitespace();
            if (this.lexer.getCurrentChar() === ",") {
                this.lexer.advance();
            }
        }
        const end = this.lexer.getCurrentPosition();
        this.flowLevel--;
        return createArrayNode(items, start, end);
    }
    parseInlineObject() {
        const start = this.lexer.getCurrentPosition();
        this.lexer.advance();
        this.flowLevel++;
        const properties = [];
        while (!this.lexer.isAtEnd()) {
            this.lexer.skipWhitespace();
            if (this.lexer.getCurrentChar() === "}") {
                this.lexer.advance();
                break;
            }
            if (this.lexer.getCurrentChar() === "#") {
                this.lexer.skipToEndOfLine();
                this.lexer.advanceLine();
                continue;
            }
            const positionBefore = this.lexer.savePosition();
            const keyStart = this.lexer.getCurrentPosition();
            let keyValue = "";
            if (this.lexer.getCurrentChar() === "\"" || this.lexer.getCurrentChar() === `'`) {
                const quote = this.lexer.getCurrentChar();
                this.lexer.advance();
                while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== "" && this.lexer.getCurrentChar() !== quote) {
                    keyValue += this.lexer.advance();
                }
                if (this.lexer.getCurrentChar() === quote) {
                    this.lexer.advance();
                }
            } else {
                while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== "" && this.lexer.getCurrentChar() !== ":") {
                    keyValue += this.lexer.advance();
                }
            }
            keyValue = keyValue.trim();
            const keyEnd = this.lexer.getCurrentPosition();
            const key = createStringNode(keyValue, keyStart, keyEnd);
            this.lexer.skipWhitespace();
            if (this.lexer.getCurrentChar() === ":") {
                this.lexer.advance();
            }
            this.lexer.skipWhitespace();
            const value = this.parseValue();
            properties.push({
                key,
                value
            });
            const positionAfter = this.lexer.savePosition();
            if (positionBefore.line === positionAfter.line && positionBefore.char === positionAfter.char) {
                if (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== "") {
                    this.lexer.advance();
                } else {
                    break;
                }
            }
            this.lexer.skipWhitespace();
            if (this.lexer.getCurrentChar() === ",") {
                this.lexer.advance();
            }
        }
        const end = this.lexer.getCurrentPosition();
        this.flowLevel--;
        return createObjectNode(properties, start, end);
    }
    parseBlockArray(baseIndent) {
        const start = this.lexer.getCurrentPosition();
        const items = [];
        while (!this.lexer.isAtEnd()) {
            this.lexer.moveToNextNonEmptyLine();
            if (this.lexer.isAtEnd()) {
                break;
            }
            const currentIndent = this.lexer.getIndentation();
            if (currentIndent < baseIndent) {
                break;
            }
            this.lexer.skipWhitespace();
            if (this.lexer.getCurrentChar() === "-") {
                this.lexer.advance();
                this.lexer.skipWhitespace();
                const itemStart = this.lexer.getCurrentPosition();
                if (this.lexer.getCurrentChar() === "" || this.lexer.getCurrentChar() === "#") {
                    this.lexer.advanceLine();
                    if (!this.lexer.isAtEnd()) {
                        const nextIndent = this.lexer.getIndentation();
                        if (nextIndent > currentIndent) {
                            this.lexer.skipWhitespace();
                            if (this.lexer.getCurrentChar() === "-") {
                                const nestedArray = this.parseBlockArray(nextIndent);
                                items.push(nestedArray);
                            } else {
                                const currentLine = this.lexer.getCurrentLineText();
                                const currentPos = this.lexer.getCurrentCharNumber();
                                const remainingLine = currentLine.substring(currentPos);
                                if (remainingLine.includes(":") && !remainingLine.trim().startsWith("#")) {
                                    const nestedObject = this.parseBlockObject(nextIndent, this.lexer.getCurrentCharNumber());
                                    items.push(nestedObject);
                                } else {
                                    items.push(createStringNode("", itemStart, itemStart));
                                }
                            }
                        } else {
                            items.push(createStringNode("", itemStart, itemStart));
                        }
                    } else {
                        items.push(createStringNode("", itemStart, itemStart));
                    }
                } else {
                    const currentLine = this.lexer.getCurrentLineText();
                    const currentPos = this.lexer.getCurrentCharNumber();
                    const remainingLine = currentLine.substring(currentPos);
                    const hasColon = remainingLine.includes(":");
                    if (hasColon) {
                        const item = this.parseBlockObject(itemStart.character, itemStart.character);
                        items.push(item);
                    } else {
                        const item = this.parseValue();
                        items.push(item);
                        while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== "" && this.lexer.getCurrentChar() !== "#") {
                            this.lexer.advance();
                        }
                        this.lexer.advanceLine();
                    }
                }
            } else {
                break;
            }
        }
        let end = start;
        if (items.length > 0) {
            const lastItem = items[items.length - 1];
            end = lastItem.end;
        } else {
            end = createPosition(start.line, start.character + 1);
        }
        return createArrayNode(items, start, end);
    }
    parseBlockObject(baseIndent, baseCharPosition) {
        const start = this.lexer.getCurrentPosition();
        const properties = [];
        const localKeysSeen = ( new Set());
        const fromCurrentPosition = baseCharPosition !== undefined;
        let firstIteration = true;
        while (!this.lexer.isAtEnd()) {
            if (!firstIteration || !fromCurrentPosition) {
                this.lexer.moveToNextNonEmptyLine();
            }
            firstIteration = false;
            if (this.lexer.isAtEnd()) {
                break;
            }
            const currentIndent = this.lexer.getIndentation();
            if (fromCurrentPosition) {
                this.lexer.skipWhitespace();
                const currentCharPosition = this.lexer.getCurrentCharNumber();
                if (currentCharPosition < baseCharPosition) {
                    break;
                }
            } else {
                if (currentIndent < baseIndent) {
                    break;
                }
                if (currentIndent > baseIndent) {
                    const lineStart = createPosition(this.lexer.getCurrentLineNumber(), 0);
                    const lineEnd = createPosition(this.lexer.getCurrentLineNumber(), this.lexer.getCurrentLineText().length);
                    this.addError("Unexpected indentation", "indentation", lineStart, lineEnd);
                    this.lexer.skipWhitespace();
                } else {
                    this.lexer.skipWhitespace();
                }
            }
            const keyStart = this.lexer.getCurrentPosition();
            let keyValue = "";
            while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== "" && this.lexer.getCurrentChar() !== ":") {
                keyValue += this.lexer.advance();
            }
            keyValue = keyValue.trim();
            const keyEnd = this.lexer.getCurrentPosition();
            const key = createStringNode(keyValue, keyStart, keyEnd);
            if (!this.options.allowDuplicateKeys && ( localKeysSeen.has(keyValue))) {
                this.addError(`Duplicate key '${keyValue}'`, "duplicateKey", keyStart, keyEnd);
            }
            localKeysSeen.add(keyValue);
            if (this.lexer.getCurrentChar() === ":") {
                this.lexer.advance();
            }
            this.lexer.skipWhitespace();
            let value;
            const valueStart = this.lexer.getCurrentPosition();
            if (this.lexer.getCurrentChar() === "" || this.lexer.getCurrentChar() === "#") {
                this.lexer.advanceLine();
                if (!this.lexer.isAtEnd()) {
                    const nextIndent = this.lexer.getIndentation();
                    if (nextIndent > currentIndent) {
                        this.lexer.skipWhitespace();
                        if (this.lexer.getCurrentChar() === "-") {
                            value = this.parseBlockArray(nextIndent);
                        } else {
                            const currentLine = this.lexer.getCurrentLineText();
                            const currentPos = this.lexer.getCurrentCharNumber();
                            const remainingLine = currentLine.substring(currentPos);
                            if (remainingLine.includes(":") && !remainingLine.trim().startsWith("#")) {
                                value = this.parseBlockObject(nextIndent);
                            } else {
                                value = this.parseValue();
                            }
                        }
                    } else if (!fromCurrentPosition && nextIndent === currentIndent) {
                        this.lexer.skipWhitespace();
                        if (this.lexer.getCurrentChar() === "-") {
                            value = this.parseBlockArray(currentIndent);
                        } else {
                            value = createStringNode("", valueStart, valueStart);
                        }
                    } else {
                        value = createStringNode("", valueStart, valueStart);
                    }
                } else {
                    value = createStringNode("", valueStart, valueStart);
                }
            } else {
                value = this.parseValue();
                while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== "" && this.lexer.getCurrentChar() !== "#") {
                    if (isWhitespace(this.lexer.getCurrentChar())) {
                        this.lexer.advance();
                    } else {
                        break;
                    }
                }
                if (this.lexer.getCurrentChar() === "#") {
                    this.lexer.skipToEndOfLine();
                }
                if (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() === "") {
                    this.lexer.advanceLine();
                }
            }
            properties.push({
                key,
                value
            });
        }
        let end = start;
        if (properties.length > 0) {
            const lastProperty = properties[properties.length - 1];
            end = lastProperty.value.end;
        }
        return createObjectNode(properties, start, end);
    }
    parse() {
        if (this.lexer.isAtEnd()) {
            return undefined;
        }
        this.lexer.moveToNextNonEmptyLine();
        if (this.lexer.isAtEnd()) {
            return undefined;
        }
        this.lexer.skipWhitespace();
        if (this.lexer.getCurrentChar() === "-") {
            const nextChar = this.lexer.peek();
            if (nextChar === " " || nextChar === "\t" || nextChar === "" || nextChar === "#") {
                return this.parseBlockArray(0);
            } else {
                return this.parseValue();
            }
        } else if (this.lexer.getCurrentChar() === "[") {
            return this.parseInlineArray();
        } else if (this.lexer.getCurrentChar() === "{") {
            return this.parseInlineObject();
        } else {
            const currentLine = this.lexer.getCurrentLineText();
            const currentPos = this.lexer.getCurrentCharNumber();
            const remainingLine = currentLine.substring(currentPos);
            let hasColon = false;
            let inQuotes = false;
            let quoteChar = "";
            for (let i = 0; i < remainingLine.length; i++) {
                const char = remainingLine[i];
                if (!inQuotes && (char === "\"" || char === `'`)) {
                    inQuotes = true;
                    quoteChar = char;
                } else if (inQuotes && char === quoteChar) {
                    inQuotes = false;
                    quoteChar = "";
                } else if (!inQuotes && char === ":") {
                    hasColon = true;
                    break;
                } else if (!inQuotes && char === "#") {
                    break;
                }
            }
            if (hasColon) {
                return this.parseBlockObject(0);
            } else {
                return this.parseValue();
            }
        }
    }
}

export { parse };
