/**
 * MCP tool registry – metadata and dispatch.
 *
 * Centralizes the mapping between tool names, their metadata (name,
 * description, inputSchema) and the handler functions that execute
 * tool calls. The {@link getToolMetadataArray} function feeds the
 * MCP `ListTools` response, while {@link dispatchToolCall} routes
 * incoming `CallTool` requests to the correct handler.
 *
 * @module server/toolRegistry
 */

import { handleGetMEPs, getMEPsToolMetadata } from '../tools/getMEPs.js';
import { handleGetMEPDetails, getMEPDetailsToolMetadata } from '../tools/getMEPDetails.js';
import { handleGetPlenarySessions, getPlenarySessionsToolMetadata } from '../tools/getPlenarySessions.js';
import { handleGetVotingRecords, getVotingRecordsToolMetadata } from '../tools/getVotingRecords.js';
import { handleSearchDocuments, searchDocumentsToolMetadata } from '../tools/searchDocuments.js';
import { handleGetCommitteeInfo, getCommitteeInfoToolMetadata } from '../tools/getCommitteeInfo.js';
import { handleGetParliamentaryQuestions, getParliamentaryQuestionsToolMetadata } from '../tools/getParliamentaryQuestions.js';

import { handleAnalyzeVotingPatterns, analyzeVotingPatternsToolMetadata } from '../tools/analyzeVotingPatterns.js';
import { handleTrackLegislation, trackLegislationToolMetadata } from '../tools/trackLegislation.js';
import { handleGenerateReport, generateReportToolMetadata } from '../tools/generateReport.js';

import { handleAssessMepInfluence, assessMepInfluenceToolMetadata } from '../tools/assessMepInfluence.js';
import { handleAnalyzeCoalitionDynamics, analyzeCoalitionDynamicsToolMetadata } from '../tools/analyzeCoalitionDynamics.js';
import { handleDetectVotingAnomalies, detectVotingAnomaliesToolMetadata } from '../tools/detectVotingAnomalies.js';
import { handleComparePoliticalGroups, comparePoliticalGroupsToolMetadata } from '../tools/comparePoliticalGroups.js';
import { handleAnalyzeLegislativeEffectiveness, analyzeLegislativeEffectivenessToolMetadata } from '../tools/analyzeLegislativeEffectiveness.js';
import { handleMonitorLegislativePipeline, monitorLegislativePipelineToolMetadata } from '../tools/monitorLegislativePipeline.js';

import { handleAnalyzeCommitteeActivity, analyzeCommitteeActivityToolMetadata } from '../tools/analyzeCommitteeActivity.js';
import { handleTrackMepAttendance, trackMepAttendanceToolMetadata } from '../tools/trackMepAttendance.js';

import { handleAnalyzeCountryDelegation, analyzeCountryDelegationToolMetadata } from '../tools/analyzeCountryDelegation.js';
import { handleGeneratePoliticalLandscape, generatePoliticalLandscapeToolMetadata } from '../tools/generatePoliticalLandscape.js';

import { handleNetworkAnalysis, networkAnalysisToolMetadata } from '../tools/networkAnalysis.js';
import { handleSentimentTracker, sentimentTrackerToolMetadata } from '../tools/sentimentTracker.js';
import { handleEarlyWarningSystem, earlyWarningSystemToolMetadata } from '../tools/earlyWarningSystem.js';
import { handleComparativeIntelligence, comparativeIntelligenceToolMetadata } from '../tools/comparativeIntelligence.js';
import { handleCorrelateIntelligence, correlateIntelligenceToolMetadata } from '../tools/correlateIntelligence.js';

