const { logger } = require('@librechat/data-schemas');
const {
  ADDED_AGENT_ID,
  initializeAgent,
  validateAgentModel,
  resolveAgentScopedSkillIds,
  resolveModelSpecSkillIds,
  loadAddedAgent: loadAddedAgentFn,
} = require('@librechat/api');
const { isEphemeralAgentId } = require('librechat-data-provider');
const { filterFilesByAgentAccess } = require('~/server/services/Files/permissions');
const { getMCPServerTools } = require('~/server/services/Config');
const { canAuthorSkillFiles } = require('./skillDeps');
const db = require('~/models');

const loadAddedAgent = (params) =>
  loadAddedAgentFn(params, { getAgent: db.getAgent, getMCPServerTools });

/**
 * Process addedConvo for parallel agent execution.
 * Creates a parallel agent config from an added conversation.
 *
 * When an added agent has no incoming edges, it becomes a start node
 * and runs in parallel with the primary agent automatically.
 *
 * Edge cases handled:
 * - Primary agent has edges (handoffs): Added agent runs in parallel with primary,
 *   but doesn't participate in the primary's handoff graph
 * - Primary agent has agent_ids (legacy chain): Added agent runs in parallel with primary,
 *   but doesn't participate in the chain
 * - Primary agent has both: Added agent is independent, runs parallel from start
 *
 * @param {Object} params
 * @param {import('express').Request} params.req
 * @param {import('express').Response} params.res
 * @param {Object} params.endpointOption - The endpoint option containing addedConvo
 * @param {Object} params.modelsConfig - The models configuration
 * @param {Function} params.logViolation - Function to log violations
 * @param {Function} params.loadTools - Function to load agent tools
 * @param {Array} params.requestFiles - Request files
 * @param {string} params.conversationId - The conversation ID
 * @param {string} [params.parentMessageId] - The parent message ID for thread filtering
 * @param {Set} params.allowedProviders - Set of allowed providers
 * @param {Map} params.agentConfigs - Map of agent configs to add to
 * @param {string} params.primaryAgentId - The primary agent ID
 * @param {Object|undefined} params.userMCPAuthMap - User MCP auth map to merge into
 * @param {Array} [params.accessibleSkillIds] - Full VIEW-accessible skill IDs for the user
 * @param {Array} [params.editableSkillIds] - Full EDIT-accessible skill IDs for the user
 * @param {boolean} [params.skillsCapabilityEnabled] - Whether endpoint Skills are enabled
 * @param {boolean} [params.ephemeralSkillsToggle] - Per-request ephemeral Skills badge state
 * @param {boolean} [params.skillCreateAllowed] - Whether the user can create Skills
 * @param {Record<string, boolean>} [params.skillStates] - Per-user Skill active overrides
 * @param {boolean} [params.defaultActiveOnShare] - Default active state for shared Skills
 * @param {boolean} [params.codeEnvAvailable] - `execute_code` capability flag;
 *   forwarded verbatim to the added agent's `initializeAgent`. @see
 *   InitializeAgentParams.codeEnvAvailable for full semantics.
 * @returns {Promise<{userMCPAuthMap: Object|undefined}>} The updated userMCPAuthMap
 */
