/**
 * Web Tool
 *
 * Tool for web search and page fetching.
 */

import { z } from 'zod';
import { defineTool, type ToolResult } from './types.js';

/**
 * Search result interface
 */
export interface SearchResult {
  title: string;
  url: string;
  snippet: string;
}

/**
 * Web Tool
 *
 * Search the web or fetch content from a URL.
 */
export const webTool = defineTool({
  name: 'web',
  description: `Search the web or fetch content from a URL.

Use mode 'search' to search the web for information.
Use mode 'fetch' to get content from a specific URL.

Useful for:
- Finding documentation
- Looking up error messages
- Getting latest library information
- Reading external resources`,
  category: 'search',
  schema: z.object({
    mode: z.enum(['search', 'fetch']).describe("Operation mode: 'search' for web search, 'fetch' for URL content"),
    query: z.string().optional().describe('Search query (required for search mode)'),
    url: z.string().optional().describe('URL to fetch (required for fetch mode)'),
    max_results: z.number().int().min(1).max(20).optional().default(5).describe('Maximum search results'),
  }),
  async execute(input): Promise<ToolResult<{
    query?: string;
    url?: string;
    title?: string;
    content?: string;
    results?: SearchResult[];
    count?: number;
    length?: number;
  }>> {
    const { mode, query, url, max_results = 5 } = input;

    if (mode === 'search') {
      if (!query) {
        return {
          success: false,
          error: 'Query required for search mode',
        };
      }
      return webSearch(query, max_results);
    } else if (mode === 'fetch') {
      if (!url) {
        return {
          success: false,
          error: 'URL required for fetch mode',
        };
      }
      return webFetch(url);
    } else {
      return {
        success: false,
        error: `Unknown mode: ${mode}`,
        suggestions: ["Use mode 'search' or 'fetch'"],
      };
    }
  },
});

/**
 * Perform web search using DuckDuckGo
 */
async function webSearch(
  query: string,
  maxResults: number
): Promise<ToolResult<{ query: string; results: SearchResult[]; count: number }>> {
  try {
    // Try to dynamically import duck-duck-scrape (lighter than duckduckgo-search)
    const ddg = await import('duck-duck-scrape').catch(() => null);

    if (!ddg) {
      // Fallback: use a simple fetch-based approach
      return {
        success: false,
        error: 'Web search not available',
        suggestions: [
          'Install duck-duck-scrape: npm install duck-duck-scrape',
          'Or use the fetch mode with a specific URL',
        ],
      };
    }

    const searchResults = await ddg.search(query, {
      safeSearch: ddg.SafeSearchType.MODERATE,
    });

    const results: SearchResult[] = searchResults.results
      .slice(0, maxResults)
      .map((r: { title: string; url: string; description: string }) => ({
        title: r.title || '',
        url: r.url || '',
        snippet: r.description || '',
      }));

    return {
      success: true,
      data: {
        query,
        results,
        count: results.length,
      },
    };
  } catch (e) {
    return {
      success: false,
      error: `Search failed: ${e}`,
    };
  }
}

/**
 * Fetch content from a URL
 */
async function webFetch(
  url: string
): Promise<ToolResult<{ url: string; title: string; content: string; length: number }>> {
  try {
    // Use native fetch (available in Node 18+)
    const response = await fetch(url, {
      headers: {
        'User-Agent': 'Mozilla/5.0 (compatible; emdash-agent/1.0)',
        Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
      },
      redirect: 'follow',
    });

    if (!response.ok) {
      return {
        success: false,
        error: `HTTP ${response.status}: ${response.statusText}`,
      };
    }

    const html = await response.text();

    // Simple HTML to text extraction without external dependencies
    const { title, content } = extractTextFromHtml(html);

    // Truncate if too long
    const truncatedContent =
      content.length > 10000 ? content.slice(0, 10000) + '\n\n[Content truncated...]' : content;

    return {
      success: true,
      data: {
        url,
        title,
        content: truncatedContent,
        length: truncatedContent.length,
      },
    };
  } catch (e) {
    return {
      success: false,
      error: `Fetch failed: ${e}`,
    };
  }
}

/**
 * Extract text content from HTML without external dependencies
 */
function extractTextFromHtml(html: string): { title: string; content: string } {
  // Extract title
  const titleMatch = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
  const title = titleMatch ? decodeHtmlEntities(titleMatch[1].trim()) : '';

  // Remove script and style elements
  let text = html
    .replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, '')
    .replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, '')
    .replace(/<nav\b[^>]*>[\s\S]*?<\/nav>/gi, '')
    .replace(/<header\b[^>]*>[\s\S]*?<\/header>/gi, '')
    .replace(/<footer\b[^>]*>[\s\S]*?<\/footer>/gi, '')
    .replace(/<aside\b[^>]*>[\s\S]*?<\/aside>/gi, '');

  // Replace block elements with newlines
  text = text
    .replace(/<(div|p|br|li|tr|h[1-6])[^>]*>/gi, '\n')
    .replace(/<\/?(div|p|li|tr|h[1-6])[^>]*>/gi, '\n');

  // Remove all remaining HTML tags
  text = text.replace(/<[^>]+>/g, '');

  // Decode HTML entities
  text = decodeHtmlEntities(text);

  // Clean up whitespace
  text = text
    .replace(/\r\n/g, '\n')
    .replace(/\n{3,}/g, '\n\n')
    .replace(/[ \t]+/g, ' ')
    .split('\n')
    .map((line) => line.trim())
    .filter((line) => line.length > 0)
    .join('\n');

  return { title, content: text };
}

/**
 * Decode common HTML entities
 */
function decodeHtmlEntities(text: string): string {
  const entities: Record<string, string> = {
    '&amp;': '&',
    '&lt;': '<',
    '&gt;': '>',
    '&quot;': '"',
    '&#39;': "'",
    '&apos;': "'",
    '&nbsp;': ' ',
    '&ndash;': '-',
    '&mdash;': '--',
    '&lsquo;': "'",
    '&rsquo;': "'",
    '&ldquo;': '"',
    '&rdquo;': '"',
    '&hellip;': '...',
    '&copy;': '(c)',
    '&reg;': '(R)',
    '&trade;': '(TM)',
  };

  let result = text;
  for (const [entity, char] of Object.entries(entities)) {
    result = result.replace(new RegExp(entity, 'g'), char);
  }

  // Handle numeric entities
  result = result.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(parseInt(code, 10)));
  result = result.replace(/&#x([0-9a-fA-F]+);/g, (_, code) =>
    String.fromCharCode(parseInt(code, 16))
  );

  return result;
}

/**
 * All web tools
 */
export const webTools = [webTool];