import { handleGetCurrentMEPs, getCurrentMEPsToolMetadata } from '../tools/getCurrentMEPs.js';
import { handleGetSpeeches, getSpeechesToolMetadata } from '../tools/getSpeeches.js';
import { handleGetProcedures, getProceduresToolMetadata } from '../tools/getProcedures.js';
import { handleGetAdoptedTexts, getAdoptedTextsToolMetadata } from '../tools/getAdoptedTexts.js';
import { handleGetEvents, getEventsToolMetadata } from '../tools/getEvents.js';
import { handleGetMeetingActivities, getMeetingActivitiesToolMetadata } from '../tools/getMeetingActivities.js';
import { handleGetMeetingDecisions, getMeetingDecisionsToolMetadata } from '../tools/getMeetingDecisions.js';
import { handleGetMEPDeclarations, getMEPDeclarationsToolMetadata } from '../tools/getMEPDeclarations.js';

import { handleGetAllGeneratedStats, getAllGeneratedStatsToolMetadata } from '../tools/getAllGeneratedStats.js';

import { handleGetIncomingMEPs, getIncomingMEPsToolMetadata } from '../tools/getIncomingMEPs.js';
import { handleGetOutgoingMEPs, getOutgoingMEPsToolMetadata } from '../tools/getOutgoingMEPs.js';
import { handleGetHomonymMEPs, getHomonymMEPsToolMetadata } from '../tools/getHomonymMEPs.js';
import { handleGetPlenaryDocuments, getPlenaryDocumentsToolMetadata } from '../tools/getPlenaryDocuments.js';
import { handleGetCommitteeDocuments, getCommitteeDocumentsToolMetadata } from '../tools/getCommitteeDocuments.js';
import { handleGetPlenarySessionDocuments, getPlenarySessionDocumentsToolMetadata } from '../tools/getPlenarySessionDocuments.js';
import { handleGetPlenarySessionDocumentItems, getPlenarySessionDocumentItemsToolMetadata } from '../tools/getPlenarySessionDocumentItems.js';
import { handleGetControlledVocabularies, getControlledVocabulariesToolMetadata } from '../tools/getControlledVocabularies.js';
import { handleGetExternalDocuments, getExternalDocumentsToolMetadata } from '../tools/getExternalDocuments.js';
import { handleGetMeetingForeseenActivities, getMeetingForeseenActivitiesToolMetadata } from '../tools/getMeetingForeseenActivities.js';
import { handleGetProcedureEvents, getProcedureEventsToolMetadata } from '../tools/getProcedureEvents.js';
import { handleGetMeetingPlenarySessionDocuments, getMeetingPlenarySessionDocumentsToolMetadata } from '../tools/getMeetingPlenarySessionDocuments.js';
import { handleGetMeetingPlenarySessionDocumentItems, getMeetingPlenarySessionDocumentItemsToolMetadata } from '../tools/getMeetingPlenarySessionDocumentItems.js';

import { handleGetMEPsFeed, getMEPsFeedToolMetadata } from '../tools/getMEPsFeed.js';
import { handleGetEventsFeed, getEventsFeedToolMetadata } from '../tools/getEventsFeed.js';
import { handleGetProceduresFeed, getProceduresFeedToolMetadata } from '../tools/getProceduresFeed.js';
import { handleGetAdoptedTextsFeed, getAdoptedTextsFeedToolMetadata } from '../tools/getAdoptedTextsFeed.js';
import { handleGetMEPDeclarationsFeed, getMEPDeclarationsFeedToolMetadata } from '../tools/getMEPDeclarationsFeed.js';
import { handleGetDocumentsFeed, getDocumentsFeedToolMetadata } from '../tools/getDocumentsFeed.js';
import { handleGetPlenaryDocumentsFeed, getPlenaryDocumentsFeedToolMetadata } from '../tools/getPlenaryDocumentsFeed.js';
import { handleGetCommitteeDocumentsFeed, getCommitteeDocumentsFeedToolMetadata } from '../tools/getCommitteeDocumentsFeed.js';
import { handleGetPlenarySessionDocumentsFeed, getPlenarySessionDocumentsFeedToolMetadata } from '../tools/getPlenarySessionDocumentsFeed.js';
import { handleGetExternalDocumentsFeed, getExternalDocumentsFeedToolMetadata } from '../tools/getExternalDocumentsFeed.js';
import { handleGetParliamentaryQuestionsFeed, getParliamentaryQuestionsFeedToolMetadata } from '../tools/getParliamentaryQuestionsFeed.js';
import { handleGetCorporateBodiesFeed, getCorporateBodiesFeedToolMetadata } from '../tools/getCorporateBodiesFeed.js';
import { handleGetControlledVocabulariesFeed, getControlledVocabulariesFeedToolMetadata } from '../tools/getControlledVocabulariesFeed.js';
import { handleGetProcedureEventById, getProcedureEventByIdToolMetadata } from '../tools/getProcedureEventById.js';

