import * as path from 'path';
import * as React from 'react';
import { useEffect, useRef, useState } from 'react';
import { useRegisterOverlay } from '../context/overlayContext.js';
import { generateFileSuggestions } from '../hooks/fileSuggestions.js';
import { useTerminalSize } from '../hooks/useTerminalSize.js';
import { Text } from '@anthropic/ink';
import { logEvent } from '../services/analytics/index.js';
import { getCwd } from '../utils/cwd.js';
import { openFileInExternalEditor } from '../utils/editor.js';
import { truncatePathMiddle, truncateToWidth } from '../utils/format.js';
import { highlightMatch } from '../utils/highlightMatch.js';
import { readFileInRange } from '../utils/readFileInRange.js';
import { FuzzyPicker, LoadingState } from '@anthropic/ink';

type Props = {
  onDone: () => void;
  onInsert: (text: string) => void;
};

const VISIBLE_RESULTS = 8;
const PREVIEW_LINES = 20;

/**
 * Quick Open dialog (ctrl+shift+p / cmd+shift+p).
 * Fuzzy file finder with a syntax-highlighted preview of the focused file.
 */
export function QuickOpenDialog({ onDone, onInsert }: Props): React.ReactNode {
  useRegisterOverlay('quick-open');
  const { columns, rows } = useTerminalSize();
  // Chrome (title + search + hints + pane border + gaps) eats ~14 rows.
  // Shrink the list on short terminals so the dialog doesn't clip.
  const visibleResults = Math.min(VISIBLE_RESULTS, Math.max(4, rows - 14));

  const [results, setResults] = useState<string[]>([]);
  const [query, setQuery] = useState('');
  const [focusedPath, setFocusedPath] = useState<string | undefined>(undefined);
  const [preview, setPreview] = useState<{
    path: string;
    content: string;
  } | null>(null);
  const queryGenRef = useRef(0);
  useEffect(() => () => void queryGenRef.current++, []);

  const previewOnRight = columns >= 120;
  // Side preview sits in a fixed-height row alongside the list (visibleCount
  // rows), so overflowing that height garbles the layout — cap to fit, minus
  // one for the path header line.
  const effectivePreviewLines = previewOnRight ? VISIBLE_RESULTS - 1 : PREVIEW_LINES;

  // A generation counter invalidates stale results if the user types faster
  // than the index can respond.
  const handleQueryChange = (q: string) => {
    setQuery(q);
    const gen = ++queryGenRef.current;
    if (!q.trim()) {
      // generateFileSuggestions('') returns raw readdir() of cwd (designed for
      // @-mentions). For Quick Open that's just noise — show the empty state.
      setResults([]);
      return;
    }
    void generateFileSuggestions(q, true).then(items => {
      if (gen !== queryGenRef.current) return;
      // Filter out directory entries — they come back with a trailing path.sep
      // from getTopLevelPaths() and would cause readFileInRange to throw EISDIR,
      // leaving the preview pane stuck on "Loading preview…".
      // Normalize separators to '/' so truncatePathMiddle (which uses
      // lastIndexOf('/')) can find the filename on Windows too.
      const paths = items
        .filter(i => i.id.startsWith('file-'))
        .map(i => i.displayText)
        .filter(p => !p.endsWith(path.sep))
        .map(p => p.split(path.sep).join('/'));
      setResults(paths);
    });
  };

  // Load a short preview of the focused file. Each navigation aborts the
  // previous read so holding ↓ doesn't pile up whole-file reads and so a
  // slow early read can't overwrite a faster later one. The stale preview
  // stays visible until the new one arrives — renderPreview overlays a dim
  // loading indicator rather than blanking the pane.
  useEffect(() => {
    if (!focusedPath) {
      // No results — clear so the empty-state renders instead of a stale
      // preview from a previous query.
      setPreview(null);
      return;
    }
    const controller = new AbortController();
    const absolute = path.resolve(getCwd(), focusedPath);
    void readFileInRange(absolute, 0, effectivePreviewLines, undefined, controller.signal)
      .then(r => {
        if (controller.signal.aborted) return;
        setPreview({ path: focusedPath, content: r.content });
      })
      .catch(() => {
        if (controller.signal.aborted) return;
        setPreview({ path: focusedPath, content: '(preview unavailable)' });
      });
    return () => controller.abort();
  }, [focusedPath, effectivePreviewLines]);

  const maxPathWidth = previewOnRight ? Math.max(20, Math.floor((columns - 10) * 0.4)) : Math.max(20, columns - 8);
  const previewWidth = previewOnRight ? Math.max(40, columns - maxPathWidth - 14) : columns - 6;

  const handleOpen = (p: string) => {
    const opened = openFileInExternalEditor(path.resolve(getCwd(), p));
    logEvent('tengu_quick_open_select', {
      result_count: results.length,
      opened_editor: opened,
    });
    onDone();
  };

  const handleInsert = (p: string, mention: boolean) => {
    onInsert(mention ? `@${p} ` : `${p} `);
    logEvent('tengu_quick_open_insert', {
      result_count: results.length,
      mention,
    });
    onDone();
  };

  return (
    <FuzzyPicker
      title="Quick Open"
      placeholder="Type to search files…"
      items={results}
      getKey={p => p}
      visibleCount={visibleResults}
      direction="up"
      previewPosition={previewOnRight ? 'right' : 'bottom'}
      onQueryChange={handleQueryChange}
      onFocus={p => setFocusedPath(p)}
      onSelect={handleOpen}
      onTab={{ action: 'mention', handler: p => handleInsert(p, true) }}
      onShiftTab={{
        action: 'insert path',
        handler: p => handleInsert(p, false),
      }}
      onCancel={onDone}
      emptyMessage={q => (q ? 'No matching files' : 'Start typing to search…')}
      selectAction="open in editor"
      renderItem={(p, isFocused) => (
        <Text color={isFocused ? 'suggestion' : undefined}>{truncatePathMiddle(p, maxPathWidth)}</Text>
      )}
      renderPreview={p =>
        preview ? (
          <>
            <Text dimColor>
              {truncatePathMiddle(p, previewWidth)}
              {preview.path !== p ? ' · loading…' : ''}
            </Text>
            {preview.content.split('\n').map((line, i) => (
              <Text key={i}>{highlightMatch(truncateToWidth(line, previewWidth), query)}</Text>
            ))}
          </>
        ) : (
          <LoadingState message="Loading preview…" dimColor />
        )
      }
    />
  );
}
