/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import {
  type CommandContext,
  type SlashCommand,
  type SlashCommandActionReturn,
  CommandKind,
} from './types.js';
import {
  type HistoryItemInfo,
  type HistoryItemSkillsList,
  MessageType,
} from '../types.js';
import { disableSkill, enableSkill } from '../../utils/skillSettings.js';

import { getAdminErrorMessage, getErrorMessage } from '@google/gemini-cli-core';
import {
  linkSkill,
  renderSkillActionFeedback,
} from '../../utils/skillUtils.js';
import { SettingScope } from '../../config/settings.js';
import {
  requestConsentInteractive,
  skillsConsentString,
} from '../../config/extensions/consent.js';

async function listAction(
  context: CommandContext,
  args: string,
): Promise<void | SlashCommandActionReturn> {
  const subArgs = args.trim().split(/\s+/);

  // Default to SHOWING descriptions. The user can hide them with 'nodesc'.
  let useShowDescriptions = true;
  let showAll = false;

  for (const arg of subArgs) {
    if (arg === 'nodesc' || arg === '--nodesc') {
      useShowDescriptions = false;
    } else if (arg === 'all' || arg === '--all') {
      showAll = true;
    }
  }

  const skillManager = context.services.agentContext?.config.getSkillManager();
  if (!skillManager) {
    context.ui.addItem({
      type: MessageType.ERROR,
      text: 'Could not retrieve skill manager.',
    });
    return;
  }

  const skills = showAll
    ? skillManager.getAllSkills()
    : skillManager.getAllSkills().filter((s) => !s.isBuiltin);

  const skillsListItem: HistoryItemSkillsList = {
    type: MessageType.SKILLS_LIST,
    skills: skills.map((skill) => ({
      name: skill.name,
      description: skill.description,
      disabled: skill.disabled,
      location: skill.location,
      body: skill.body,
      isBuiltin: skill.isBuiltin,
    })),
    showDescriptions: useShowDescriptions,
  };

  context.ui.addItem(skillsListItem);
}

async function linkAction(
  context: CommandContext,
  args: string,
): Promise<void | SlashCommandActionReturn> {
  const parts = args.trim().split(/\s+/);
  const sourcePath = parts[0];

  if (!sourcePath) {
    context.ui.addItem({
      type: MessageType.ERROR,
      text: 'Usage: /skills link <path> [--scope user|workspace]',
    });
    return;
  }

  let scopeArg = 'user';
  if (parts.length >= 3 && parts[1] === '--scope') {
    scopeArg = parts[2];
  } else if (parts.length >= 2 && parts[1].startsWith('--scope=')) {
    scopeArg = parts[1].split('=')[1];
  }

  const scope = scopeArg === 'workspace' ? 'workspace' : 'user';

  try {
    await linkSkill(
      sourcePath,
      scope,
      (msg) =>
        context.ui.addItem({
          type: MessageType.INFO,
          text: msg,
        }),
      async (skills, targetDir) => {
        const consentString = await skillsConsentString(
          skills,
          sourcePath,
          targetDir,
          true,
        );
        return requestConsentInteractive(
          consentString,
          context.ui.setConfirmationRequest.bind(context.ui),
        );
      },
    );

    context.ui.addItem({
      type: MessageType.INFO,
      text: `Successfully linked skills from "${sourcePath}" (${scope}).`,
    });

    if (context.services.agentContext?.config) {
      await context.services.agentContext.config.reloadSkills();
    }
  } catch (error) {
    context.ui.addItem({
      type: MessageType.ERROR,
      text: `Failed to link skills: ${getErrorMessage(error)}`,
    });
  }
}

async function disableAction(
  context: CommandContext,
  args: string,
): Promise<void | SlashCommandActionReturn> {
  const skillName = args.trim();
  if (!skillName) {
    context.ui.addItem({
      type: MessageType.ERROR,
      text: 'Please provide a skill name to disable.',
    });
    return;
  }
  const skillManager = context.services.agentContext?.config.getSkillManager();
  if (skillManager?.isAdminEnabled() === false) {
    context.ui.addItem(
      {
        type: MessageType.ERROR,
        text: getAdminErrorMessage(
          'Agent skills',
          context.services.agentContext?.config ?? undefined,
        ),
      },
      Date.now(),
    );
    return;
  }

  const skill = skillManager?.getSkill(skillName);
  if (!skill) {
    context.ui.addItem(
      {
        type: MessageType.ERROR,
        text: `Skill "${skillName}" not found.`,
      },
      Date.now(),
    );
    return;
  }

  const scope = context.services.settings.workspace.path
    ? SettingScope.Workspace
    : SettingScope.User;

  const result = disableSkill(context.services.settings, skillName, scope);

  let feedback = renderSkillActionFeedback(
    result,
    (label, path) => `${label} (${path})`,
  );
  if (result.status === 'success' || result.status === 'no-op') {
    feedback +=
      ' You can run "/skills reload" to refresh your current instance.';
  }

  context.ui.addItem({
    type: MessageType.INFO,
    text: feedback,
  });
}