import { handleGetLatestVotes, getLatestVotesToolMetadata } from '../tools/getLatestVotes.js';

import { handleGetServerHealth, getServerHealthToolMetadata } from '../tools/getServerHealth.js';

import { feedHealthTracker, FEED_TOOL_NAMES } from '../services/FeedHealthTracker.js';

import { ToolError } from '../tools/shared/errors.js';

import type { ToolHandler, ToolCategory, ToolResult, ToolMetadata } from './types.js';

/** Re-export types for consumers */
export type { ToolResult, ToolHandler, ToolMetadata, ToolCategory, CLIOptions } from './types.js';

/**
 * Helper: attach a category to a plain tool metadata object.
 * @internal
 */
function withCategory(
  meta: { name: string; description: string; inputSchema: unknown },
  category: ToolCategory
): ToolMetadata {
  return { ...meta, category };
}

/**
 * Returns the full ordered list of tool metadata for the MCP `ListTools` response.
 * Each entry includes the tool's `category` in addition to the standard MCP fields.
 */
export function getToolMetadataArray(): ToolMetadata[] {
  return [
    withCategory(getMEPsToolMetadata, 'core'),
    withCategory(getMEPDetailsToolMetadata, 'core'),
    withCategory(getPlenarySessionsToolMetadata, 'core'),
    withCategory(getVotingRecordsToolMetadata, 'core'),
    withCategory(searchDocumentsToolMetadata, 'core'),
    withCategory(getCommitteeInfoToolMetadata, 'core'),
    withCategory(getParliamentaryQuestionsToolMetadata, 'core'),
    withCategory(getServerHealthToolMetadata, 'core'),
    withCategory(analyzeVotingPatternsToolMetadata, 'advanced'),
    withCategory(trackLegislationToolMetadata, 'advanced'),
    withCategory(generateReportToolMetadata, 'advanced'),
    withCategory(assessMepInfluenceToolMetadata, 'osint'),
    withCategory(analyzeCoalitionDynamicsToolMetadata, 'osint'),
    withCategory(detectVotingAnomaliesToolMetadata, 'osint'),
    withCategory(comparePoliticalGroupsToolMetadata, 'osint'),
    withCategory(analyzeLegislativeEffectivenessToolMetadata, 'osint'),
    withCategory(monitorLegislativePipelineToolMetadata, 'osint'),
    withCategory(analyzeCommitteeActivityToolMetadata, 'osint'),
    withCategory(trackMepAttendanceToolMetadata, 'osint'),
    withCategory(analyzeCountryDelegationToolMetadata, 'osint'),
    withCategory(generatePoliticalLandscapeToolMetadata, 'osint'),
    withCategory(networkAnalysisToolMetadata, 'osint'),
    withCategory(sentimentTrackerToolMetadata, 'osint'),
    withCategory(earlyWarningSystemToolMetadata, 'osint'),
    withCategory(comparativeIntelligenceToolMetadata, 'osint'),
    withCategory(correlateIntelligenceToolMetadata, 'osint'),
    withCategory(getCurrentMEPsToolMetadata, 'phase4'),
    withCategory(getSpeechesToolMetadata, 'phase4'),
    withCategory(getProceduresToolMetadata, 'phase4'),
    withCategory(getAdoptedTextsToolMetadata, 'phase4'),
    withCategory(getEventsToolMetadata, 'phase4'),
    withCategory(getMeetingActivitiesToolMetadata, 'phase4'),
    withCategory(getMeetingDecisionsToolMetadata, 'phase4'),
    withCategory(getMEPDeclarationsToolMetadata, 'phase4'),
    withCategory(getIncomingMEPsToolMetadata, 'phase5'),
    withCategory(getOutgoingMEPsToolMetadata, 'phase5'),
    withCategory(getHomonymMEPsToolMetadata, 'phase5'),
    withCategory(getPlenaryDocumentsToolMetadata, 'phase5'),
    withCategory(getCommitteeDocumentsToolMetadata, 'phase5'),
    withCategory(getPlenarySessionDocumentsToolMetadata, 'phase5'),
    withCategory(getPlenarySessionDocumentItemsToolMetadata, 'phase5'),
    withCategory(getControlledVocabulariesToolMetadata, 'phase5'),
    withCategory(getExternalDocumentsToolMetadata, 'phase5'),
    withCategory(getMeetingForeseenActivitiesToolMetadata, 'phase5'),
    withCategory(getProcedureEventsToolMetadata, 'phase5'),
    withCategory(getMeetingPlenarySessionDocumentsToolMetadata, 'phase5'),
    withCategory(getMeetingPlenarySessionDocumentItemsToolMetadata, 'phase5'),
    withCategory(getAllGeneratedStatsToolMetadata, 'phase5'),
    withCategory(getMEPsFeedToolMetadata, 'feed'),
    withCategory(getEventsFeedToolMetadata, 'feed'),
    withCategory(getProceduresFeedToolMetadata, 'feed'),
    withCategory(getAdoptedTextsFeedToolMetadata, 'feed'),
    withCategory(getMEPDeclarationsFeedToolMetadata, 'feed'),
    withCategory(getDocumentsFeedToolMetadata, 'feed'),
    withCategory(getPlenaryDocumentsFeedToolMetadata, 'feed'),
    withCategory(getCommitteeDocumentsFeedToolMetadata, 'feed'),
    withCategory(getPlenarySessionDocumentsFeedToolMetadata, 'feed'),
    withCategory(getExternalDocumentsFeedToolMetadata, 'feed'),
    withCategory(getParliamentaryQuestionsFeedToolMetadata, 'feed'),
    withCategory(getCorporateBodiesFeedToolMetadata, 'feed'),
    withCategory(getControlledVocabulariesFeedToolMetadata, 'feed'),
    withCategory(getProcedureEventByIdToolMetadata, 'phase5'),
    withCategory(getLatestVotesToolMetadata, 'core'),
  ];
}

