"use strict";
// Copyright 2020 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
Object.defineProperty(exports, "__esModule", { value: true });
exports.compileStringAsync = exports.compileAsync = exports.compileString = exports.compile = void 0;
const p = require("path");
const supportsColor = require("supports-color");
const proto = require("./vendor/embedded_sass_pb");
const utils = require("./utils");
const async_compiler_1 = require("./async-compiler");
const dispatcher_1 = require("./dispatcher");
const exception_1 = require("./exception");
const function_registry_1 = require("./function-registry");
const importer_registry_1 = require("./importer-registry");
const message_transformer_1 = require("./message-transformer");
const packet_transformer_1 = require("./packet-transformer");
const sync_compiler_1 = require("./sync-compiler");
const deprotofy_span_1 = require("./deprotofy-span");
const importer_1 = require("./legacy/importer");
function compile(path, options) {
    const importers = new importer_registry_1.ImporterRegistry(options);
    return compileRequestSync(newCompilePathRequest(path, importers, options), importers, options);
}
exports.compile = compile;
function compileString(source, options) {
    const importers = new importer_registry_1.ImporterRegistry(options);
    return compileRequestSync(newCompileStringRequest(source, importers, options), importers, options);
}
exports.compileString = compileString;
function compileAsync(path, options) {
    const importers = new importer_registry_1.ImporterRegistry(options);
    return compileRequestAsync(newCompilePathRequest(path, importers, options), importers, options);
}
exports.compileAsync = compileAsync;
function compileStringAsync(source, options) {
    const importers = new importer_registry_1.ImporterRegistry(options);
    return compileRequestAsync(newCompileStringRequest(source, importers, options), importers, options);
}
exports.compileStringAsync = compileStringAsync;
// Creates a request for compiling a file.
function newCompilePathRequest(path, importers, options) {
    const request = newCompileRequest(importers, options);
    request.input = { case: 'path', value: path };
    return request;
}
// Creates a request for compiling a string.
function newCompileStringRequest(source, importers, options) {
    var _a, _b;
    const input = new proto.InboundMessage_CompileRequest_StringInput({
        source,
        syntax: utils.protofySyntax((_a = options === null || options === void 0 ? void 0 : options.syntax) !== null && _a !== void 0 ? _a : 'scss'),
    });
    const url = (_b = options === null || options === void 0 ? void 0 : options.url) === null || _b === void 0 ? void 0 : _b.toString();
    if (url && url !== importer_1.legacyImporterProtocol) {
        input.url = url;
    }
    if (options && 'importer' in options && options.importer) {
        input.importer = importers.register(options.importer);
    }
    else if (url === importer_1.legacyImporterProtocol) {
        input.importer = new proto.InboundMessage_CompileRequest_Importer({
            importer: { case: 'path', value: p.resolve('.') },
        });
    }
    else {
        // When importer is not set on the host, the compiler will set a
        // FileSystemImporter if `url` is set to a file: url or a NoOpImporter.
    }
    const request = newCompileRequest(importers, options);
    request.input = { case: 'string', value: input };
    return request;
}
// Creates a compilation request for the given `options` without adding any
// input-specific options.
function newCompileRequest(importers, options) {
    var _a, _b, _c, _d;
    const request = new proto.InboundMessage_CompileRequest({
        importers: importers.importers,
        globalFunctions: Object.keys((_a = options === null || options === void 0 ? void 0 : options.functions) !== null && _a !== void 0 ? _a : {}),
        sourceMap: !!(options === null || options === void 0 ? void 0 : options.sourceMap),
        sourceMapIncludeSources: !!(options === null || options === void 0 ? void 0 : options.sourceMapIncludeSources),
        alertColor: (_b = options === null || options === void 0 ? void 0 : options.alertColor) !== null && _b !== void 0 ? _b : !!supportsColor.stdout,
        alertAscii: !!(options === null || options === void 0 ? void 0 : options.alertAscii),
        quietDeps: !!(options === null || options === void 0 ? void 0 : options.quietDeps),
        verbose: !!(options === null || options === void 0 ? void 0 : options.verbose),
        charset: !!((_c = options === null || options === void 0 ? void 0 : options.charset) !== null && _c !== void 0 ? _c : true),
    });
    switch ((_d = options === null || options === void 0 ? void 0 : options.style) !== null && _d !== void 0 ? _d : 'expanded') {
        case 'expanded':
            request.style = proto.OutputStyle.EXPANDED;
            break;
        case 'compressed':
            request.style = proto.OutputStyle.COMPRESSED;
            break;
        default:
            throw new Error(`Unknown options.style: "${options === null || options === void 0 ? void 0 : options.style}"`);
    }
    return request;
}
// Spins up a compiler, then sends it a compile request. Returns a promise that
// resolves with the CompileResult. Throws if there were any protocol or
// compilation errors. Shuts down the compiler after compilation.
async function compileRequestAsync(request, importers, options) {
    const functions = new function_registry_1.FunctionRegistry(options === null || options === void 0 ? void 0 : options.functions);
    const embeddedCompiler = new async_compiler_1.AsyncEmbeddedCompiler();
    embeddedCompiler.stderr$.subscribe(data => process.stderr.write(data));
    try {
        const dispatcher = createDispatcher(embeddedCompiler.stdout$, buffer => {
            embeddedCompiler.writeStdin(buffer);
        }, {
            handleImportRequest: request => importers.import(request),
            handleFileImportRequest: request => importers.fileImport(request),
            handleCanonicalizeRequest: request => importers.canonicalize(request),
            handleFunctionCallRequest: request => functions.call(request),
        });
        dispatcher.logEvents$.subscribe(event => handleLogEvent(options, event));
        return handleCompileResponse(await new Promise((resolve, reject) => dispatcher.sendCompileRequest(request, (err, response) => {
            if (err) {
                reject(err);
            }
            else {
                resolve(response);
            }
        })));
    }
    finally {
        embeddedCompiler.close();
        await embeddedCompiler.exit$;
    }
}
// Spins up a compiler, then sends it a compile request. Returns a promise that
// resolves with the CompileResult. Throws if there were any protocol or
// compilation errors. Shuts down the compiler after compilation.
function compileRequestSync(request, importers, options) {
    const functions = new function_registry_1.FunctionRegistry(options === null || options === void 0 ? void 0 : options.functions);
    const embeddedCompiler = new sync_compiler_1.SyncEmbeddedCompiler();
    embeddedCompiler.stderr$.subscribe(data => process.stderr.write(data));
    try {
        const dispatcher = createDispatcher(embeddedCompiler.stdout$, buffer => {
            embeddedCompiler.writeStdin(buffer);
        }, {
            handleImportRequest: request => importers.import(request),
            handleFileImportRequest: request => importers.fileImport(request),
            handleCanonicalizeRequest: request => importers.canonicalize(request),
            handleFunctionCallRequest: request => functions.call(request),
        });
        dispatcher.logEvents$.subscribe(event => handleLogEvent(options, event));
        let error;
        let response;
        dispatcher.sendCompileRequest(request, (error_, response_) => {
            if (error_) {
                error = error_;
            }
            else {
                response = response_;
            }
        });
        for (;;) {
            if (!embeddedCompiler.yield()) {
                throw utils.compilerError('Embedded compiler exited unexpectedly.');
            }
            if (error)
                throw error;
            if (response)
                return handleCompileResponse(response);
        }
    }
    finally {
        embeddedCompiler.close();
        embeddedCompiler.yieldUntilExit();
    }
}
/**
 * Creates a dispatcher that dispatches messages from the given `stdout` stream.
 */
