import React, { useState, useCallback, useMemo, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
import { useApp, useHostStyles } from '@modelcontextprotocol/ext-apps/react';

/* ── Types ── */
interface N8nNode {
  name: string;
  type: string;
  category: 'triggers' | 'actions' | 'transform' | 'flow' | 'ai';
  description: string;
  fullDescription: string;
  badge: 'Core' | 'Community';
  operations?: string[];
  version: string;
}

type CategoryFilter = 'all' | 'triggers' | 'actions' | 'transform' | 'flow' | 'ai' | 'core' | 'community';

/* ── Node Database ── */
const NODE_DB: N8nNode[] = [
  // Triggers
  { name: 'Webhook', type: 'n8n-nodes-base.webhook', category: 'triggers', badge: 'Core', version: '2.0', description: 'Starts workflow on HTTP request', fullDescription: 'Starts the workflow when an HTTP request is received. Supports GET, POST, PUT, DELETE methods with custom headers and authentication.', operations: ['Listen for requests', 'Respond with data', 'Custom auth'] },
  { name: 'Schedule Trigger', type: 'n8n-nodes-base.scheduleTrigger', category: 'triggers', badge: 'Core', version: '1.2', description: 'Runs workflow on a schedule', fullDescription: 'Triggers workflows at specific intervals using cron expressions or simple interval settings. Supports timezone configuration.', operations: ['Cron expression', 'Simple interval', 'Specific times'] },
  { name: 'Chat Trigger', type: '@n8n/n8n-nodes-langchain.chatTrigger', category: 'triggers', badge: 'Core', version: '1.1', description: 'Starts workflow from chat input', fullDescription: 'Provides a chat interface that triggers workflows with user messages. Ideal for building conversational AI workflows.', operations: ['Receive message', 'Send response', 'Chat memory'] },
  { name: 'Form Trigger', type: 'n8n-nodes-base.formTrigger', category: 'triggers', badge: 'Core', version: '2.2', description: 'Starts workflow from form submission', fullDescription: 'Creates a web form that triggers the workflow when submitted. Supports custom fields, validation, and file uploads.', operations: ['Custom fields', 'Validation', 'File uploads'] },
  { name: 'Manual Trigger', type: 'n8n-nodes-base.manualTrigger', category: 'triggers', badge: 'Core', version: '1.0', description: 'Manually start the workflow', fullDescription: 'Allows manual execution of the workflow from the n8n editor. Useful for testing and one-off runs.', operations: ['Manual execution'] },
  { name: 'Email Trigger (IMAP)', type: 'n8n-nodes-base.imapEmail', category: 'triggers', badge: 'Core', version: '2.1', description: 'Triggers on new emails via IMAP', fullDescription: 'Monitors an email inbox via IMAP and triggers the workflow when new emails arrive. Supports filtering by sender, subject, and labels.', operations: ['Monitor inbox', 'Filter emails', 'Download attachments'] },
  { name: 'Kafka Trigger', type: 'n8n-nodes-base.kafkaTrigger', category: 'triggers', badge: 'Core', version: '1.0', description: 'Triggers on Kafka messages', fullDescription: 'Listens to Kafka topics and triggers workflows when new messages arrive. Supports consumer groups and offset management.', operations: ['Subscribe to topic', 'Consumer groups', 'Offset management'] },
  { name: 'MQTT Trigger', type: 'n8n-nodes-base.mqttTrigger', category: 'triggers', badge: 'Core', version: '1.0', description: 'Triggers on MQTT messages', fullDescription: 'Subscribes to MQTT topics and triggers workflows when messages are published. Supports QoS levels and wildcard topics.', operations: ['Subscribe', 'QoS levels', 'Wildcard topics'] },

  // Actions
  { name: 'HTTP Request', type: 'n8n-nodes-base.httpRequest', category: 'actions', badge: 'Core', version: '4.2', description: 'Make HTTP requests to any API', fullDescription: 'Versatile HTTP client supporting all methods, authentication types, headers, query parameters, and body formats. Supports pagination and batch requests.', operations: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'Pagination'] },
  { name: 'Slack', type: 'n8n-nodes-base.slack', category: 'actions', badge: 'Core', version: '2.2', description: 'Send messages and manage Slack', fullDescription: 'Interact with Slack workspaces. Send messages, create channels, manage users, upload files, and react to messages.', operations: ['Send message', 'Create channel', 'Upload file', 'Add reaction', 'Get user'] },
  { name: 'Gmail', type: 'n8n-nodes-base.gmail', category: 'actions', badge: 'Core', version: '2.1', description: 'Send and manage Gmail emails', fullDescription: 'Full Gmail integration. Send, read, label, and manage emails. Supports attachments, HTML content, and label management.', operations: ['Send email', 'Get emails', 'Add label', 'Delete email', 'Reply'] },
  { name: 'Discord', type: 'n8n-nodes-base.discord', category: 'actions', badge: 'Core', version: '2.1', description: 'Send messages to Discord', fullDescription: 'Send messages to Discord channels using webhooks or bot tokens. Supports embeds, attachments, and thread management.', operations: ['Send message', 'Send webhook', 'Manage channels', 'Create thread'] },
  { name: 'Telegram', type: 'n8n-nodes-base.telegram', category: 'actions', badge: 'Core', version: '1.2', description: 'Send Telegram messages and media', fullDescription: 'Full Telegram Bot API integration. Send messages, photos, documents, and manage chats, groups, and inline keyboards.', operations: ['Send message', 'Send photo', 'Send document', 'Edit message', 'Pin message'] },
  { name: 'GitHub', type: 'n8n-nodes-base.github', category: 'actions', badge: 'Core', version: '1.0', description: 'Manage GitHub repos and issues', fullDescription: 'Interact with GitHub repositories, issues, pull requests, releases, and users. Supports file operations and webhook creation.', operations: ['Create issue', 'Create PR', 'Get file', 'Create release', 'List repos'] },
  { name: 'Jira', type: 'n8n-nodes-base.jira', category: 'actions', badge: 'Core', version: '1.0', description: 'Manage Jira issues and projects', fullDescription: 'Full Jira Software integration. Create and manage issues, transitions, comments, sprints, and project configurations.', operations: ['Create issue', 'Update issue', 'Transition', 'Add comment', 'Search'] },
  { name: 'Notion', type: 'n8n-nodes-base.notion', category: 'actions', badge: 'Core', version: '2.2', description: 'Manage Notion pages and databases', fullDescription: 'Create, read, and update Notion pages and databases. Supports rich content blocks, database queries, and property management.', operations: ['Create page', 'Update page', 'Query database', 'Append block', 'Search'] },
  { name: 'Airtable', type: 'n8n-nodes-base.airtable', category: 'actions', badge: 'Core', version: '2.1', description: 'Manage Airtable records', fullDescription: 'CRUD operations on Airtable bases. Create, read, update, delete records, and manage attachments with formula support.', operations: ['Create record', 'Get record', 'Update record', 'Delete record', 'List records'] },
  { name: 'Google Sheets', type: 'n8n-nodes-base.googleSheets', category: 'actions', badge: 'Core', version: '4.5', description: 'Read and write Google Sheets data', fullDescription: 'Full Google Sheets integration. Read, write, append, and update spreadsheet data. Supports ranges, formatting, and batch operations.', operations: ['Read rows', 'Append row', 'Update row', 'Delete row', 'Clear sheet'] },
  { name: 'Postgres', type: 'n8n-nodes-base.postgres', category: 'actions', badge: 'Core', version: '2.5', description: 'Execute PostgreSQL queries', fullDescription: 'Connect to PostgreSQL databases. Execute queries, insert, update, delete rows. Supports parameterized queries and transactions.', operations: ['Select', 'Insert', 'Update', 'Delete', 'Execute query'] },
  { name: 'MySQL', type: 'n8n-nodes-base.mySql', category: 'actions', badge: 'Core', version: '2.4', description: 'Execute MySQL queries', fullDescription: 'Connect to MySQL/MariaDB databases. Full CRUD operations with parameterized queries, transactions, and stored procedures.', operations: ['Select', 'Insert', 'Update', 'Delete', 'Execute query'] },
  { name: 'MongoDB', type: 'n8n-nodes-base.mongoDb', category: 'actions', badge: 'Core', version: '1.1', description: 'Manage MongoDB documents', fullDescription: 'Connect to MongoDB databases. Insert, find, update, delete documents with full query support and aggregation pipelines.', operations: ['Find', 'Insert', 'Update', 'Delete', 'Aggregate'] },
  { name: 'Redis', type: 'n8n-nodes-base.redis', category: 'actions', badge: 'Core', version: '1.0', description: 'Get and set Redis keys', fullDescription: 'Interact with Redis key-value store. Get, set, delete keys with support for TTL, lists, sets, and pub/sub operations.', operations: ['Get', 'Set', 'Delete', 'Push', 'Pop', 'Publish'] },
  { name: 'S3', type: 'n8n-nodes-base.s3', category: 'actions', badge: 'Core', version: '1.0', description: 'Manage AWS S3 buckets and objects', fullDescription: 'Full S3 integration. Upload, download, list, and delete objects in S3 buckets. Supports presigned URLs and multipart uploads.', operations: ['Upload', 'Download', 'List', 'Delete', 'Copy', 'Presigned URL'] },
  { name: 'SendGrid', type: 'n8n-nodes-base.sendGrid', category: 'actions', badge: 'Core', version: '1.0', description: 'Send emails via SendGrid', fullDescription: 'Send transactional and marketing emails through SendGrid. Supports templates, dynamic content, and contact list management.', operations: ['Send email', 'Use template', 'Manage contacts', 'Create list'] },
  { name: 'Twilio', type: 'n8n-nodes-base.twilio', category: 'actions', badge: 'Core', version: '1.0', description: 'Send SMS and make calls via Twilio', fullDescription: 'Send SMS messages, make voice calls, and manage Twilio resources. Supports MMS, programmable voice, and lookup API.', operations: ['Send SMS', 'Make call', 'Send MMS', 'Lookup number'] },
  { name: 'Stripe', type: 'n8n-nodes-base.stripe', category: 'actions', badge: 'Core', version: '1.0', description: 'Manage Stripe payments', fullDescription: 'Full Stripe integration. Manage customers, charges, subscriptions, invoices, and payment intents with webhook support.', operations: ['Create charge', 'Create customer', 'Create subscription', 'Manage invoice', 'Refund'] },

  // Transform
  { name: 'Code', type: 'n8n-nodes-base.code', category: 'transform', badge: 'Core', version: '2.0', description: 'Run custom JavaScript or Python code', fullDescription: 'Execute custom JavaScript or Python code to transform data. Full access to all items, built-in helpers, and npm packages.', operations: ['Run JavaScript', 'Run Python', 'Transform data'] },
  { name: 'Set', type: 'n8n-nodes-base.set', category: 'transform', badge: 'Core', version: '3.4', description: 'Set field values on items', fullDescription: 'Set, rename, or remove fields on workflow items. Supports expressions, static values, and field mapping.', operations: ['Set value', 'Remove field', 'Rename field', 'Add field'] },
  { name: 'IF', type: 'n8n-nodes-base.if', category: 'transform', badge: 'Core', version: '2.2', description: 'Route items based on conditions', fullDescription: 'Conditional routing node. Evaluate conditions on item data and route to true/false output branches.', operations: ['Compare values', 'String contains', 'Number comparison', 'Boolean check', 'Regex match'] },
  { name: 'Switch', type: 'n8n-nodes-base.switch', category: 'transform', badge: 'Core', version: '3.2', description: 'Route items to multiple outputs', fullDescription: 'Multi-output routing node. Define rules to route items to different output branches based on field values or expressions.', operations: ['Value routing', 'Expression routing', 'Fallback output'] },
  { name: 'Merge', type: 'n8n-nodes-base.merge', category: 'transform', badge: 'Core', version: '3.0', description: 'Merge data from multiple inputs', fullDescription: 'Combine data from two inputs using various modes: append, combine by position, combine by matching fields, or keep unique.', operations: ['Append', 'Combine by position', 'Match fields', 'Keep unique', 'SQL query mode'] },
  { name: 'Split In Batches', type: 'n8n-nodes-base.splitInBatches', category: 'transform', badge: 'Core', version: '3.0', description: 'Process items in smaller batches', fullDescription: 'Split a large set of items into smaller batches for processing. Useful for rate-limited APIs and memory management.', operations: ['Set batch size', 'Process sequentially'] },
  { name: 'Aggregate', type: 'n8n-nodes-base.aggregate', category: 'transform', badge: 'Core', version: '1.0', description: 'Aggregate items into a single item', fullDescription: 'Combine multiple items into a single item by aggregating field values into arrays, sums, counts, or other operations.', operations: ['Aggregate to array', 'Sum', 'Count', 'Min/Max', 'Average'] },
  { name: 'Compare Datasets', type: 'n8n-nodes-base.compareDatasets', category: 'transform', badge: 'Core', version: '1.0', description: 'Compare two sets of data', fullDescription: 'Compare two input datasets and identify items that are only in input 1, only in input 2, or in both. Useful for sync operations.', operations: ['Find new items', 'Find removed items', 'Find common items'] },
  { name: 'Rename Keys', type: 'n8n-nodes-base.renameKeys', category: 'transform', badge: 'Core', version: '2.0', description: 'Rename item field keys', fullDescription: 'Rename one or more keys in item data. Supports regex patterns and bulk rename operations.', operations: ['Rename key', 'Regex rename', 'Bulk rename'] },
  { name: 'Remove Duplicates', type: 'n8n-nodes-base.removeDuplicates', category: 'transform', badge: 'Core', version: '1.0', description: 'Remove duplicate items', fullDescription: 'Remove duplicate items from the data based on one or more fields. Keeps the first or last occurrence.', operations: ['Remove by field', 'Keep first', 'Keep last'] },
  { name: 'Sort', type: 'n8n-nodes-base.sort', category: 'transform', badge: 'Core', version: '1.0', description: 'Sort items by field values', fullDescription: 'Sort workflow items by one or more fields in ascending or descending order. Supports multiple sort criteria.', operations: ['Sort ascending', 'Sort descending', 'Multi-field sort'] },
  { name: 'Limit', type: 'n8n-nodes-base.limit', category: 'transform', badge: 'Core', version: '1.0', description: 'Limit the number of items', fullDescription: 'Limit the number of items passing through the workflow. Keep the first N or last N items.', operations: ['Keep first N', 'Keep last N'] },
  { name: 'HTML Extract', type: 'n8n-nodes-base.html', category: 'transform', badge: 'Core', version: '1.0', description: 'Extract data from HTML content', fullDescription: 'Extract structured data from HTML using CSS selectors. Supports extracting text, attributes, and nested elements.', operations: ['CSS selector', 'Extract text', 'Extract attributes', 'Extract table'] },
  { name: 'XML', type: 'n8n-nodes-base.xml', category: 'transform', badge: 'Core', version: '1.0', description: 'Convert between JSON and XML', fullDescription: 'Parse XML to JSON or convert JSON to XML. Supports attribute handling, namespaces, and custom formatting.', operations: ['XML to JSON', 'JSON to XML'] },
  { name: 'Markdown', type: 'n8n-nodes-base.markdown', category: 'transform', badge: 'Core', version: '1.0', description: 'Convert between Markdown and HTML', fullDescription: 'Convert Markdown to HTML or HTML to Markdown. Supports GFM, tables, code blocks, and custom extensions.', operations: ['Markdown to HTML', 'HTML to Markdown'] },
  { name: 'Crypto', type: 'n8n-nodes-base.crypto', category: 'transform', badge: 'Core', version: '1.0', description: 'Hash and encrypt data', fullDescription: 'Cryptographic operations: hashing (MD5, SHA-256, etc.), HMAC, encoding (Base64, Hex), and random value generation.', operations: ['Hash', 'HMAC', 'Base64 encode', 'Base64 decode', 'Random UUID'] },

  // Flow
  { name: 'Wait', type: 'n8n-nodes-base.wait', category: 'flow', badge: 'Core', version: '1.4', description: 'Pause workflow execution', fullDescription: 'Pause the workflow for a specified duration or until a webhook/form is received. Useful for delays and human-in-the-loop workflows.', operations: ['Wait time', 'Wait for webhook', 'Wait for form'] },
  { name: 'Loop Over Items', type: 'n8n-nodes-base.splitInBatches', category: 'flow', badge: 'Core', version: '3.0', description: 'Loop over items one at a time', fullDescription: 'Process items in a loop, executing the connected nodes for each item or batch. Supports custom batch sizes and error handling.', operations: ['Loop items', 'Set batch size', 'Error handling'] },
  { name: 'Error Trigger', type: 'n8n-nodes-base.errorTrigger', category: 'flow', badge: 'Core', version: '1.0', description: 'Trigger on workflow errors', fullDescription: 'Catches errors from other workflows and triggers this workflow. Provides error details, workflow info, and execution context.', operations: ['Catch errors', 'Error details', 'Retry logic'] },
  { name: 'Execute Workflow', type: 'n8n-nodes-base.executeWorkflow', category: 'flow', badge: 'Core', version: '1.2', description: 'Run another workflow as sub-workflow', fullDescription: 'Execute another workflow as a sub-workflow. Pass data to the sub-workflow and receive results. Supports async execution.', operations: ['Execute by ID', 'Pass input data', 'Receive output', 'Async mode'] },
  { name: 'Respond to Webhook', type: 'n8n-nodes-base.respondToWebhook', category: 'flow', badge: 'Core', version: '1.1', description: 'Send response back to webhook', fullDescription: 'Send a custom HTTP response back to the webhook that triggered the workflow. Set status code, headers, and body.', operations: ['Set status code', 'Set headers', 'Set body', 'JSON response'] },
  { name: 'No Operation', type: 'n8n-nodes-base.noOp', category: 'flow', badge: 'Core', version: '1.0', description: 'Do nothing — pass data through', fullDescription: 'A no-operation node that passes data through unchanged. Useful as a placeholder, junction point, or for organizing complex workflows.', operations: ['Pass through'] },

  // AI
  { name: 'OpenAI', type: '@n8n/n8n-nodes-langchain.openAi', category: 'ai', badge: 'Core', version: '1.6', description: 'Use OpenAI models (GPT, DALL·E)', fullDescription: 'Interact with OpenAI APIs. Generate text with GPT models, create images with DALL·E, generate embeddings, and moderate content.', operations: ['Chat completion', 'Text generation', 'Image generation', 'Embeddings', 'Moderation'] },
  { name: 'AI Agent', type: '@n8n/n8n-nodes-langchain.agent', category: 'ai', badge: 'Core', version: '1.7', description: 'Create autonomous AI agents', fullDescription: 'Build autonomous AI agents that can use tools, make decisions, and execute multi-step tasks. Supports various agent types and tool configurations.', operations: ['Tools agent', 'Conversational', 'Plan and execute', 'Custom tools'] },
  { name: 'Vector Store', type: '@n8n/n8n-nodes-langchain.vectorStore', category: 'ai', badge: 'Core', version: '1.0', description: 'Store and retrieve vector embeddings', fullDescription: 'Manage vector embeddings for semantic search and RAG workflows. Supports Pinecone, Qdrant, Supabase, and in-memory stores.', operations: ['Insert documents', 'Similarity search', 'Delete vectors', 'Get by ID'] },
  { name: 'Text Classifier', type: '@n8n/n8n-nodes-langchain.textClassifier', category: 'ai', badge: 'Core', version: '1.0', description: 'Classify text into categories', fullDescription: 'Use AI to classify text into predefined categories. Supports multi-label classification with custom category definitions.', operations: ['Classify text', 'Multi-label', 'Custom categories'] },
  { name: 'Summarization Chain', type: '@n8n/n8n-nodes-langchain.chainSummarization', category: 'ai', badge: 'Core', version: '2.0', description: 'Summarize long documents', fullDescription: 'Summarize long texts using AI chains. Supports stuff, map-reduce, and refine strategies for handling large documents.', operations: ['Stuff summary', 'Map reduce', 'Refine', 'Custom prompts'] },
  { name: 'Information Extractor', type: '@n8n/n8n-nodes-langchain.informationExtractor', category: 'ai', badge: 'Core', version: '1.0', description: 'Extract structured data from text', fullDescription: 'Use AI to extract structured information from unstructured text. Define output schema and let the AI parse relevant data.', operations: ['Define schema', 'Extract entities', 'Parse data'] },
  { name: 'Sentiment Analysis', type: '@n8n/n8n-nodes-langchain.sentimentAnalysis', category: 'ai', badge: 'Core', version: '1.0', description: 'Analyze text sentiment', fullDescription: 'Analyze the sentiment of text content. Returns positive, negative, or neutral classification with confidence scores.', operations: ['Analyze sentiment', 'Confidence score', 'Batch analysis'] },
  { name: 'Chat Memory', type: '@n8n/n8n-nodes-langchain.memoryBufferWindow', category: 'ai', badge: 'Core', version: '1.3', description: 'Manage conversation memory', fullDescription: 'Store and retrieve conversation history for AI chat workflows. Supports window buffer, summary, and persistent memory backends.', operations: ['Buffer window', 'Summary memory', 'PostgreSQL backend', 'Redis backend'] },
  { name: 'Embeddings', type: '@n8n/n8n-nodes-langchain.embeddingsOpenAi', category: 'ai', badge: 'Core', version: '1.2', description: 'Generate text embeddings', fullDescription: 'Generate vector embeddings from text using OpenAI, Cohere, HuggingFace, or other providers. Essential for RAG and semantic search.', operations: ['OpenAI embeddings', 'Cohere embeddings', 'Custom model', 'Batch embed'] },
];