async function enableAction(
  context: CommandContext,
  args: string,
): Promise<void | SlashCommandActionReturn> {
  const skillName = args.trim();
  if (!skillName) {
    context.ui.addItem({
      type: MessageType.ERROR,
      text: 'Please provide a skill name to enable.',
    });
    return;
  }

  const skillManager = context.services.agentContext?.config.getSkillManager();
  if (skillManager?.isAdminEnabled() === false) {
    context.ui.addItem(
      {
        type: MessageType.ERROR,
        text: getAdminErrorMessage(
          'Agent skills',
          context.services.agentContext?.config ?? undefined,
        ),
      },
      Date.now(),
    );
    return;
  }

  const result = enableSkill(context.services.settings, skillName);

  let feedback = renderSkillActionFeedback(
    result,
    (label, path) => `${label} (${path})`,
  );
  if (result.status === 'success' || result.status === 'no-op') {
    feedback +=
      ' You can run "/skills reload" to refresh your current instance.';
  }

  context.ui.addItem({
    type: MessageType.INFO,
    text: feedback,
  });
}

async function reloadAction(
  context: CommandContext,
): Promise<void | SlashCommandActionReturn> {
  const config = context.services.agentContext?.config;
  if (!config) {
    context.ui.addItem({
      type: MessageType.ERROR,
      text: 'Could not retrieve configuration.',
    });
    return;
  }

  const skillManager = config.getSkillManager();
  const beforeNames = new Set(skillManager.getSkills().map((s) => s.name));

  const startTime = Date.now();
  let pendingItemSet = false;
  const pendingTimeout = setTimeout(() => {
    context.ui.setPendingItem({
      type: MessageType.INFO,
      text: 'Reloading agent skills...',
    });
    pendingItemSet = true;
  }, 100);

  try {
    await config.reloadSkills();

    clearTimeout(pendingTimeout);
    if (pendingItemSet) {
      // If we showed the pending item, make sure it stays for at least 500ms
      // total to avoid a "flicker" where it appears and immediately disappears.
      const elapsed = Date.now() - startTime;
      const minVisibleDuration = 500;
      if (elapsed < minVisibleDuration) {
        await new Promise((resolve) =>
          setTimeout(resolve, minVisibleDuration - elapsed),
        );
      }
      context.ui.setPendingItem(null);
    }

    const afterSkills = skillManager.getSkills();
    const afterNames = new Set(afterSkills.map((s) => s.name));

    const added = afterSkills.filter((s) => !beforeNames.has(s.name));
    const removedCount = [...beforeNames].filter(
      (name) => !afterNames.has(name),
    ).length;

    let successText = 'Agent skills reloaded successfully.';
    const details: string[] = [];

    if (added.length > 0) {
      details.push(
        `${added.length} newly available skill${added.length > 1 ? 's' : ''}`,
      );
    }
    if (removedCount > 0) {
      details.push(
        `${removedCount} skill${removedCount > 1 ? 's' : ''} no longer available`,
      );
    }

    if (details.length > 0) {
      successText += ` ${details.join(' and ')}.`;
    }

    context.ui.addItem({
      type: 'info',
      text: successText,
      icon: '✓ ',
      color: 'green',
    } as HistoryItemInfo);
  } catch (error) {
    clearTimeout(pendingTimeout);
    if (pendingItemSet) {
      context.ui.setPendingItem(null);
    }
    context.ui.addItem({
      type: MessageType.ERROR,
      text: `Failed to reload skills: ${error instanceof Error ? error.message : String(error)}`,
    });
  }
}

function disableCompletion(
  context: CommandContext,
  partialArg: string,
): string[] {
  const skillManager = context.services.agentContext?.config.getSkillManager();
  if (!skillManager) {
    return [];
  }
  return skillManager
    .getAllSkills()
    .filter((s) => !s.disabled && s.name.startsWith(partialArg))
    .map((s) => s.name);
}

function enableCompletion(
  context: CommandContext,
  partialArg: string,
): string[] {
  const skillManager = context.services.agentContext?.config.getSkillManager();
  if (!skillManager) {
    return [];
  }
  return skillManager
    .getAllSkills()
    .filter((s) => s.disabled && s.name.startsWith(partialArg))
    .map((s) => s.name);
}

import { parseSlashCommand } from '../../utils/commands.js';

export const skillsCommand: SlashCommand = {
  name: 'skills',
  description:
    'List, enable, disable, or reload Gemini CLI agent skills. Usage: /skills [list | disable <name> | enable <name> | reload]',
  kind: CommandKind.BUILT_IN,
  autoExecute: false,
  subCommands: [
    {
      name: 'list',
      description:
        'List available agent skills. Usage: /skills list [nodesc] [all]',
      kind: CommandKind.BUILT_IN,
      action: listAction,
    },
    {
      name: 'link',
      description:
        'Link an agent skill from a local path. Usage: /skills link <path> [--scope user|workspace]',
      kind: CommandKind.BUILT_IN,
      action: linkAction,
    },
    {
      name: 'disable',
      description: 'Disable a skill by name. Usage: /skills disable <name>',
      kind: CommandKind.BUILT_IN,
      action: disableAction,
      completion: disableCompletion,
    },
    {
      name: 'enable',
      description:
        'Enable a disabled skill by name. Usage: /skills enable <name>',
      kind: CommandKind.BUILT_IN,
      action: enableAction,
      completion: enableCompletion,
    },
    {
      name: 'reload',
      altNames: ['refresh'],
      description:
        'Reload the list of discovered skills. Usage: /skills reload',
      kind: CommandKind.BUILT_IN,
      action: reloadAction,
    },
  ],
  action: async (context, args) => {
    if (args) {
      const parsed = parseSlashCommand(`/${args}`, skillsCommand.subCommands!);
      if (parsed.commandToExecute?.action) {
        return parsed.commandToExecute.action(context, parsed.args);
      }
    }
    return listAction(context, args);
  },
};
