/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { SettingScope } from '../config/settings.js';
import { Storage, loadSkillsFromDir, } from '@google/gemini-cli-core';
import { cloneFromGit } from '../config/extensions/github.js';
import extract from 'extract-zip';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import * as os from 'node:os';
/**
 * Shared logic for building the core skill action message while allowing the
 * caller to control how each scope and its path are rendered (e.g., bolding or
 * dimming).
 *
 * This function ONLY returns the description of what happened. It is up to the
 * caller to append any interface-specific guidance (like "Use /skills reload"
 * or "Restart required").
 */
export function renderSkillActionFeedback(result, formatScope) {
    const { skillName, action, status, error } = result;
    if (status === 'error') {
        return (error ||
            `An error occurred while attempting to ${action} skill "${skillName}".`);
    }
    if (status === 'no-op') {
        return `Skill "${skillName}" is already ${action === 'enable' ? 'enabled' : 'disabled'}.`;
    }
    const isEnable = action === 'enable';
    const actionVerb = isEnable ? 'enabled' : 'disabled';
    const preposition = isEnable
        ? 'by removing it from the disabled list in'
        : 'by adding it to the disabled list in';
    const formatScopeItem = (s) => {
        const label = s.scope === SettingScope.Workspace ? 'workspace' : s.scope.toLowerCase();
        return formatScope(label, s.path);
    };
    const totalAffectedScopes = [
        ...result.modifiedScopes,
        ...result.alreadyInStateScopes,
    ];
    if (totalAffectedScopes.length === 2) {
        const s1 = formatScopeItem(totalAffectedScopes[0]);
        const s2 = formatScopeItem(totalAffectedScopes[1]);
        if (isEnable) {
            return `Skill "${skillName}" ${actionVerb} ${preposition} ${s1} and ${s2} settings.`;
        }
        else {
            return `Skill "${skillName}" is now disabled in both ${s1} and ${s2} settings.`;
        }
    }
    const s = formatScopeItem(totalAffectedScopes[0]);
    return `Skill "${skillName}" ${actionVerb} ${preposition} ${s} settings.`;
}
/**
 * Central logic for installing a skill from a remote URL or local path.
 */
export async function installSkill(source, scope, subpath, onLog, requestConsent = () => Promise.resolve(true)) {
    let sourcePath = source;
    let tempDirToClean = undefined;
    const isGitUrl = source.startsWith('git@') ||
        source.startsWith('http://') ||
        source.startsWith('https://');
    const isSkillFile = source.toLowerCase().endsWith('.skill');
    try {
        if (isGitUrl) {
            tempDirToClean = await fs.mkdtemp(path.join(os.tmpdir(), 'gemini-skill-'));
            sourcePath = tempDirToClean;
            onLog(`Cloning skill from ${source}...`);
            // Reuse existing robust git cloning utility from extension manager.
            await cloneFromGit({
                source,
                type: 'git',
            }, tempDirToClean);
        }
        else if (isSkillFile) {
            tempDirToClean = await fs.mkdtemp(path.join(os.tmpdir(), 'gemini-skill-'));
            sourcePath = tempDirToClean;
            onLog(`Extracting skill from ${source}...`);
            await extract(path.resolve(source), { dir: tempDirToClean });
        }
        // If a subpath is provided, resolve it against the cloned/local root.
        if (subpath) {
            sourcePath = path.join(sourcePath, subpath);
        }
        sourcePath = path.resolve(sourcePath);
        // Quick security check to prevent directory traversal out of temp dir when cloning
        if (tempDirToClean &&
            !sourcePath.startsWith(path.resolve(tempDirToClean))) {
            throw new Error('Invalid path: Directory traversal not allowed.');
        }
        onLog(`Searching for skills in ${sourcePath}...`);
        const skills = await loadSkillsFromDir(sourcePath);
        if (skills.length === 0) {
            throw new Error(`No valid skills found in ${source}${subpath ? ` at path "${subpath}"` : ''}. Ensure a SKILL.md file exists with valid frontmatter.`);
        }
        const workspaceDir = process.cwd();
        const storage = new Storage(workspaceDir);
        const targetDir = scope === 'workspace'
            ? storage.getProjectSkillsDir()
            : Storage.getUserSkillsDir();
        if (!(await requestConsent(skills, targetDir))) {
            throw new Error('Skill installation cancelled by user.');
        }
        await fs.mkdir(targetDir, { recursive: true });
        const installedSkills = [];
        for (const skill of skills) {
            const skillName = skill.name;
            const skillDir = path.dirname(skill.location);
            const destPath = path.join(targetDir, skillName);
            const exists = await fs.stat(destPath).catch(() => null);
            if (exists) {
                onLog(`Skill "${skillName}" already exists. Overwriting...`);
                await fs.rm(destPath, { recursive: true, force: true });
            }
            await fs.cp(skillDir, destPath, { recursive: true });
            installedSkills.push({ name: skillName, location: destPath });
        }
        return installedSkills;
    }
    finally {
        if (tempDirToClean) {
            await fs.rm(tempDirToClean, { recursive: true, force: true });
        }
    }
}
/**
 * Central logic for linking a skill from a local path via symlink.
 */