/* ── Category config ── */
const CATEGORY_EMOJI: Record<string, string> = {
  triggers: '⚡',
  actions: '🔧',
  transform: '🔄',
  flow: '↗️',
  ai: '🤖',
};

const CATEGORY_PILLS: { label: string; value: CategoryFilter }[] = [
  { label: 'All', value: 'all' },
  { label: '⚡ Triggers', value: 'triggers' },
  { label: '🔧 Actions', value: 'actions' },
  { label: '🔄 Transform', value: 'transform' },
  { label: '↗️ Flow', value: 'flow' },
  { label: '🤖 AI', value: 'ai' },
  { label: '🏷️ Core', value: 'core' },
  { label: '🌐 Community', value: 'community' },
];

/* ── Main App ── */
function NodeExplorerApp() {
  const [toolInput, setToolInput] = useState<{ action: string; query: string; category: string } | null>(null);
  const [searchQuery, setSearchQuery] = useState('');
  const [activeCategory, setActiveCategory] = useState<CategoryFilter>('all');
  const [expandedNode, setExpandedNode] = useState<string | null>(null);
  const [hasServerTools, setHasServerTools] = useState(false);
  const [isSearching, setIsSearching] = useState(false);

  const { app, isConnected, error } = useApp({
    appInfo: { name: 'NodeExplorer', version: '1.0.0' },
    capabilities: {},
    onAppCreated: (app) => {
      app.ontoolinput = (params) => {
        const args = params.arguments as unknown as { action: string; query: string; category: string };
        if (args) {
          setToolInput(args);
          if (args.query) setSearchQuery(args.query);
          if (args.category && args.category !== 'all') {
            setActiveCategory(args.category as CategoryFilter);
          }
        }
      };
      app.ontoolresult = () => {
        setIsSearching(false);
      };
      app.ontoolcancelled = () => {
        setIsSearching(false);
      };
    },
  });

  useHostStyles(app, app?.getHostContext());

  useEffect(() => {
    if (app) {
      const caps = app.getHostCapabilities();
      setHasServerTools(!!caps?.serverTools);
    }
  }, [app, isConnected]);

  const filteredNodes = useMemo(() => {
    let nodes = NODE_DB;
    if (activeCategory !== 'all') {
      if (activeCategory === 'core') {
        nodes = nodes.filter((n) => n.badge === 'Core');
      } else if (activeCategory === 'community') {
        nodes = nodes.filter((n) => n.badge === 'Community');
      } else {
        nodes = nodes.filter((n) => n.category === activeCategory);
      }
    }
    if (searchQuery.trim()) {
      const q = searchQuery.toLowerCase();
      nodes = nodes.filter(
        (n) =>
          n.name.toLowerCase().includes(q) ||
          n.type.toLowerCase().includes(q) ||
          n.description.toLowerCase().includes(q) ||
          n.category.toLowerCase().includes(q)
      );
    }
    return nodes;
  }, [activeCategory, searchQuery]);

  const doLiveSearch = useCallback(async () => {
    if (!app || !searchQuery.trim()) return;
    setIsSearching(true);
    try {
      if (hasServerTools) {
        await app.callServerTool({
          name: 'n8n_search_nodes',
          arguments: { query: searchQuery },
        });
      } else {
        await app.sendMessage({
          text: `Search n8n nodes for: "${searchQuery}"`,
        });
      }
    } catch {
      // silently handle
    } finally {
      setTimeout(() => setIsSearching(false), 2000);
    }
  }, [app, searchQuery, hasServerTools]);

  const toggleExpand = useCallback((nodeType: string) => {
    setExpandedNode((prev) => (prev === nodeType ? null : nodeType));
  }, []);

  if (error) {
    return (
      <div className="page">
        <div className="empty-state">
          <div className="icon">⚠️</div>
          <h3>Connection Error</h3>
          <p>{error.message}</p>
        </div>
      </div>
    );
  }

  if (!isConnected) {
    return (
      <div className="page" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: 300 }}>
        <div style={{ textAlign: 'center' }}>
          <div className="spinner" style={{ width: 32, height: 32, margin: '0 auto 16px' }} />
          <p style={{ color: 'var(--color-text-muted)', fontSize: 13 }}>Connecting to host…</p>
        </div>
      </div>
    );
  }

  return (
    <div className="page">
      {/* Header */}
      <div className="page-header">
        <div>
          <div className="page-title">
            <span className="icon">🧩</span>
            n8n Node Explorer
          </div>
          <div className="page-subtitle">1,084+ nodes available — browse and discover</div>
        </div>
        <div className="page-actions">
          <span className="badge n8n" style={{ fontSize: 12, padding: '4px 12px' }}>
            {filteredNodes.length} shown
          </span>
        </div>
      </div>

      {/* Search */}
      <div style={{ marginBottom: 20 }}>
        <div style={{ display: 'flex', gap: 8 }}>
          <div className="search-box" style={{ flex: 1 }}>
            <span className="search-icon">🔍</span>
            <input
              className="input"
              placeholder="Search nodes by name, type, or description…"
              value={searchQuery}
              onChange={(e) => setSearchQuery(e.target.value)}
              style={{ fontSize: 14, padding: '10px 12px 10px 36px' }}
            />
          </div>
          <button
            className="btn btn-primary"
            onClick={doLiveSearch}
            disabled={isSearching || !searchQuery.trim()}
            style={{ flexShrink: 0, opacity: (isSearching || !searchQuery.trim()) ? 0.6 : 1 }}
          >
            {isSearching ? (
              <><span className="spinner" style={{ width: 14, height: 14 }} /> Searching…</>
            ) : (
              '🔎 Search n8n'
            )}
          </button>
        </div>
      </div>

      {/* Category pills */}
      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 20 }}>
        {CATEGORY_PILLS.map((pill) => (
          <button
            key={pill.value}
            onClick={() => setActiveCategory(pill.value)}
            style={{
              padding: '6px 14px',
              borderRadius: 20,
              border: '1px solid',
              borderColor: activeCategory === pill.value ? 'var(--color-n8n)' : 'var(--color-border)',
              background: activeCategory === pill.value ? 'rgba(255,109,90,0.12)' : 'var(--color-bg-secondary)',
              color: activeCategory === pill.value ? 'var(--color-n8n)' : 'var(--color-text-muted)',
              fontSize: 12,
              fontWeight: 600,
              cursor: 'pointer',
              transition: 'all 150ms ease',
              fontFamily: 'inherit',
            }}
          >
            {pill.label}
          </button>
        ))}
      </div>

      {/* Results count */}
      <div style={{ fontSize: 12, color: 'var(--color-text-dim)', marginBottom: 12 }}>
        {filteredNodes.length === NODE_DB.length
          ? `Showing all ${NODE_DB.length} built-in nodes`
          : `${filteredNodes.length} of ${NODE_DB.length} nodes`}
        {searchQuery && ` matching "${searchQuery}"`}
      </div>

      {/* Node grid */}
      {filteredNodes.length === 0 ? (
        <div className="empty-state" style={{ padding: 48 }}>
          <div className="icon">🔍</div>
          <h3>No Nodes Found</h3>
          <p>Try a different search term or category filter.</p>
        </div>
      ) : (
        <div className="grid grid-3">
          {filteredNodes.map((node) => {
            const isExpanded = expandedNode === node.type;
            return (
              <div
                key={node.type}
                className="card"
                onClick={() => toggleExpand(node.type)}
                style={{
                  cursor: 'pointer',
                  borderColor: isExpanded ? 'var(--color-n8n)' : undefined,
                  transition: 'all 150ms ease',
                }}
              >
                <div className="card-body" style={{ padding: 16 }}>
                  <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12, marginBottom: 8 }}>
                    <span style={{
                      fontSize: 28,
                      lineHeight: 1,
                      width: 40,
                      height: 40,
                      display: 'flex',
                      alignItems: 'center',
                      justifyContent: 'center',
                      background: 'var(--color-bg-tertiary)',
                      borderRadius: 'var(--radius-sm)',
                      flexShrink: 0,
                    }}>
                      {CATEGORY_EMOJI[node.category] || '📦'}
                    </span>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ fontWeight: 700, fontSize: 14, marginBottom: 2 }}>
                        {node.name}
                      </div>
                      <div style={{
                        fontSize: 11,
                        color: 'var(--color-text-dim)',
                        fontFamily: 'var(--font-mono)',
                        overflow: 'hidden',
                        textOverflow: 'ellipsis',
                        whiteSpace: 'nowrap',
                      }}>
                        {node.type}
                      </div>
                    </div>
                    <span className={`badge ${node.badge === 'Core' ? 'active' : 'n8n'}`} style={{ flexShrink: 0 }}>
                      {node.badge}
                    </span>
                  </div>

                  <p style={{ fontSize: 12, color: 'var(--color-text-muted)', lineHeight: 1.5, marginBottom: isExpanded ? 12 : 0 }}>
                    {node.description}
                  </p>

                  {/* Expanded details */}
                  {isExpanded && (
                    <div style={{
                      borderTop: '1px solid var(--color-border)',
                      paddingTop: 12,
                      marginTop: 4,
                      animation: 'fadeIn 150ms ease',
                    }}>
                      <p style={{ fontSize: 12, color: 'var(--color-text)', lineHeight: 1.6, marginBottom: 12 }}>
                        {node.fullDescription}
                      </p>

                      {node.operations && node.operations.length > 0 && (
                        <div style={{ marginBottom: 10 }}>
                          <span style={{ fontSize: 10, fontWeight: 700, color: 'var(--color-text-muted)', textTransform: 'uppercase', letterSpacing: '0.5px' }}>
                            Operations
                          </span>
                          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 6 }}>
                            {node.operations.map((op) => (
                              <span
                                key={op}
                                style={{
                                  padding: '2px 8px',
                                  borderRadius: 4,
                                  background: 'var(--color-bg)',
                                  border: '1px solid var(--color-border)',
                                  fontSize: 11,
                                  color: 'var(--color-text-muted)',
                                }}
                              >
                                {op}
                              </span>
                            ))}
                          </div>
                        </div>
                      )}

                      <div style={{ display: 'flex', gap: 12, fontSize: 11, color: 'var(--color-text-dim)' }}>
                        <span>Version: <strong style={{ color: 'var(--color-text)' }}>{node.version}</strong></span>
                        <span>Category: <strong style={{ color: 'var(--color-text)' }}>{node.category}</strong></span>
                      </div>
                    </div>
                  )}
                </div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

/* ── Mount ── */
const root = document.getElementById('root');
if (root) {
  createRoot(root).render(<NodeExplorerApp />);
}

export default NodeExplorerApp;
