import { useState, useCallback } from 'react';
import MiniSearch from 'minisearch';

// ---- Types ----

interface MkDocsIndexDoc {
  location: string;
  title: string;
  text: string;
}

interface MkDocsSearchIndex {
  docs: MkDocsIndexDoc[];
}

export interface DocSearchResult {
  title: string;
  snippet: string;
  location: string;
  docUrl: string;
  qrUrl: string;
}

// ---- Module-level cache (survives unmount/remount) ----

let cachedIndex: MiniSearch | null = null;
let cachedDocs: MkDocsIndexDoc[] = [];
let indexPromise: Promise<void> | null = null;

// ---- Helpers ----

function stripHtml(html: string): string {
  return html.replace(/<[^>]*>/g, '');
}

function isLocalDev(): boolean {
  const host = window.location.hostname;
  return host === 'localhost' || host === '127.0.0.1' || host.startsWith('192.168.');
}

function getDocsBaseUrl(): string {
  if (isLocalDev()) {
    return `http://localhost:${import.meta.env.VITE_MKDOCS_PORT || '4003'}`;
  }
  const domain = window.location.hostname.replace(/^app\./, '');
  return `${window.location.protocol}//docs.${domain}`;
}

function getSearchIndexUrl(): string {
  // Always use same-origin proxy path (Vite proxy in dev, nginx in production)
  return '/mkdocs-proxy/search/search_index.json';
}

function getDocPageUrl(location: string): string {
  const base = getDocsBaseUrl();
  return `${base}/${location}`;
}

function extractSnippet(text: string, query: string, maxLength = 150): string {
  const lowerText = text.toLowerCase();
  const lowerQuery = query.toLowerCase();
  const index = lowerText.indexOf(lowerQuery);

  if (index === -1) {
    return text.substring(0, maxLength) + (text.length > maxLength ? '...' : '');
  }

  const start = Math.max(0, index - 50);
  const end = Math.min(text.length, index + query.length + 100);
  let snippet = text.substring(start, end);

  if (start > 0) snippet = '...' + snippet;
  if (end < text.length) snippet = snippet + '...';

  return snippet;
}

// ---- Hook ----

export function useDocsSearch() {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [results, setResults] = useState<DocSearchResult[]>([]);
  const [indexReady, setIndexReady] = useState(!!cachedIndex);

  const loadIndex = useCallback(async () => {
    // Already cached
    if (cachedIndex) {
      setIndexReady(true);
      return;
    }

    // Already loading — wait for existing promise
    if (indexPromise) {
      setLoading(true);
      try {
        await indexPromise;
        setIndexReady(true);
        setError(null);
      } catch (e) {
        setError(e instanceof Error ? e.message : 'Failed to load docs index');
      } finally {
        setLoading(false);
      }
      return;
    }

    setLoading(true);
    setError(null);

    indexPromise = (async () => {
      const url = getSearchIndexUrl();
      const res = await fetch(url);
      if (!res.ok) throw new Error(`Failed to fetch docs index (${res.status})`);
      const data: MkDocsSearchIndex = await res.json();

      const idx = new MiniSearch<MkDocsIndexDoc>({
        fields: ['title', 'text'],
        storeFields: ['title', 'text', 'location'],
        searchOptions: {
          boost: { title: 10 },
          prefix: true,
          fuzzy: 0.2,
        },
      });

      const docs = data.docs
        .filter((d) => d.title && d.location)
        .map((d, i) => ({
          id: i,
          title: stripHtml(d.title),
          text: stripHtml(d.text),
          location: d.location,
        }));

      idx.addAll(docs);
      cachedDocs = data.docs.map((d) => ({
        title: stripHtml(d.title),
        text: stripHtml(d.text),
        location: d.location,
      }));
      cachedIndex = idx;
    })();

    try {
      await indexPromise;
      setIndexReady(true);
    } catch (e) {
      indexPromise = null; // Allow retry
      const msg = e instanceof Error ? e.message : 'Failed to load docs index';
      setError(msg);
    } finally {
      setLoading(false);
    }
  }, []);

  const search = useCallback(
    (query: string) => {
      if (!cachedIndex || !query || query.length < 2) {
        setResults([]);
        return;
      }

      const raw = cachedIndex.search(query).slice(0, 10);

      const mapped: DocSearchResult[] = raw
        .filter((r) => cachedDocs[r.id])
        .map((r) => {
          const doc = cachedDocs[r.id]!;
          const docUrl = getDocPageUrl(doc.location);
          return {
            title: doc.title,
            snippet: extractSnippet(doc.text, query),
            location: doc.location,
            docUrl,
            qrUrl: `/api/qr?text=${encodeURIComponent(docUrl)}&size=150`,
          };
        });

      setResults(mapped);
    },
    [],
  );

  const retry = useCallback(() => {
    cachedIndex = null;
    cachedDocs = [];
    indexPromise = null;
    setError(null);
    setIndexReady(false);
    loadIndex();
  }, [loadIndex]);

  return { loading, error, indexReady, results, loadIndex, search, retry };
}