/**
 * Name → handler dispatch map. Populated once, reused for every call.
 */
const toolHandlers: Record<string, ToolHandler> = {
  'get_meps': handleGetMEPs,
  'get_mep_details': handleGetMEPDetails,
  'get_plenary_sessions': handleGetPlenarySessions,
  'get_voting_records': handleGetVotingRecords,
  'search_documents': handleSearchDocuments,
  'get_committee_info': handleGetCommitteeInfo,
  'get_parliamentary_questions': handleGetParliamentaryQuestions,
  'get_server_health': handleGetServerHealth,
  'analyze_voting_patterns': handleAnalyzeVotingPatterns,
  'track_legislation': handleTrackLegislation,
  'generate_report': handleGenerateReport,
  'assess_mep_influence': handleAssessMepInfluence,
  'analyze_coalition_dynamics': handleAnalyzeCoalitionDynamics,
  'detect_voting_anomalies': handleDetectVotingAnomalies,
  'compare_political_groups': handleComparePoliticalGroups,
  'analyze_legislative_effectiveness': handleAnalyzeLegislativeEffectiveness,
  'monitor_legislative_pipeline': handleMonitorLegislativePipeline,
  'analyze_committee_activity': handleAnalyzeCommitteeActivity,
  'track_mep_attendance': handleTrackMepAttendance,
  'analyze_country_delegation': handleAnalyzeCountryDelegation,
  'generate_political_landscape': handleGeneratePoliticalLandscape,
  'network_analysis': handleNetworkAnalysis,
  'sentiment_tracker': handleSentimentTracker,
  'early_warning_system': handleEarlyWarningSystem,
  'comparative_intelligence': handleComparativeIntelligence,
  'correlate_intelligence': handleCorrelateIntelligence,
  'get_current_meps': handleGetCurrentMEPs,
  'get_speeches': handleGetSpeeches,
  'get_procedures': handleGetProcedures,
  'get_adopted_texts': handleGetAdoptedTexts,
  'get_events': handleGetEvents,
  'get_meeting_activities': handleGetMeetingActivities,
  'get_meeting_decisions': handleGetMeetingDecisions,
  'get_mep_declarations': handleGetMEPDeclarations,
  'get_incoming_meps': handleGetIncomingMEPs,
  'get_outgoing_meps': handleGetOutgoingMEPs,
  'get_homonym_meps': handleGetHomonymMEPs,
  'get_plenary_documents': handleGetPlenaryDocuments,
  'get_committee_documents': handleGetCommitteeDocuments,
  'get_plenary_session_documents': handleGetPlenarySessionDocuments,
  'get_plenary_session_document_items': handleGetPlenarySessionDocumentItems,
  'get_controlled_vocabularies': handleGetControlledVocabularies,
  'get_external_documents': handleGetExternalDocuments,
  'get_meeting_foreseen_activities': handleGetMeetingForeseenActivities,
  'get_procedure_events': handleGetProcedureEvents,
  'get_meeting_plenary_session_documents': handleGetMeetingPlenarySessionDocuments,
  'get_meeting_plenary_session_document_items': handleGetMeetingPlenarySessionDocumentItems,
  'get_all_generated_stats': handleGetAllGeneratedStats,
  'get_meps_feed': handleGetMEPsFeed,
  'get_events_feed': handleGetEventsFeed,
  'get_procedures_feed': handleGetProceduresFeed,
  'get_adopted_texts_feed': handleGetAdoptedTextsFeed,
  'get_mep_declarations_feed': handleGetMEPDeclarationsFeed,
  'get_documents_feed': handleGetDocumentsFeed,
  'get_plenary_documents_feed': handleGetPlenaryDocumentsFeed,
  'get_committee_documents_feed': handleGetCommitteeDocumentsFeed,
  'get_plenary_session_documents_feed': handleGetPlenarySessionDocumentsFeed,
  'get_external_documents_feed': handleGetExternalDocumentsFeed,
  'get_parliamentary_questions_feed': handleGetParliamentaryQuestionsFeed,
  'get_corporate_bodies_feed': handleGetCorporateBodiesFeed,
  'get_controlled_vocabularies_feed': handleGetControlledVocabulariesFeed,
  'get_procedure_event_by_id': handleGetProcedureEventById,
  'get_latest_votes': handleGetLatestVotes,
};

