"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadWorkspaceSkills = loadWorkspaceSkills;
exports.readWorkspaceSkill = readWorkspaceSkill;
exports.renderWorkspaceSkillsPrompt = renderWorkspaceSkillsPrompt;
const promises_1 = require("node:fs/promises");
const node_path_1 = __importDefault(require("node:path"));
const frontmatter_1 = require("../markdown/frontmatter");
const resources_1 = require("../mcp/resources");
function parseSkillDirectFileName(fileName) {
    const match = /^(?<stem>.+?)(?:\.(?<lang>en|cn))?\.md$/i.exec(fileName);
    if (!match || !match.groups)
        return null;
    const stem = match.groups['stem']?.trim() ?? '';
    if (stem === '')
        return null;
    const lang = match.groups['lang']?.toLowerCase();
    const variant = lang === 'en' || lang === 'cn' ? lang : 'neutral';
    return { skillId: stem, variant };
}
function parseSkillPackageFileName(fileName) {
    const lowered = fileName.toLowerCase();
    if (lowered === 'skill.md')
        return 'neutral';
    if (lowered === 'skill.en.md')
        return 'en';
    if (lowered === 'skill.cn.md')
        return 'cn';
    return null;
}
async function readSkillEntryKind(params) {
    if (params.entry.isFile())
        return 'file';
    if (params.entry.isDirectory())
        return 'directory';
    if (!params.entry.isSymbolicLink())
        return 'other';
    try {
        const followStat = await (0, promises_1.stat)(params.entryPathAbs);
        if (followStat.isFile())
            return 'file';
        if (followStat.isDirectory())
            return 'directory';
        return 'other';
    }
    catch (error) {
        const isEnoent = typeof error === 'object' &&
            error !== null &&
            'code' in error &&
            error.code === 'ENOENT';
        if (isEnoent) {
            throw new Error(`Invalid skill symlink '${params.entryPathRel}': target does not exist`);
        }
        throw error;
    }
}
function getPreferredSkillVariants(language) {
    if (language === 'zh') {
        return ['cn', 'neutral'];
    }
    return ['en', 'neutral'];
}
function pickSkillCandidateFile(files, language) {
    const variantsByKind = new Map();
    for (const file of files) {
        const bucket = variantsByKind.get(file.variant);
        if (bucket) {
            bucket.push(file);
        }
        else {
            variantsByKind.set(file.variant, [file]);
        }
    }
    for (const [variant, bucket] of variantsByKind.entries()) {
        if (bucket.length > 1) {
            const filesText = bucket.map((item) => item.filePathRel).join(', ');
            throw new Error(`Duplicate skill variant '${variant}' detected: ${filesText}`);
        }
    }
    for (const variant of getPreferredSkillVariants(language)) {
        const bucket = variantsByKind.get(variant);
        if (bucket && bucket.length === 1) {
            return bucket[0];
        }
    }
    return null;
}
function parseSkillFrontmatter(frontmatter, sourcePathRel) {
    const nameValue = frontmatter['name'];
    if (typeof nameValue !== 'string' || nameValue.trim() === '') {
        throw new Error(`Invalid skill frontmatter: 'name' is required (${sourcePathRel})`);
    }
    const descriptionValue = frontmatter['description'];
    if (typeof descriptionValue !== 'string' || descriptionValue.trim() === '') {
        throw new Error(`Invalid skill frontmatter: 'description' is required (${sourcePathRel})`);
    }
    const allowedToolsValue = frontmatter['allowed-tools'];
    const declaredAllowedTools = (() => {
        if (allowedToolsValue === undefined)
            return undefined;
        if (typeof allowedToolsValue === 'string') {
            const values = allowedToolsValue
                .split(',')
                .map((item) => item.trim())
                .filter((item) => item !== '');
            if (values.length === 0) {
                throw new Error(`Invalid skill frontmatter: 'allowed-tools' string must not be empty (${sourcePathRel})`);
            }
            return values;
        }
        if (!Array.isArray(allowedToolsValue)) {
            throw new Error(`Invalid skill frontmatter: 'allowed-tools' must be a string or string[] (${sourcePathRel})`);
        }
        const values = [];
        for (let index = 0; index < allowedToolsValue.length; index += 1) {
            const item = allowedToolsValue[index];
            if (typeof item !== 'string' || item.trim() === '') {
                throw new Error(`Invalid skill frontmatter: 'allowed-tools[${String(index)}]' must be a non-empty string (${sourcePathRel})`);
            }
            values.push(item.trim());
        }
        return values;
    })();
    const userInvocableValue = frontmatter['user-invocable'];
    if (userInvocableValue !== undefined && typeof userInvocableValue !== 'boolean') {
        throw new Error(`Invalid skill frontmatter: 'user-invocable' must be boolean (${sourcePathRel})`);
    }
    const disableModelInvocationValue = frontmatter['disable-model-invocation'];
    if (disableModelInvocationValue !== undefined &&
        typeof disableModelInvocationValue !== 'boolean') {
        throw new Error(`Invalid skill frontmatter: 'disable-model-invocation' must be boolean (${sourcePathRel})`);
    }
    for (const key of Object.keys(frontmatter)) {
        if (key !== 'name' &&
            key !== 'description' &&
            key !== 'allowed-tools' &&
            key !== 'user-invocable' &&
            key !== 'disable-model-invocation') {
            throw new Error(`Invalid skill frontmatter: unsupported key '${key}' (${sourcePathRel})`);
        }
    }
    return {
        name: nameValue.trim(),
        description: descriptionValue.trim(),
        declaredAllowedTools,
        userInvocable: userInvocableValue,
        disableModelInvocation: disableModelInvocationValue,
    };
}
async function readScopeSkillCandidates(params) {
    let entries;
    try {
        entries = await (0, promises_1.readdir)(params.scopeRootAbs, { withFileTypes: true });
    }
    catch (error) {
        const isEnoent = typeof error === 'object' &&
            error !== null &&
            'code' in error &&
            error.code === 'ENOENT';
        if (isEnoent)
            return [];
        throw error;
    }
    const groups = new Map();
    const pushFile = (skillId, file) => {
        const bucket = groups.get(skillId);
        if (bucket) {
            bucket.push(file);
        }
        else {
            groups.set(skillId, [file]);
        }
    };
    for (const entry of entries) {
        const entryPathAbs = node_path_1.default.join(params.scopeRootAbs, entry.name);
        const entryPathRel = node_path_1.default.join(params.scopeRootRel, entry.name);
        const entryKind = await readSkillEntryKind({
            entry,
            entryPathAbs,
            entryPathRel,
        });
        if (entryKind === 'file') {
            const parsed = parseSkillDirectFileName(entry.name);
            if (!parsed)
                continue;
            pushFile(parsed.skillId, {
                fileName: entry.name,
                filePathAbs: entryPathAbs,
                filePathRel: entryPathRel,
                variant: parsed.variant,
            });
            continue;
        }
        if (entryKind !== 'directory') {
            continue;
        }
        const packageRootAbs = entryPathAbs;
        const packageRootRel = entryPathRel;
        let packageEntries;
        try {
            packageEntries = await (0, promises_1.readdir)(packageRootAbs, { withFileTypes: true });
        }
        catch (error) {
            const isEnoent = typeof error === 'object' &&
                error !== null &&
                'code' in error &&
                error.code === 'ENOENT';
            if (isEnoent)
                continue;
            throw error;
        }
        for (const packageEntry of packageEntries) {
            const variant = parseSkillPackageFileName(packageEntry.name);
            if (variant === null)
                continue;
            const filePathAbs = node_path_1.default.join(packageRootAbs, packageEntry.name);
            const filePathRel = node_path_1.default.join(packageRootRel, packageEntry.name);
            const packageEntryKind = await readSkillEntryKind({
                entry: packageEntry,
                entryPathAbs: filePathAbs,
                entryPathRel: filePathRel,
            });
            if (packageEntryKind !== 'file')
                continue;
            pushFile(entry.name, {
                fileName: packageEntry.name,
                filePathAbs,
                filePathRel,
                variant,
            });
        }
    }
    return [...groups.entries()]
        .sort((a, b) => a[0].localeCompare(b[0]))
        .map(([id, files]) => ({ id, files }));
}
async function loadScopeSkills(params) {
    const scopeRootRel = params.scope === 'team_shared'
        ? node_path_1.default.join('.minds', 'skills', 'team_shared')
        : node_path_1.default.join('.minds', 'skills', 'individual', params.memberId);
    const scopeRootAbs = node_path_1.default.resolve(params.rtwsRootAbs, scopeRootRel);
    const candidateGroups = await readScopeSkillCandidates({
        scopeRootAbs,
        scopeRootRel,
    });
    const loaded = [];
    for (const group of candidateGroups) {
        const selectedFile = pickSkillCandidateFile(group.files, params.language);
        if (!selectedFile)
            continue;
        const raw = await (0, promises_1.readFile)(selectedFile.filePathAbs, 'utf-8');
        const { body, frontmatter } = (0, frontmatter_1.parseMarkdownFrontmatter)(raw, `skill '${selectedFile.filePathRel}'`);
        const parsed = parseSkillFrontmatter(frontmatter, selectedFile.filePathRel);
        const prompt = body.trim();
        if (prompt === '') {
            throw new Error(`Invalid skill file: markdown body is required (${selectedFile.filePathRel})`);
        }
        loaded.push({
            id: group.id,
            title: parsed.name,
            description: parsed.description,
            prompt,
            body: prompt,
            scope: params.scope,
            sourcePathAbs: selectedFile.filePathAbs,
            sourcePathRel: selectedFile.filePathRel,
            declaredAllowedTools: parsed.declaredAllowedTools,
            userInvocable: parsed.userInvocable,
            disableModelInvocation: parsed.disableModelInvocation,
        });
    }
    return loaded;
}
async function loadWorkspaceSkills(params) {
    const teamSharedSkills = await loadScopeSkills({
        ...params,
        scope: 'team_shared',
    });
    const individualSkills = await loadScopeSkills({
        ...params,
        scope: 'individual',
    });
    const mcpResourceSkills = (0, resources_1.listMcpVirtualSkills)()
        .filter((skill) => params.visibleMcpServerIds === undefined || params.visibleMcpServerIds.has(skill.serverId))
        .map((skill) => ({
        id: skill.id,
        title: skill.title,
        description: skill.description,
        prompt: skill.body,
        body: skill.body,
        scope: 'mcp_resource',
        sourcePathAbs: skill.uri,
        sourcePathRel: `mcp://${skill.serverId}/${skill.uri}`,
        declaredAllowedTools: skill.declaredAllowedTools,
        userInvocable: skill.userInvocable,
        disableModelInvocation: skill.disableModelInvocation,
    }));
    return [...teamSharedSkills, ...individualSkills, ...mcpResourceSkills];
}
async function readWorkspaceSkill(params) {
    const localSkills = await loadWorkspaceSkills({
        rtwsRootAbs: params.rtwsRootAbs,
        memberId: params.memberId,
        language: params.language,
        visibleMcpServerIds: params.visibleMcpServerIds,
    });
    for (const skill of localSkills) {
        if (skill.id === params.skillId)
            return skill;
    }
    const mcpSkill = (0, resources_1.getMcpVirtualSkill)(params.skillId);
    if (!mcpSkill)
        return null;
    if (params.visibleMcpServerIds !== undefined &&
        !params.visibleMcpServerIds.has(mcpSkill.serverId)) {
        return null;
    }
    return {
        id: mcpSkill.id,
        title: mcpSkill.title,
        description: mcpSkill.description,
        prompt: mcpSkill.body,
        body: mcpSkill.body,
        scope: 'mcp_resource',
        sourcePathAbs: mcpSkill.uri,
        sourcePathRel: `mcp://${mcpSkill.serverId}/${mcpSkill.uri}`,
        declaredAllowedTools: mcpSkill.declaredAllowedTools,
        userInvocable: mcpSkill.userInvocable,
        disableModelInvocation: mcpSkill.disableModelInvocation,
    };
}
function renderScopeLabel(language, scope) {
    if (language === 'zh') {
        if (scope === 'team_shared')
            return '团队共享';
        if (scope === 'mcp_resource')
            return 'MCP 资源';
        return '个人';
    }
    if (scope === 'team_shared')
        return 'Team Shared';
    if (scope === 'mcp_resource')
        return 'MCP Resource';
    return 'Individual';
}
function renderWorkspaceSkillsPrompt(params) {
    if (params.skills.length === 0)
        return '';
    const title = params.language === 'zh' ? '### Skills（工作技能）' : '### Skills';
    const descLabel = params.language === 'zh' ? '说明' : 'Description';
    const scopeLabel = params.language === 'zh' ? '作用域' : 'Scope';
    const readLabel = params.language === 'zh' ? '读取正文' : 'Read body';
    const allowedToolsLabel = params.language === 'zh' ? '上游 allowed-tools' : 'Upstream allowed-tools';
    const allowedToolsHint = params.language === 'zh'
        ? '仅作迁移提示；Dominds 不会据此自动授予工具权限，实际权限仍以 team.yaml / toolsets / apps 为准。'
        : 'Advisory only; Dominds does not auto-grant tool permissions from this field. Actual access still comes from team.yaml / toolsets / apps.';
    const blocks = params.skills.map((skill) => {
        const lines = [
            `#### ${skill.title}`,
            `- ID: \`${skill.id}\``,
            `- ${scopeLabel}: ${renderScopeLabel(params.language, skill.scope)}`,
            `- ${descLabel}: ${skill.description}`,
            `- ${readLabel}: \`read_skill({ "skill_id": "${skill.id}" })\``,
        ];
        if (skill.declaredAllowedTools && skill.declaredAllowedTools.length > 0) {
            lines.push(`- ${allowedToolsLabel}: ${skill.declaredAllowedTools.join(', ')} (${allowedToolsHint})`);
        }
        return lines.join('\n');
    });
    return [title, ...blocks].join('\n\n');
}