function createDispatcher(stdout, writeStdin, handlers) {
    const packetTransformer = new packet_transformer_1.PacketTransformer(stdout, writeStdin);
    const messageTransformer = new message_transformer_1.MessageTransformer(packetTransformer.outboundProtobufs$, packet => packetTransformer.writeInboundProtobuf(packet));
    return new dispatcher_1.Dispatcher(messageTransformer.outboundMessages$, message => messageTransformer.writeInboundMessage(message), handlers);
}
/** Handles a log event according to `options`. */
function handleLogEvent(options, event) {
    var _a, _b;
    if (event.type === proto.LogEventType.DEBUG) {
        if ((_a = options === null || options === void 0 ? void 0 : options.logger) === null || _a === void 0 ? void 0 : _a.debug) {
            options.logger.debug(event.message, {
                span: (0, deprotofy_span_1.deprotofySourceSpan)(event.span),
            });
        }
        else {
            console.error(event.formatted);
        }
    }
    else {
        if ((_b = options === null || options === void 0 ? void 0 : options.logger) === null || _b === void 0 ? void 0 : _b.warn) {
            const params = {
                deprecation: event.type === proto.LogEventType.DEPRECATION_WARNING,
            };
            const spanProto = event.span;
            if (spanProto)
                params.span = (0, deprotofy_span_1.deprotofySourceSpan)(spanProto);
            const stack = event.stackTrace;
            if (stack)
                params.stack = stack;
            options.logger.warn(event.message, params);
        }
        else {
            console.error(event.formatted);
        }
    }
}
/**
 * Converts a `CompileResponse` into a `CompileResult`.
 *
 * Throws a `SassException` if the compilation failed.
 */
function handleCompileResponse(response) {
    if (response.result.case === 'success') {
        const success = response.result.value;
        const result = {
            css: success.css,
            loadedUrls: success.loadedUrls.map(url => new URL(url)),
        };
        const sourceMap = success.sourceMap;
        if (sourceMap)
            result.sourceMap = JSON.parse(sourceMap);
        return result;
    }
    else if (response.result.case === 'failure') {
        throw new exception_1.Exception(response.result.value);
    }
    else {
        throw utils.compilerError('Compiler sent empty CompileResponse.');
    }
}
//# sourceMappingURL=compile.js.map