"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseSyml = exports.stringifySyml = exports.PreserveOrdering = void 0;
const js_yaml_1 = require("js-yaml");
const syml_1 = require("./grammars/syml");
const simpleStringPattern = /^(?![-?:,\][{}#&*!|>'"%@` \t\r\n]).([ \t]*(?![,\][{}:# \t\r\n]).)*$/;
// The following keys will always be stored at the top of the object, in the
// specified order. It's not fair but life isn't fair either.
const specialObjectKeys = [`__metadata`, `version`, `resolution`, `dependencies`, `peerDependencies`, `dependenciesMeta`, `peerDependenciesMeta`, `binaries`];
class PreserveOrdering {
    constructor(data) {
        this.data = data;
    }
}
exports.PreserveOrdering = PreserveOrdering;
function stringifyString(value) {
    if (value.match(simpleStringPattern)) {
        return value;
    }
    else {
        return JSON.stringify(value);
    }
}
function isRemovableField(value) {
    if (typeof value === `undefined`)
        return true;
    if (typeof value === `object` && value !== null && !Array.isArray(value))
        return Object.keys(value).every(key => isRemovableField(value[key]));
    return false;
}
function stringifyValue(value, indentLevel, newLineIfObject) {
    if (value === null)
        return `null\n`;
    if (typeof value === `number` || typeof value === `boolean`)
        return `${value.toString()}\n`;
    if (typeof value === `string`)
        return `${stringifyString(value)}\n`;
    if (Array.isArray(value)) {
        if (value.length === 0)
            return `[]\n`;
        const indent = `  `.repeat(indentLevel);
        const serialized = value.map(sub => {
            return `${indent}- ${stringifyValue(sub, indentLevel + 1, false)}`;
        }).join(``);
        return `\n${serialized}`;
    }
    if (typeof value === `object` && value) {
        const [data, sort] = value instanceof PreserveOrdering
            ? [value.data, false]
            : [value, true];
        const indent = `  `.repeat(indentLevel);
        const keys = Object.keys(data);
        if (sort) {
            keys.sort((a, b) => {
                const aIndex = specialObjectKeys.indexOf(a);
                const bIndex = specialObjectKeys.indexOf(b);
                if (aIndex === -1 && bIndex === -1)
                    return a < b ? -1 : a > b ? +1 : 0;
                if (aIndex !== -1 && bIndex === -1)
                    return -1;
                if (aIndex === -1 && bIndex !== -1)
                    return +1;
                return aIndex - bIndex;
            });
        }
        const fields = keys.filter(key => {
            return !isRemovableField(data[key]);
        }).map((key, index) => {
            const value = data[key];
            const stringifiedKey = stringifyString(key);
            const stringifiedValue = stringifyValue(value, indentLevel + 1, true);
            const recordIndentation = index > 0 || newLineIfObject
                ? indent
                : ``;
            // Yaml 1.2 spec says that keys over 1024 characters need to be prefixed with ? and the : goes in a new line
            const keyPart = stringifiedKey.length > 1024
                ? `? ${stringifiedKey}\n${recordIndentation}:`
                : `${stringifiedKey}:`;
            const valuePart = stringifiedValue.startsWith(`\n`)
                ? stringifiedValue
                : ` ${stringifiedValue}`;
            return `${recordIndentation}${keyPart}${valuePart}`;
        }).join(indentLevel === 0 ? `\n` : ``) || `\n`;
        if (!newLineIfObject) {
            return `${fields}`;
        }
        else {
            return `\n${fields}`;
        }
    }
    throw new Error(`Unsupported value type (${value})`);
}
function stringifySyml(value) {
    try {
        const stringified = stringifyValue(value, 0, false);
        return stringified !== `\n` ? stringified : ``;
    }
    catch (error) {
        if (error.location)
            error.message = error.message.replace(/(\.)?$/, ` (line ${error.location.start.line}, column ${error.location.start.column})$1`);
        throw error;
    }
}
exports.stringifySyml = stringifySyml;
stringifySyml.PreserveOrdering = PreserveOrdering;
function parseViaPeg(source) {
    if (!source.endsWith(`\n`))
        source += `\n`;
    return (0, syml_1.parse)(source);
}
const LEGACY_REGEXP = /^(#.*(\r?\n))*?#\s+yarn\s+lockfile\s+v1\r?\n/i;
function parseViaJsYaml(source) {
    if (LEGACY_REGEXP.test(source))
        return parseViaPeg(source);
    const value = (0, js_yaml_1.safeLoad)(source, {
        schema: js_yaml_1.FAILSAFE_SCHEMA,
        json: true,
    });
    // Empty files are parsed as `undefined` instead of an empty object
    // Empty files with 2 newlines or more are `null` instead
    if (value === undefined || value === null)
        return {};
    if (typeof value !== `object`)
        throw new Error(`Expected an indexed object, got a ${typeof value} instead. Does your file follow Yaml's rules?`);
    if (Array.isArray(value))
        throw new Error(`Expected an indexed object, got an array instead. Does your file follow Yaml's rules?`);
    return value;
}
function parseSyml(source) {
    return parseViaJsYaml(source);
}
exports.parseSyml = parseSyml;