const processAddedConvo = async ({
  req,
  res,
  endpointOption,
  modelsConfig,
  logViolation,
  loadTools,
  requestFiles,
  conversationId,
  parentMessageId,
  allowedProviders,
  agentConfigs,
  primaryAgentId,
  primaryAgent,
  userMCPAuthMap,
  accessibleSkillIds = [],
  editableSkillIds = [],
  skillsCapabilityEnabled = false,
  ephemeralSkillsToggle = false,
  skillCreateAllowed = false,
  skillStates,
  defaultActiveOnShare,
  codeEnvAvailable,
  memoryAvailable,
}) => {
  const addedConvo = endpointOption.addedConvo;
  if (addedConvo == null) {
    return { userMCPAuthMap };
  }

  logger.debug('[processAddedConvo] Processing added conversation', {
    model: addedConvo.model,
    agentId: addedConvo.agent_id,
    endpoint: addedConvo.endpoint,
  });

  try {
    const addedAgent = await loadAddedAgent({ req, conversation: addedConvo, primaryAgent });
    if (!addedAgent) {
      return { userMCPAuthMap };
    }

    const addedValidation = await validateAgentModel({
      req,
      res,
      modelsConfig,
      logViolation,
      agent: addedAgent,
    });

    if (!addedValidation.isValid) {
      logger.warn(
        `[processAddedConvo] Added agent validation failed: ${addedValidation.error?.message}`,
      );
      return { userMCPAuthMap };
    }

    const selectedModelSpec =
      addedConvo.spec && Array.isArray(req.config?.modelSpecs?.list)
        ? req.config.modelSpecs.list.find((modelSpec) => modelSpec.name === addedConvo.spec)
        : null;

    if (
      addedAgent &&
      isEphemeralAgentId(addedAgent.id) &&
      selectedModelSpec &&
      Object.hasOwn(selectedModelSpec, 'skills')
    ) {
      if (selectedModelSpec.skills === true) {
        addedAgent.skills_enabled = true;
        delete addedAgent.skills;
      } else if (selectedModelSpec.skills === false) {
        addedAgent.skills_enabled = false;
        addedAgent.skills = [];
      } else if (Array.isArray(selectedModelSpec.skills)) {
        const resolvedSkillIds = await resolveModelSpecSkillIds({
          names: selectedModelSpec.skills,
          accessibleSkillIds,
          getSkillByName: db.getSkillByName,
        });
        addedAgent.skills_enabled = true;
        addedAgent.skills = resolvedSkillIds.map((id) => id.toString());
      }
    }

    const scopedSkillIds = resolveAgentScopedSkillIds({
      agent: addedAgent,
      accessibleSkillIds,
      skillsCapabilityEnabled,
      ephemeralSkillsToggle,
    });
    const scopedEditableSkillIds = resolveAgentScopedSkillIds({
      agent: addedAgent,
      accessibleSkillIds: editableSkillIds,
      skillsCapabilityEnabled,
      ephemeralSkillsToggle,
    });

    const addedConfig = await initializeAgent(
      {
        req,
        res,
        loadTools,
        requestFiles,
        conversationId,
        parentMessageId,
        agent: addedAgent,
        endpointOption,
        allowedProviders,
        accessibleSkillIds: scopedSkillIds,
        skillAuthoringAvailable: canAuthorSkillFiles({
          agent: addedAgent,
          scopedEditableSkillIds,
          skillCreateAllowed,
          skillsCapabilityEnabled,
          ephemeralSkillsToggle,
        }),
        codeEnvAvailable,
        memoryAvailable,
        skillStates,
        defaultActiveOnShare,
      },
      {
        getFiles: db.getFiles,
        getUserKey: db.getUserKey,
        getMessages: db.getMessages,
        getConvoFiles: db.getConvoFiles,
        updateFilesUsage: db.updateFilesUsage,
        getUserCodeFiles: db.getUserCodeFiles,
        getUserKeyValues: db.getUserKeyValues,
        getToolFilesByIds: db.getToolFilesByIds,
        getCodeGeneratedFiles: db.getCodeGeneratedFiles,
        filterFilesByAgentAccess,
        listSkillsByAccess: db.listSkillsByAccess,
        listAlwaysApplySkills: db.listAlwaysApplySkills,
        getSkillByName: db.getSkillByName,
      },
    );

    if (userMCPAuthMap != null) {
      Object.assign(userMCPAuthMap, addedConfig.userMCPAuthMap ?? {});
    } else {
      userMCPAuthMap = addedConfig.userMCPAuthMap;
    }

    const addedAgentId = addedConfig.id || ADDED_AGENT_ID;
    agentConfigs.set(addedAgentId, addedConfig);

    // No edges needed - agent without incoming edges becomes a start node
    // and runs in parallel with the primary agent automatically.
    // This is independent of any edges/agent_ids the primary agent has.

    logger.debug(
      `[processAddedConvo] Added parallel agent: ${addedAgentId} (primary: ${primaryAgentId}, ` +
        `primary has edges: ${!!endpointOption.edges}, primary has agent_ids: ${!!endpointOption.agent_ids})`,
    );

    return { userMCPAuthMap };
  } catch (err) {
    logger.error('[processAddedConvo] Error processing addedConvo for parallel agent', err);
    return { userMCPAuthMap };
  }
};

module.exports = {
  processAddedConvo,
  ADDED_AGENT_ID,
};
