import path from "node:path";
import { unprocessable } from "../../errors.js";
import { DEFAULT_INCLUDE, COMPANY_LOGO_CONTENT_TYPE_EXTENSIONS, asString, isSensitiveEnvKey, isPlainRecord, } from "./organization-portability.core.js";
import { buildManifestFromPackageFiles, buildYamlFile, parseFrontmatterMarkdown, parseYamlFile, readIncludeEntries, } from "./organization-portability.package.js";
export function normalizeInclude(input) {
    return {
        organization: input?.organization ?? DEFAULT_INCLUDE.organization,
        agents: input?.agents ?? DEFAULT_INCLUDE.agents,
        projects: input?.projects ?? DEFAULT_INCLUDE.projects,
        issues: input?.issues ?? DEFAULT_INCLUDE.issues,
        skills: input?.skills ?? DEFAULT_INCLUDE.skills,
    };
}
export function normalizePortablePath(input) {
    const normalized = input.replace(/\\/g, "/").replace(/^\.\/+/, "");
    const parts = [];
    for (const segment of normalized.split("/")) {
        if (!segment || segment === ".")
            continue;
        if (segment === "..") {
            if (parts.length > 0)
                parts.pop();
            continue;
        }
        parts.push(segment);
    }
    return parts.join("/");
}
export function resolvePortablePath(fromPath, targetPath) {
    const baseDir = path.posix.dirname(fromPath.replace(/\\/g, "/"));
    return normalizePortablePath(path.posix.join(baseDir, targetPath.replace(/\\/g, "/")));
}
export function isPortableBinaryFile(value) {
    return typeof value === "object" && value !== null && value.encoding === "base64" && typeof value.data === "string";
}
export function readPortableTextFile(files, filePath) {
    const value = files[filePath];
    return typeof value === "string" ? value : null;
}
export function inferContentTypeFromPath(filePath) {
    const extension = path.posix.extname(filePath).toLowerCase();
    switch (extension) {
        case ".gif":
            return "image/gif";
        case ".jpeg":
        case ".jpg":
            return "image/jpeg";
        case ".png":
            return "image/png";
        case ".svg":
            return "image/svg+xml";
        case ".webp":
            return "image/webp";
        default:
            return null;
    }
}
export function resolveCompanyLogoExtension(contentType, originalFilename) {
    const fromContentType = contentType ? COMPANY_LOGO_CONTENT_TYPE_EXTENSIONS[contentType.toLowerCase()] : null;
    if (fromContentType)
        return fromContentType;
    const extension = originalFilename ? path.extname(originalFilename).toLowerCase() : "";
    return extension || ".png";
}
export function portableBinaryFileToBuffer(entry) {
    return Buffer.from(entry.data, "base64");
}
export function portableFileToBuffer(entry, filePath) {
    if (typeof entry === "string") {
        return Buffer.from(entry, "utf8");
    }
    if (isPortableBinaryFile(entry)) {
        return portableBinaryFileToBuffer(entry);
    }
    throw unprocessable(`Unsupported file entry encoding for ${filePath}`);
}
export function bufferToPortableBinaryFile(buffer, contentType) {
    return {
        encoding: "base64",
        data: buffer.toString("base64"),
        contentType,
    };
}
export async function streamToBuffer(stream) {
    const chunks = [];
    for await (const chunk of stream) {
        chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
    }
    return Buffer.concat(chunks);
}
export function normalizeFileMap(files, rootPath) {
    const normalizedRoot = rootPath ? normalizePortablePath(rootPath) : null;
    const out = {};
    for (const [rawPath, content] of Object.entries(files)) {
        let nextPath = normalizePortablePath(rawPath);
        if (normalizedRoot && nextPath === normalizedRoot) {
            continue;
        }
        if (normalizedRoot && nextPath.startsWith(`${normalizedRoot}/`)) {
            nextPath = nextPath.slice(normalizedRoot.length + 1);
        }
        if (!nextPath)
            continue;
        out[nextPath] = content;
    }
    return out;
}
export function pickTextFiles(files) {
    const out = {};
    for (const [filePath, content] of Object.entries(files)) {
        if (typeof content === "string") {
            out[filePath] = content;
        }
    }
    return out;
}
export function collectSelectedExportSlugs(selectedFiles) {
    const agents = new Set();
    const projects = new Set();
    const tasks = new Set();
    for (const filePath of selectedFiles) {
        const agentMatch = filePath.match(/^agents\/([^/]+)\//);
        if (agentMatch)
            agents.add(agentMatch[1]);
        const projectMatch = filePath.match(/^projects\/([^/]+)\//);
        if (projectMatch)
            projects.add(projectMatch[1]);
        const taskMatch = filePath.match(/^tasks\/([^/]+)\//);
        if (taskMatch)
            tasks.add(taskMatch[1]);
    }
    return { agents, projects, tasks, automations: new Set(tasks) };
}
export function normalizePortableSlugList(value) {
    if (!Array.isArray(value))
        return [];
    const seen = new Set();
    const normalized = [];
    for (const entry of value) {
        if (typeof entry !== "string")
            continue;
        const trimmed = entry.trim();
        if (!trimmed || seen.has(trimmed))
            continue;
        seen.add(trimmed);
        normalized.push(trimmed);
    }
    return normalized;
}
export function normalizePortableSidebarOrder(value) {
    if (!isPlainRecord(value))
        return null;
    const sidebar = {
        agents: normalizePortableSlugList(value.agents),
        projects: normalizePortableSlugList(value.projects),
    };
    return sidebar.agents.length > 0 || sidebar.projects.length > 0 ? sidebar : null;
}
export function sortAgentsBySidebarOrder(agents) {
    if (agents.length === 0)
        return [];
    const byId = new Map(agents.map((agent) => [agent.id, agent]));
    const childrenOf = new Map();
    for (const agent of agents) {
        const parentId = agent.reportsTo && byId.has(agent.reportsTo) ? agent.reportsTo : null;
        const siblings = childrenOf.get(parentId) ?? [];
        siblings.push(agent);
        childrenOf.set(parentId, siblings);
    }
    for (const siblings of childrenOf.values()) {
        siblings.sort((left, right) => left.name.localeCompare(right.name));
    }
    const sorted = [];
    const queue = [...(childrenOf.get(null) ?? [])];
    while (queue.length > 0) {
        const agent = queue.shift();
        if (!agent)
            continue;
        sorted.push(agent);
        const children = childrenOf.get(agent.id);
        if (children)
            queue.push(...children);
    }
    return sorted;
}
export function filterPortableExtensionYaml(yaml, selectedFiles) {
    const selected = collectSelectedExportSlugs(selectedFiles);
    const parsed = parseYamlFile(yaml);
    for (const section of ["agents", "projects", "tasks", "automations"]) {
        const sectionValue = parsed[section];
        if (!isPlainRecord(sectionValue))
            continue;
        const sectionSlugs = selected[section];
        const filteredEntries = Object.fromEntries(Object.entries(sectionValue).filter(([slug]) => sectionSlugs.has(slug)));
        if (Object.keys(filteredEntries).length > 0) {
            parsed[section] = filteredEntries;
        }
        else {
            delete parsed[section];
        }
    }
    const companySection = parsed.organization;
    if (isPlainRecord(companySection)) {
        const logoPath = asString(companySection.logoPath) ?? asString(companySection.logo);
        if (logoPath && !selectedFiles.has(logoPath)) {
            delete companySection.logoPath;
            delete companySection.logo;
        }
    }
    const sidebarOrder = normalizePortableSidebarOrder(parsed.sidebar);
    if (sidebarOrder) {
        const filteredSidebar = stripEmptyValues({
            agents: sidebarOrder.agents.filter((slug) => selected.agents.has(slug)),
            projects: sidebarOrder.projects.filter((slug) => selected.projects.has(slug)),
        });
        if (isPlainRecord(filteredSidebar)) {
            parsed.sidebar = filteredSidebar;
        }
        else {
            delete parsed.sidebar;
        }
    }
    else {
        delete parsed.sidebar;
    }
    return buildYamlFile(parsed, { preserveEmptyStrings: true });
}
export function filterExportFiles(files, selectedFilesInput, rudderExtensionPath) {
    if (!selectedFilesInput || selectedFilesInput.length === 0) {
        return files;
    }
    const selectedFiles = new Set(selectedFilesInput
        .map((entry) => normalizePortablePath(entry))
        .filter((entry) => entry.length > 0));
    const filtered = {};
    for (const [filePath, content] of Object.entries(files)) {
        if (!selectedFiles.has(filePath))
            continue;
        filtered[filePath] = content;
    }
    const extensionEntry = filtered[rudderExtensionPath];
    if (selectedFiles.has(rudderExtensionPath) && typeof extensionEntry === "string") {
        filtered[rudderExtensionPath] = filterPortableExtensionYaml(extensionEntry, selectedFiles);
    }
    return filtered;
}
export function findPaperclipExtensionPath(files) {
    if (typeof files[".rudder.yaml"] === "string")
        return ".rudder.yaml";
    if (typeof files[".rudder.yml"] === "string")
        return ".rudder.yml";
    return Object.keys(files).find((entry) => entry.endsWith("/.rudder.yaml") || entry.endsWith("/.rudder.yml")) ?? null;
}
export function ensureMarkdownPath(pathValue) {
    const normalized = pathValue.replace(/\\/g, "/");
    if (!normalized.endsWith(".md")) {
        throw unprocessable(`Manifest file path must end in .md: ${pathValue}`);
    }
    return normalized;
}
export function normalizePortableConfig(value) {
    if (typeof value !== "object" || value === null || Array.isArray(value))
        return {};
    const input = value;
    const next = {};
    for (const [key, entry] of Object.entries(input)) {
        if (key === "cwd" ||
            key === "instructionsFilePath" ||
            key === "instructionsBundleMode" ||
            key === "instructionsRootPath" ||
            key === "instructionsEntryFile" ||
            key === "promptTemplate" ||
            key === "bootstrapPromptTemplate" ||
            key === "rudderSkillSync")
            continue;
        if (key === "env")
            continue;
        next[key] = entry;
    }
    return next;
}
export function isAbsoluteCommand(value) {
    return path.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value);
}
export function extractPortableEnvInputs(agentSlug, envValue, warnings) {
    if (!isPlainRecord(envValue))
        return [];
    const env = envValue;
    const inputs = [];
    for (const [key, binding] of Object.entries(env)) {
        if (key.toUpperCase() === "PATH") {
            warnings.push(`Agent ${agentSlug} PATH override was omitted from export because it is system-dependent.`);
            continue;
        }
        if (isPlainRecord(binding) && binding.type === "secret_ref") {
            inputs.push({
                key,
                description: `Provide ${key} for agent ${agentSlug}`,
                agentSlug,
                kind: "secret",
                requirement: "optional",
                defaultValue: "",
                portability: "portable",
            });
            continue;
        }
        if (isPlainRecord(binding) && binding.type === "plain") {
            const defaultValue = asString(binding.value);
            const isSensitive = isSensitiveEnvKey(key);
            const portability = defaultValue && isAbsoluteCommand(defaultValue)
                ? "system_dependent"
                : "portable";
            if (portability === "system_dependent") {
                warnings.push(`Agent ${agentSlug} env ${key} default was exported as system-dependent.`);
            }
            inputs.push({
                key,
                description: `Optional default for ${key} on agent ${agentSlug}`,
                agentSlug,
                kind: isSensitive ? "secret" : "plain",
                requirement: "optional",
                defaultValue: isSensitive ? "" : defaultValue ?? "",
                portability,
            });
            continue;
        }
        if (typeof binding === "string") {
            const portability = isAbsoluteCommand(binding) ? "system_dependent" : "portable";
            if (portability === "system_dependent") {
                warnings.push(`Agent ${agentSlug} env ${key} default was exported as system-dependent.`);
            }
            inputs.push({
                key,
                description: `Optional default for ${key} on agent ${agentSlug}`,
                agentSlug,
                kind: isSensitiveEnvKey(key) ? "secret" : "plain",
                requirement: "optional",
                defaultValue: binding,
                portability,
            });
        }
    }
    return inputs;
}
export function jsonEqual(left, right) {
    return JSON.stringify(left) === JSON.stringify(right);
}
export function isPathDefault(pathSegments, value, rules) {
    return rules.some((rule) => jsonEqual(rule.path, pathSegments) && jsonEqual(rule.value, value));
}
export function pruneDefaultLikeValue(value, opts) {
    const pathSegments = opts.path ?? [];
    if (opts.defaultRules && isPathDefault(pathSegments, value, opts.defaultRules)) {
        return undefined;
    }
    if (Array.isArray(value)) {
        return value.map((entry) => pruneDefaultLikeValue(entry, { ...opts, path: pathSegments }));
    }
    if (isPlainRecord(value)) {
        const out = {};
        for (const [key, entry] of Object.entries(value)) {
            const next = pruneDefaultLikeValue(entry, {
                ...opts,
                path: [...pathSegments, key],
            });
            if (next === undefined)
                continue;
            out[key] = next;
        }
        return out;
    }
    if (value === undefined)
        return undefined;
    if (opts.dropFalseBooleans && value === false)
        return undefined;
    return value;
}
export function renderYamlScalar(value) {
    if (value === null)
        return "null";
    if (typeof value === "boolean" || typeof value === "number")
        return String(value);
    if (typeof value === "string")
        return JSON.stringify(value);
    return JSON.stringify(value);
}
export function isEmptyObject(value) {
    return isPlainRecord(value) && Object.keys(value).length === 0;
}
export function isEmptyArray(value) {
    return Array.isArray(value) && value.length === 0;
}
export function stripEmptyValues(value, opts) {
    if (Array.isArray(value)) {
        const next = value
            .map((entry) => stripEmptyValues(entry, opts))
            .filter((entry) => entry !== undefined);
        return next.length > 0 ? next : undefined;
    }
    if (isPlainRecord(value)) {
        const next = {};
        for (const [key, entry] of Object.entries(value)) {
            const cleaned = stripEmptyValues(entry, opts);
            if (cleaned === undefined)
                continue;
            next[key] = cleaned;
        }
        return Object.keys(next).length > 0 ? next : undefined;
    }
    if (value === undefined ||
        value === null ||
        (!opts?.preserveEmptyStrings && value === "") ||
        isEmptyArray(value) ||
        isEmptyObject(value)) {
        return undefined;
    }
    return value;
}
export const YAML_KEY_PRIORITY = [
    "name",
    "description",
    "title",
    "schema",
    "kind",
    "slug",
    "reportsTo",
    "skills",
    "owner",
    "assignee",
    "project",
    "schedule",
    "version",
    "license",
    "authors",
    "homepage",
    "tags",
    "includes",
    "requirements",
    "role",
    "icon",
    "capabilities",
    "brandColor",
    "logoPath",
    "adapter",
    "runtime",
    "permissions",
    "budgetMonthlyCents",
    "metadata",
];
export const YAML_KEY_PRIORITY_INDEX = new Map(YAML_KEY_PRIORITY.map((key, index) => [key, index]));
export function compareYamlKeys(left, right) {
    const leftPriority = YAML_KEY_PRIORITY_INDEX.get(left);
    const rightPriority = YAML_KEY_PRIORITY_INDEX.get(right);
    if (leftPriority !== undefined || rightPriority !== undefined) {
        if (leftPriority === undefined)
            return 1;
        if (rightPriority === undefined)
            return -1;
        if (leftPriority !== rightPriority)
            return leftPriority - rightPriority;
    }
    return left.localeCompare(right);
}
export function orderedYamlEntries(value) {
    return Object.entries(value).sort(([leftKey], [rightKey]) => compareYamlKeys(leftKey, rightKey));
}
export function renderYamlBlock(value, indentLevel) {
    const indent = "  ".repeat(indentLevel);
    if (Array.isArray(value)) {
        if (value.length === 0)
            return [`${indent}[]`];
        const lines = [];
        for (const entry of value) {
            const scalar = entry === null ||
                typeof entry === "string" ||
                typeof entry === "boolean" ||
                typeof entry === "number" ||
                Array.isArray(entry) && entry.length === 0 ||
                isEmptyObject(entry);
            if (scalar) {
                lines.push(`${indent}- ${renderYamlScalar(entry)}`);
                continue;
            }
            lines.push(`${indent}-`);
            lines.push(...renderYamlBlock(entry, indentLevel + 1));
        }
        return lines;
    }
    if (isPlainRecord(value)) {
        const entries = orderedYamlEntries(value);
        if (entries.length === 0)
            return [`${indent}{}`];
        const lines = [];
        for (const [key, entry] of entries) {
            const scalar = entry === null ||
                typeof entry === "string" ||
                typeof entry === "boolean" ||
                typeof entry === "number" ||
                Array.isArray(entry) && entry.length === 0 ||
                isEmptyObject(entry);
            if (scalar) {
                lines.push(`${indent}${key}: ${renderYamlScalar(entry)}`);
                continue;
            }
            lines.push(`${indent}${key}:`);
            lines.push(...renderYamlBlock(entry, indentLevel + 1));
        }
        return lines;
    }
    return [`${indent}${renderYamlScalar(value)}`];
}
export function renderFrontmatter(frontmatter) {
    const lines = ["---"];
    for (const [key, value] of orderedYamlEntries(frontmatter)) {
        // Skip null/undefined values — don't export empty fields
        if (value === null || value === undefined)
            continue;
        const scalar = typeof value === "string" ||
            typeof value === "boolean" ||
            typeof value === "number" ||
            Array.isArray(value) && value.length === 0 ||
            isEmptyObject(value);
        if (scalar) {
            lines.push(`${key}: ${renderYamlScalar(value)}`);
            continue;
        }
        lines.push(`${key}:`);
        lines.push(...renderYamlBlock(value, 1));
    }
    lines.push("---");
    return `${lines.join("\n")}\n`;
}
export function buildMarkdown(frontmatter, body) {
    const cleanBody = body.replace(/\r\n/g, "\n");
    if (!cleanBody.trim()) {
        return `${renderFrontmatter(frontmatter)}\n`;
    }
    return `${renderFrontmatter(frontmatter)}${cleanBody}\n`;
}
export function normalizeSelectedFiles(selectedFiles) {
    if (!selectedFiles)
        return null;
    return new Set(selectedFiles
        .map((entry) => normalizePortablePath(entry))
        .filter((entry) => entry.length > 0));
}
export function filterCompanyMarkdownIncludes(companyPath, markdown, selectedFiles) {
    const parsed = parseFrontmatterMarkdown(markdown);
    const includeEntries = readIncludeEntries(parsed.frontmatter);
    const filteredIncludes = includeEntries.filter((entry) => selectedFiles.has(resolvePortablePath(companyPath, entry.path)));
    const nextFrontmatter = { ...parsed.frontmatter };
    if (filteredIncludes.length > 0) {
        nextFrontmatter.includes = filteredIncludes.map((entry) => entry.path);
    }
    else {
        delete nextFrontmatter.includes;
    }
    return buildMarkdown(nextFrontmatter, parsed.body);
}
export function applySelectedFilesToSource(source, selectedFiles) {
    const normalizedSelection = normalizeSelectedFiles(selectedFiles);
    if (!normalizedSelection)
        return source;
    const companyPath = source.manifest.organization
        ? ensureMarkdownPath(source.manifest.organization.path)
        : Object.keys(source.files).find((entry) => entry.endsWith("/ORGANIZATION.md") || entry === "ORGANIZATION.md") ?? null;
    if (!companyPath) {
        throw unprocessable("Organization package is missing ORGANIZATION.md");
    }
    const companyMarkdown = source.files[companyPath];
    if (typeof companyMarkdown !== "string") {
        throw unprocessable("Organization package is missing ORGANIZATION.md");
    }
    const effectiveFiles = {};
    for (const [filePath, content] of Object.entries(source.files)) {
        const normalizedPath = normalizePortablePath(filePath);
        if (!normalizedSelection.has(normalizedPath))
            continue;
        effectiveFiles[normalizedPath] = content;
    }
    effectiveFiles[companyPath] = filterCompanyMarkdownIncludes(companyPath, companyMarkdown, normalizedSelection);
    const filtered = buildManifestFromPackageFiles(effectiveFiles, {
        sourceLabel: source.manifest.source,
    });
    if (!normalizedSelection.has(companyPath)) {
        filtered.manifest.organization = null;
    }
    filtered.manifest.includes = {
        organization: filtered.manifest.organization !== null,
        agents: filtered.manifest.agents.length > 0,
        projects: filtered.manifest.projects.length > 0,
        issues: filtered.manifest.issues.length > 0,
        skills: filtered.manifest.skills.length > 0,
    };
    return filtered;
}
//# sourceMappingURL=organization-portability.files.js.map