for (const feedName of FEED_TOOL_NAMES) {
  if (toolHandlers[feedName] === undefined) {
    throw new Error(
      `Feed tool "${feedName}" is tracked by FeedHealthTracker but has no registered handler in toolRegistry`
    );
  }
}

/**
 * Parse the JSON envelope from a feed tool result, or `undefined` when
 * the content is missing/unparseable.
 *
 * @internal
 */
function parseFeedEnvelope(result: ToolResult): Record<string, unknown> | undefined {
  const text = result.content[0]?.text;
  if (typeof text !== 'string' || text === '') return undefined;
  try {
    const parsed: unknown = JSON.parse(text);
    if (parsed === null || typeof parsed !== 'object') return undefined;
    return parsed as Record<string, unknown>;
  } catch {
    return undefined;
  }
}

/**
 * Extract the upstream HTTP status code from a feed envelope, if any.
 *
 * @internal
 */
function extractUpstreamStatusCode(envelope: Record<string, unknown>): number | undefined {
  const upstream = envelope['upstream'];
  if (upstream === null || typeof upstream !== 'object') return undefined;
  const statusCode = (upstream as { statusCode?: unknown }).statusCode;
  return typeof statusCode === 'number' ? statusCode : undefined;
}

/**
 * Inspect an in-band feed envelope for an operational failure code.
 *
 * Feed tool handlers may convert transient upstream failures (timeouts,
 * rate limits, 5xx, enrichment failures) into an "unavailable" envelope
 * returned via a normal {@link ToolResult} instead of a thrown error.
 * Without inspection, {@link FeedHealthTracker.recordSuccess} would mark
 * such calls as healthy and `get_server_health` would lose visibility
 * into real upstream problems.
 *
 * Returns a short error message when the envelope encodes an operational
 * failure that should be recorded, or `undefined` for success/no-data
 * responses (including `NOT_FOUND`, which represents a definitive empty
 * window rather than an upstream malfunction).
 *
 * @internal
 */