export async function linkSkill(source, scope, onLog, requestConsent = () => Promise.resolve(true)) {
    const sourcePath = path.resolve(source);
    onLog(`Searching for skills in ${sourcePath}...`);
    const skills = await loadSkillsFromDir(sourcePath);
    if (skills.length === 0) {
        throw new Error(`No valid skills found in "${sourcePath}". Ensure a SKILL.md file exists with valid frontmatter.`);
    }
    // Check for internal name collisions
    const seenNames = new Map();
    for (const skill of skills) {
        if (seenNames.has(skill.name)) {
            throw new Error(`Duplicate skill name "${skill.name}" found at multiple locations:\n  - ${seenNames.get(skill.name)}\n  - ${skill.location}`);
        }
        seenNames.set(skill.name, skill.location);
    }
    const workspaceDir = process.cwd();
    const storage = new Storage(workspaceDir);
    const targetDir = scope === 'workspace'
        ? storage.getProjectSkillsDir()
        : Storage.getUserSkillsDir();
    if (!(await requestConsent(skills, targetDir))) {
        throw new Error('Skill linking cancelled by user.');
    }
    await fs.mkdir(targetDir, { recursive: true });
    const linkedSkills = [];
    for (const skill of skills) {
        const skillName = skill.name;
        const skillSourceDir = path.dirname(skill.location);
        const destPath = path.join(targetDir, skillName);
        const exists = await fs.lstat(destPath).catch(() => null);
        if (exists) {
            onLog(`Skill "${skillName}" already exists at destination. Overwriting...`);
            await fs.rm(destPath, { recursive: true, force: true });
        }
        await fs.symlink(skillSourceDir, destPath, 'dir');
        linkedSkills.push({ name: skillName, location: destPath });
    }
    return linkedSkills;
}
/**
 * Central logic for uninstalling a skill by name.
 */
export async function uninstallSkill(name, scope) {
    const workspaceDir = process.cwd();
    const storage = new Storage(workspaceDir);
    const targetDir = scope === 'workspace'
        ? storage.getProjectSkillsDir()
        : Storage.getUserSkillsDir();
    // Load all skills in the target directory to find the one with the matching name
    const discoveredSkills = await loadSkillsFromDir(targetDir);
    const skillToUninstall = discoveredSkills.find((s) => s.name === name);
    if (!skillToUninstall) {
        // Fallback: Check if a directory with the given name exists.
        // This maintains backward compatibility for cases where the metadata might be missing or corrupted
        // but the directory name matches the user's request.
        const skillPath = path.resolve(targetDir, name);
        // Security check: ensure the resolved path is within the target directory to prevent path traversal
        if (!skillPath.startsWith(path.resolve(targetDir))) {
            return null;
        }
        const exists = await fs.lstat(skillPath).catch(() => null);
        if (!exists) {
            return null;
        }
        await fs.rm(skillPath, { recursive: true, force: true });
        return { location: skillPath };
    }
    const skillDir = path.dirname(skillToUninstall.location);
    await fs.rm(skillDir, { recursive: true, force: true });
    return { location: skillDir };
}
//# sourceMappingURL=skillUtils.js.map