/**
 * Search Routes
 *
 * Semantic and text search endpoints.
 */

import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import type { AppContext } from '../router.js';
import {
  SearchRequestSchema,
  type SearchResponse,
  type SearchResult,
} from '../types.js';
import { GraphQueries } from '../../graph/queries.js';

export const searchRoutes = new Hono<AppContext>();

/**
 * POST /search - Search codebase
 */
searchRoutes.post(
  '/',
  zValidator('json', SearchRequestSchema),
  async (c) => {
    const conn = c.get('conn');
    const embeddings = c.get('embeddings');
    const body = c.req.valid('json');

    if (!conn) {
      return c.json({ error: 'No database connection' }, 500);
    }

    try {
      let results: SearchResult[] = [];

      if (body.type === 'semantic' && embeddings) {
        // Semantic search using embeddings
        const searchResults = await embeddings.search(body.query, body.limit);

        results = searchResults.map((r) => ({
          qualifiedName: r.id,
          name: r.id.split('.').pop() ?? r.id,
          entityType: inferEntityType(r.id),
          score: r.score,
          description: r.metadata?.description as string | undefined,
          filePath: r.metadata?.filePath as string | undefined,
        }));
      } else {
        // Text search using graph queries
        const queries = new GraphQueries(conn);

        // Search functions
        if (!body.entityTypes || body.entityTypes.includes('Function')) {
          const functions = await queries.findFunctionsByName(body.query);
          results.push(
            ...functions.slice(0, body.limit).map((f) => ({
              qualifiedName: f.qualifiedName,
              name: f.name,
              entityType: 'Function' as const,
              filePath: f.filePath,
              lineNumber: f.lineStart,
              score: 1.0,
              description: f.docstring,
            }))
          );
        }

        // Search classes
        if (!body.entityTypes || body.entityTypes.includes('Class')) {
          const classes = await queries.findClassesByName(body.query);
          results.push(
            ...classes.slice(0, body.limit).map((cls) => ({
              qualifiedName: cls.qualifiedName,
              name: cls.name,
              entityType: 'Class' as const,
              filePath: cls.filePath,
              lineNumber: cls.lineStart,
              score: 1.0,
              description: cls.docstring,
            }))
          );
        }

        // Search files
        if (!body.entityTypes || body.entityTypes.includes('File')) {
          const files = await queries.findFilesByName(body.query);
          results.push(
            ...files.slice(0, body.limit).map((f) => ({
              qualifiedName: f.path,
              name: f.name,
              entityType: 'File' as const,
              filePath: f.path,
              score: 1.0,
            }))
          );
        }

        // Sort by score and limit
        results = results
          .sort((a, b) => b.score - a.score)
          .slice(0, body.limit);
      }

      // Filter by min score
      if (body.minScore !== undefined) {
        results = results.filter((r) => r.score >= body.minScore!);
      }

      const response: SearchResponse = {
        results,
        total: results.length,
        query: body.query,
        searchType: body.type,
      };

      return c.json(response);
    } catch (error) {
      return c.json(
        { error: error instanceof Error ? error.message : 'Unknown error' },
        500
      );
    }
  }
);

/**
 * GET /search/quick - Quick semantic search
 */
searchRoutes.get('/quick', async (c) => {
  const conn = c.get('conn');
  const embeddings = c.get('embeddings');
  const query = c.req.query('q');
  const limit = parseInt(c.req.query('limit') ?? '10', 10);

  if (!query) {
    return c.json({ error: 'Query parameter q is required' }, 400);
  }

  if (!conn) {
    return c.json({ error: 'No database connection' }, 500);
  }

  try {
    let results: SearchResult[] = [];

    if (embeddings) {
      const searchResults = await embeddings.search(query, limit);
      results = searchResults.map((r) => ({
        qualifiedName: r.id,
        name: r.id.split('.').pop() ?? r.id,
        entityType: inferEntityType(r.id),
        score: r.score,
      }));
    } else {
      // Fallback to text search
      const queries = new GraphQueries(conn);
      const functions = await queries.findFunctionsByName(query);
      results = functions.slice(0, limit).map((f) => ({
        qualifiedName: f.qualifiedName,
        name: f.name,
        entityType: 'Function' as const,
        filePath: f.filePath,
        score: 1.0,
      }));
    }

    return c.json<SearchResponse>({
      results,
      total: results.length,
      query,
      searchType: embeddings ? 'semantic' : 'text',
    });
  } catch (error) {
    return c.json(
      { error: error instanceof Error ? error.message : 'Unknown error' },
      500
    );
  }
});

/**
 * Infer entity type from qualified name
 */
function inferEntityType(qualifiedName: string): 'File' | 'Function' | 'Class' {
  if (/\.[a-z]+$/i.test(qualifiedName)) {
    return 'File';
  }
  const lastPart = qualifiedName.split('.').pop() ?? '';
  if (lastPart[0] === lastPart[0]?.toUpperCase() && !/[a-z][A-Z]/.test(lastPart.slice(1))) {
    return 'Class';
  }
  return 'Function';
}