function extractInBandFeedError(result: ToolResult): string | undefined {
  const envelope = parseFeedEnvelope(result);
  if (envelope === undefined) return undefined;
  if (envelope['status'] !== 'unavailable') return undefined;
  const code = typeof envelope['errorCode'] === 'string' ? envelope['errorCode'] : undefined;
  // NOT_FOUND (and bare unavailable responses without errorCode metadata)
  // represent a healthy empty-window response, not an upstream failure.
  if (code === undefined || code === 'NOT_FOUND') return undefined;
  const reason = typeof envelope['reason'] === 'string' ? envelope['reason'] : '';
  const statusCode = extractUpstreamStatusCode(envelope);
  const statusSuffix = statusCode !== undefined ? ` (upstream ${String(statusCode)})` : '';
  return reason !== '' ? `${code}${statusSuffix}: ${reason}` : `${code}${statusSuffix}`;
}

/**
 * Dispatches a tool call to the registered handler.
 *
 * Feed tool calls are automatically tracked by the {@link feedHealthTracker}
 * so that the `get_server_health` tool can report per-feed availability
 * without making upstream API calls. Both thrown errors and in-band
 * "unavailable" envelopes carrying an operational `errorCode`
 * (e.g. `UPSTREAM_TIMEOUT`, `UPSTREAM_ERROR`, `RATE_LIMIT`,
 * `ENRICHMENT_FAILED`) are recorded as failures; `NOT_FOUND` and
 * empty-but-healthy responses are recorded as successes.
 *
 * @param name - Tool name from the MCP `CallTool` request
 * @param args - Validated tool arguments
 * @returns Tool execution result
 * @throws {Error} If the tool name is not recognized
 */
export async function dispatchToolCall(
  name: string,
  args: unknown
): Promise<ToolResult> {
  const handler = toolHandlers[name];
  if (handler === undefined) {
    throw new Error(`Unknown tool: ${name}`);
  }

  if (feedHealthTracker.isFeedTool(name)) {
    try {
      const result = await handler(args);
      const inBandError = extractInBandFeedError(result);
      if (inBandError !== undefined) {
        feedHealthTracker.recordError(name, inBandError);
      } else {
        feedHealthTracker.recordSuccess(name);
      }
      return result;
    } catch (error: unknown) {
      const isValidationError =
        error instanceof ToolError && error.operation === 'validateInput';
      if (!isValidationError) {
        const msg = error instanceof Error ? error.message : 'Unknown error';
        feedHealthTracker.recordError(name, msg);
      }
      throw error;
    }
  }

  return handler(args);
}
