'use client';

import React, { useState, useRef, useCallback, useEffect } from 'react';
import i18n from '@cockpit/shared-i18n';

/**
 * Content search hook for JSON readable mode.
 * Uses the CSS Custom Highlight API for highlighting matches — no DOM mutation, no re-render.
 */
// Dynamically inject ::highlight styles (Turbopack/PostCSS does not recognize this pseudo-element, so it cannot go in globals.css)
const STYLE_ID = 'json-search-highlight-style';
function ensureHighlightStyle() {
  if (document.getElementById(STYLE_ID)) return;
  const style = document.createElement('style');
  style.id = STYLE_ID;
  style.textContent = `::highlight(json-search){background:rgba(217,119,6,.3)}::highlight(json-search-current){background:rgba(217,119,6,.5)}`;
  document.head.appendChild(style);
}

export function useJsonSearch(preRef: React.RefObject<HTMLPreElement | null>) {
  const [isVisible, setIsVisible] = useState(false);
  const [query, setQuery] = useState('');
  const [caseSensitive, setCaseSensitive] = useState(false);
  const [matchCount, setMatchCount] = useState(0);
  const [currentIndex, setCurrentIndex] = useState(0);

  const searchInputRef = useRef<HTMLInputElement>(null);
  // Store all matched Ranges for scrollIntoView
  const rangesRef = useRef<Range[]>([]);

  // ---- Core: find matches in <pre> textContent, build Ranges, register CSS highlight ----
  const runSearch = useCallback(() => {
    // Clear old highlights
    CSS.highlights?.delete('json-search');
    CSS.highlights?.delete('json-search-current');
    rangesRef.current = [];

    const pre = preRef.current;
    if (!pre || !query) {
      setMatchCount(0);
      setCurrentIndex(0);
      return;
    }

    ensureHighlightStyle();

    const text = pre.textContent || '';
    const q = caseSensitive ? query : query.toLowerCase();
    const searchText = caseSensitive ? text : text.toLowerCase();

    // Find all matching [start, end) offsets
    const offsets: [number, number][] = [];
    let pos = 0;
    while (pos < searchText.length) {
      const idx = searchText.indexOf(q, pos);
      if (idx === -1) break;
      offsets.push([idx, idx + q.length]);
      pos = idx + 1;
    }

    if (offsets.length === 0) {
      setMatchCount(0);
      setCurrentIndex(0);
      return;
    }

    // Use TreeWalker to iterate text nodes and map offsets to DOM Ranges
    const walker = document.createTreeWalker(pre, NodeFilter.SHOW_TEXT);
    let charIndex = 0;
    let offsetIdx = 0;
    const ranges: Range[] = [];
    // Each Range may span multiple text nodes; track partial state with pendingRange
    let pendingRange: Range | null = null;
    let pendingEnd = 0;

    let node: Text | null;
    while ((node = walker.nextNode() as Text | null) && offsetIdx < offsets.length) {
      const nodeLen = node.textContent?.length || 0;
      const nodeStart = charIndex;
      const nodeEnd = charIndex + nodeLen;

      // Process matches that start or are in progress within this node's range
      while (offsetIdx < offsets.length) {
        const [mStart, mEnd] = offsets[offsetIdx];

        if (mStart >= nodeEnd) break; // this match is in a later node

        if (!pendingRange) {
          // Match starts in this node
          if (mStart >= nodeStart) {
            pendingRange = document.createRange();
            pendingRange.setStart(node, mStart - nodeStart);
            pendingEnd = mEnd;
          } else {
            break; // should never reach here
          }
        }

        // Match ends in this node
        if (pendingEnd <= nodeEnd) {
          pendingRange!.setEnd(node, pendingEnd - nodeStart);
          ranges.push(pendingRange!);
          pendingRange = null;
          offsetIdx++;
        } else {
          // Match continues into the next node
          break;
        }
      }

      charIndex = nodeEnd;
    }

    rangesRef.current = ranges;
    setMatchCount(ranges.length);
    setCurrentIndex(prev => ranges.length > 0 ? Math.min(prev, ranges.length - 1) : 0);

    // Register CSS highlight
    if (CSS.highlights && ranges.length > 0) {
      CSS.highlights.set('json-search', new Highlight(...ranges));
    }
  }, [preRef, query, caseSensitive]);

  // Helper to scroll element to center on target rect
  const scrollToRect = useCallback((container: Element, targetRect: DOMRect) => {
    const cRect = container.getBoundingClientRect();
    if (targetRect.top < cRect.top || targetRect.bottom > cRect.bottom) {
      container.scrollTop += targetRect.top - cRect.top - cRect.height / 2 + targetRect.height / 2;
    }
  }, []);

  // ---- Update current match highlight + scroll ----
  const updateCurrentHighlight = useCallback((index: number) => {
    CSS.highlights?.delete('json-search-current');
    const range = rangesRef.current[index];
    if (!range || !CSS.highlights) return;
    CSS.highlights.set('json-search-current', new Highlight(range));

    // Scroll to the current match
    const rect = range.getBoundingClientRect();
    const el = preRef.current?.parentElement;
    if (el) {
      scrollToRect(el, rect);
    }
  }, [preRef, scrollToRect]);

  // Re-run search when query or caseSensitive changes
  useEffect(() => {
    queueMicrotask(() => runSearch());
  }, [runSearch]);

  // Update current match highlight when currentIndex changes
  useEffect(() => {
    updateCurrentHighlight(currentIndex);
  }, [currentIndex, matchCount, updateCurrentHighlight]);

  // MutationObserver: re-run search when CollapsibleEntry collapses or expands
  useEffect(() => {
    const pre = preRef.current;
    if (!pre || !query) return;
    const observer = new MutationObserver(() => runSearch());
    observer.observe(pre, { childList: true, subtree: true, characterData: true });
    return () => observer.disconnect();
  }, [preRef, query, runSearch]);

  // Clean up when search is closed or component unmounts
  useEffect(() => {
    if (!isVisible) {
      CSS.highlights?.delete('json-search');
      CSS.highlights?.delete('json-search-current');
      rangesRef.current = [];
    }
  }, [isVisible]);

  useEffect(() => {
    return () => {
      CSS.highlights?.delete('json-search');
      CSS.highlights?.delete('json-search-current');
    };
  }, []);

  // ---- Navigation ----
  const goToNext = useCallback(() => {
    if (matchCount === 0) return;
    setCurrentIndex(prev => (prev + 1) % matchCount);
  }, [matchCount]);

  const goToPrev = useCallback(() => {
    if (matchCount === 0) return;
    setCurrentIndex(prev => (prev - 1 + matchCount) % matchCount);
  }, [matchCount]);

  const open = useCallback(() => {
    setIsVisible(true);
    setTimeout(() => searchInputRef.current?.focus(), 0);
  }, []);

  const close = useCallback(() => {
    setIsVisible(false);
    setQuery('');
    setCurrentIndex(0);
  }, []);

  const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
    if (e.key === 'Enter') {
      e.preventDefault();
      if (e.shiftKey) goToPrev(); else goToNext();
    }
    if (e.key === 'Escape') {
      e.preventDefault();
      close();
    }
  }, [goToNext, goToPrev, close]);

  return {
    isVisible,
    query, setQuery,
    caseSensitive, setCaseSensitive,
    matchCount, currentIndex,
    searchInputRef,
    open, close,
    goToNext, goToPrev,
    handleKeyDown,
  };
}

