"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.renderServiceDefinition = renderServiceDefinition;
exports.serviceDefinitionMatchesIdentity = serviceDefinitionMatchesIdentity;
exports.readServiceDefinitionEnvironment = readServiceDefinitionEnvironment;
const branding_1 = require("../../core/branding");
const service_manager_1 = require("../../core/service-manager");
const shared_1 = require("./shared");
function renderServiceDefinition(options) {
    if (options.manager.kind === service_manager_1.SERVICE_MANAGER_KINDS.launchd) {
        return renderLaunchdPlist(options);
    }
    return renderSystemdUnit(options);
}
function serviceDefinitionMatchesIdentity(definition, manager, identity) {
    if (definition === null) {
        return false;
    }
    if (manager.kind === service_manager_1.SERVICE_MANAGER_KINDS.launchd) {
        return launchdDefinitionMatchesServiceIdentity(definition, identity);
    }
    return systemdDefinitionMatchesServiceIdentity(definition, identity);
}
function readServiceDefinitionEnvironment(definition, manager) {
    if (definition === null) {
        return null;
    }
    if (manager.kind === service_manager_1.SERVICE_MANAGER_KINDS.launchd) {
        return readLaunchdDefinitionEnvironment(definition);
    }
    return readSystemdDefinitionEnvironment(definition);
}
function launchdDefinitionMatchesServiceIdentity(definition, identity) {
    return (definition.includes(renderLaunchdProgramArguments(identity)) &&
        definition.includes(`    <key>${branding_1.INTERNAL_SERVICE_ENV_NAME}</key>\n    <string>1</string>\n`) &&
        definition.includes(`  <key>StandardOutPath</key>\n  <string>${escapeXml(identity.logPath)}</string>`) &&
        definition.includes(`  <key>StandardErrorPath</key>\n  <string>${escapeXml(identity.logPath)}</string>`));
}
function systemdDefinitionMatchesServiceIdentity(definition, identity) {
    return (definition.includes(`Environment=${branding_1.INTERNAL_SERVICE_ENV_NAME}=1\n`) &&
        definition.includes(`ExecStart=${renderSystemdExecStart(identity)}\n`) &&
        definition.includes(`StandardOutput=append:${identity.logPath}\n`) &&
        definition.includes(`StandardError=append:${identity.logPath}\n`));
}
function readLaunchdDefinitionEnvironment(definition) {
    const environmentMatch = definition.match(/<key>\s*EnvironmentVariables\s*<\/key>\s*<dict>(?<entries>[\s\S]*?)<\/dict>/);
    const environmentGroups = environmentMatch?.groups;
    const entries = environmentGroups?.entries;
    if (entries === undefined) {
        return null;
    }
    const result = { environment: {} };
    const pairPattern = /<key>(?<key>[\s\S]*?)<\/key>\s*<string>(?<value>[\s\S]*?)<\/string>/g;
    for (const match of entries.matchAll(pairPattern)) {
        const groups = match.groups;
        const key = unescapeXml(groups?.key ?? "");
        const value = unescapeXml(groups?.value ?? "");
        appendServiceDefinitionEnvironmentEntry(result, key, value);
    }
    return result;
}
function readSystemdDefinitionEnvironment(definition) {
    const result = { environment: {} };
    let environmentLineCount = 0;
    for (const line of definition.split(/\r?\n/)) {
        const trimmedLine = line.trimStart();
        if (!trimmedLine.startsWith("Environment=")) {
            continue;
        }
        environmentLineCount += 1;
        const environmentValue = trimmedLine.slice("Environment=".length);
        if (environmentValue.trim() === "") {
            delete result.servicePath;
            result.environment = {};
            continue;
        }
        for (const assignment of parseSystemdEnvironmentAssignments(environmentValue)) {
            const separatorIndex = assignment.indexOf("=");
            if (separatorIndex === -1) {
                continue;
            }
            const key = assignment.slice(0, separatorIndex);
            const value = assignment.slice(separatorIndex + 1);
            appendServiceDefinitionEnvironmentEntry(result, key, value);
        }
    }
    return environmentLineCount === 0 ? null : result;
}
function appendServiceDefinitionEnvironmentEntry(result, key, value) {
    if (key === branding_1.INTERNAL_SERVICE_ENV_NAME) {
        return;
    }
    if (key === "PATH") {
        result.servicePath = value;
        return;
    }
    result.environment[key] = value;
}
function renderLaunchdPlist(options) {
    const args = renderLaunchdProgramArguments(options);
    const pathEntry = options.servicePath
        ? `    <key>PATH</key>\n    <string>${escapeXml(options.servicePath)}</string>\n`
        : "";
    const environmentEntries = sortedEnvironmentEntries(options.environment)
        .map(([key, value]) => `    <key>${escapeXml(key)}</key>\n    <string>${escapeXml(value)}</string>\n`)
        .join("");
    return `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>${branding_1.SERVICE_LABEL}</string>
  <key>ProgramArguments</key>
  <array>
${args}
  </array>
  <key>EnvironmentVariables</key>
  <dict>
    <key>${branding_1.INTERNAL_SERVICE_ENV_NAME}</key>
    <string>1</string>
${pathEntry}${environmentEntries}  </dict>
  <key>RunAtLoad</key>
  <true/>
  <key>KeepAlive</key>
  <true/>
  <key>StandardOutPath</key>
  <string>${escapeXml(options.logPath)}</string>
  <key>StandardErrorPath</key>
  <string>${escapeXml(options.logPath)}</string>
</dict>
</plist>
`;
}
function renderSystemdUnit(options) {
    const execStart = renderSystemdExecStart(options);
    const pathEnvironment = options.servicePath
        ? `Environment=${quoteSystemdEnvironmentAssignment("PATH", options.servicePath)}\n`
        : "";
    const environment = sortedEnvironmentEntries(options.environment)
        .map(([key, value]) => `Environment=${quoteSystemdEnvironmentAssignment(key, value)}\n`)
        .join("");
    return `[Unit]
Description=${branding_1.SERVICE_DESCRIPTION}

[Service]
Environment=${branding_1.INTERNAL_SERVICE_ENV_NAME}=1
${pathEnvironment}${environment}ExecStart=${execStart}
Restart=always
RestartSec=5
StandardOutput=append:${options.logPath}
StandardError=append:${options.logPath}

[Install]
WantedBy=default.target
`;
}
function getServiceProgramArguments(identity) {
    return [
        identity.nodePath,
        identity.cliEntrypoint,
        "service",
        shared_1.SERVICE_COMMAND_ACTIONS.run,
    ];
}
function renderLaunchdProgramArguments(identity) {
    return getServiceProgramArguments(identity)
        .map((value) => `    <string>${escapeXml(value)}</string>`)
        .join("\n");
}
function renderSystemdExecStart(identity) {
    return getServiceProgramArguments(identity)
        .map(quoteSystemdArgument)
        .join(" ");
}
function sortedEnvironmentEntries(environment) {
    return Object.entries(environment).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
}
function quoteSystemdArgument(value) {
    return `"${value
        .replaceAll("\\", "\\\\")
        .replaceAll('"', '\\"')
        .replaceAll("%", "%%")}"`;
}
function quoteSystemdEnvironmentAssignment(key, value) {
    return `"${key}=${value
        .replaceAll("\\", "\\\\")
        .replaceAll('"', '\\"')
        .replaceAll("%", "%%")}"`;
}
function parseSystemdEnvironmentAssignments(value) {
    const assignments = [];
    let current = "";
    let quoted = false;
    let hasContent = false;
    for (let index = 0; index < value.length; index += 1) {
        const char = value[index];
        const next = value[index + 1];
        if (!quoted && /\s/.test(char ?? "")) {
            if (hasContent) {
                assignments.push(current);
                current = "";
                hasContent = false;
            }
            continue;
        }
        if (char === '"') {
            quoted = !quoted;
            hasContent = true;
            continue;
        }
        if (char === "\\" && next !== undefined) {
            current += next;
            index += 1;
            hasContent = true;
            continue;
        }
        if (char === "%" && next === "%") {
            current += "%";
            index += 1;
            hasContent = true;
            continue;
        }
        current += char;
        hasContent = true;
    }
    if (hasContent) {
        assignments.push(current);
    }
    return assignments;
}
function escapeXml(value) {
    return value
        .replaceAll("&", "&amp;")
        .replaceAll("<", "&lt;")
        .replaceAll(">", "&gt;")
        .replaceAll('"', "&quot;")
        .replaceAll("'", "&apos;");
}
function unescapeXml(value) {
    return value
        .replaceAll("&apos;", "'")
        .replaceAll("&quot;", '"')
        .replaceAll("&gt;", ">")
        .replaceAll("&lt;", "<")
        .replaceAll("&amp;", "&");
}