/** Search bar UI, used together with useJsonSearch */
export function JsonSearchBar({ search }: { search: ReturnType<typeof useJsonSearch> }) {
  if (!search.isVisible) return null;
  return React.createElement('div', { className: 'flex-shrink-0 flex items-center gap-2 px-3 py-2 bg-secondary border-b border-border' },
    React.createElement('input', {
      ref: search.searchInputRef,
      type: 'text',
      value: search.query,
      onChange: (e: React.ChangeEvent<HTMLInputElement>) => search.setQuery(e.target.value),
      onKeyDown: search.handleKeyDown,
      placeholder: i18n.t('codeViewer.searchPlaceholder'),
      className: 'flex-1 max-w-xs px-2 py-1 text-sm border border-border rounded bg-card text-foreground focus:outline-none focus:ring-2 focus:ring-ring',
    }),
    React.createElement('button', {
      onClick: () => search.setCaseSensitive((v: boolean) => !v),
      className: `px-2 py-1 text-xs font-mono rounded border transition-colors ${search.caseSensitive ? 'bg-brand text-white border-brand' : 'border-border text-muted-foreground hover:bg-accent'}`,
      title: i18n.t('codeViewer.caseSensitive'),
    }, 'Aa'),
    React.createElement('span', { className: 'text-xs text-muted-foreground' },
      search.matchCount > 0 ? `${search.currentIndex + 1}/${search.matchCount}` : i18n.t('common.noMatch'),
    ),
    React.createElement('button', {
      onClick: search.goToPrev, disabled: search.matchCount === 0,
      className: 'p-1 rounded hover:bg-accent disabled:opacity-50', title: i18n.t('common.previous'),
    }, React.createElement('svg', { className: 'w-4 h-4', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' },
      React.createElement('path', { strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: 2, d: 'M5 15l7-7 7 7' }),
    )),
    React.createElement('button', {
      onClick: search.goToNext, disabled: search.matchCount === 0,
      className: 'p-1 rounded hover:bg-accent disabled:opacity-50', title: i18n.t('common.next'),
    }, React.createElement('svg', { className: 'w-4 h-4', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' },
      React.createElement('path', { strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: 2, d: 'M19 9l-7 7-7-7' }),
    )),
    React.createElement('button', {
      onClick: search.close,
      className: 'p-1 rounded hover:bg-accent', title: i18n.t('common.close'),
    }, React.createElement('svg', { className: 'w-4 h-4', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' },
      React.createElement('path', { strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: 2, d: 'M6 18L18 6M6 6l12 12' }),
    )),
  );
}
