import { useKeyboard, useRenderer, useTerminalDimensions } from '@opentui/react';
import { useCallback, useEffect, useMemo, useReducer, useRef } from 'react';
import packageJson from '../../package.json';
import { closeDb } from '../db';
import { checkForUpdate } from '../utils/update-check';
import { AddFeedOverlay } from './components/AddFeedOverlay';
import { ArticleList } from './components/ArticleList';
import { ArticleReader } from './components/ArticleReader';
import { ConfirmDialog } from './components/ConfirmDialog';
import { EditFeedOverlay } from './components/EditFeedOverlay';
import { FeedList } from './components/FeedList';
import { Header } from './components/Header';
import { HelpOverlay } from './components/HelpOverlay';
import { SearchBar } from './components/SearchBar';
import { StatusBar } from './components/StatusBar';
import { UpdateOverlay } from './components/UpdateOverlay';
import { useAutoFetch } from './hooks/useAutoFetch';
import { useDataLoader } from './hooks/useDataLoader';
import { computePanelWidths } from './layout';
import {
  createTuiLifecycle,
  isTuiShutdownError,
  type TuiLifecycle,
  TuiLifecycleContext,
  useTuiLifecycle,
} from './lifecycle';
import { AppDispatchContext, AppStateContext } from './state/context';
import { appReducer } from './state/reducer';
import type { LayoutMode, Panel } from './state/types';
import { initialState } from './state/types';
import { colors } from './theme';

function getLayoutMode(width: number): LayoutMode {
  if (width >= 120) return 'wide';
  if (width >= 80) return 'medium';
  return 'narrow';
}

const RESIZE_STEP = 2;

type DragTarget = 'feed' | 'article' | null;

function MainContent({
  layoutMode,
  activePanel,
  feedWidth,
  articleWidth,
  onDrag,
  onPanelClick,
}: {
  layoutMode: LayoutMode;
  activePanel: Panel;
  feedWidth: number;
  articleWidth: number;
  onDrag: (target: DragTarget, x: number) => void;
  onPanelClick: (panel: Panel) => void;
}) {
  const dragTargetRef = useRef<DragTarget>(null);
  const GRAB_TOLERANCE = 3;

  const handleMouseDown = useCallback(
    (e: { x: number }) => {
      const feedBorder = feedWidth;
      const articleBorder = feedWidth + articleWidth;
      const distFeed = Math.abs(e.x - feedBorder);
      const distArticle = layoutMode === 'wide' ? Math.abs(e.x - articleBorder) : Infinity;
      if (distFeed <= GRAB_TOLERANCE && distFeed <= distArticle) {
        dragTargetRef.current = 'feed';
      } else if (distArticle <= GRAB_TOLERANCE) {
        dragTargetRef.current = 'article';
      } else {
        dragTargetRef.current = null;
        // Click-to-focus: determine which pane was clicked
        if (layoutMode === 'wide') {
          if (e.x < feedBorder) onPanelClick('feeds');
          else if (e.x < articleBorder) onPanelClick('articles');
          else onPanelClick('reader');
        } else if (layoutMode === 'medium') {
          if (e.x < feedBorder) onPanelClick('feeds');
          else onPanelClick('articles');
        }
      }
    },
    [feedWidth, articleWidth, layoutMode, onPanelClick],
  );

  const handleMouseDrag = useCallback(
    (e: { x: number }) => {
      if (dragTargetRef.current) {
        onDrag(dragTargetRef.current, e.x);
      }
    },
    [onDrag],
  );

  const handleMouseDragEnd = useCallback(() => {
    dragTargetRef.current = null;
  }, []);

  // Wide: 3 columns (feeds | articles | reader)
  if (layoutMode === 'wide') {
    return (
      // biome-ignore lint/a11y/noStaticElementInteractions: OpenTUI boxes do not expose ARIA roles; this terminal container handles pane focus and resize.
      <box
        flexDirection="row"
        flexGrow={1}
        onMouseDown={handleMouseDown}
        onMouseDrag={handleMouseDrag}
        onMouseDragEnd={handleMouseDragEnd}
      >
        <box width={feedWidth}>
          <FeedList />
        </box>
        <box width={articleWidth}>
          <ArticleList />
        </box>
        <box flexGrow={1}>
          <ArticleReader />
        </box>
      </box>
    );
  }

  // Medium: 2 columns (feeds | articles) or full-width reader
  if (layoutMode === 'medium') {
    if (activePanel === 'reader') {
      return (
        <box flexGrow={1}>
          <ArticleReader />
        </box>
      );
    }
    return (
      // biome-ignore lint/a11y/noStaticElementInteractions: OpenTUI boxes do not expose ARIA roles; this terminal container handles pane focus and resize.
      <box
        flexDirection="row"
        flexGrow={1}
        onMouseDown={handleMouseDown}
        onMouseDrag={handleMouseDrag}
        onMouseDragEnd={handleMouseDragEnd}
      >
        <box width={feedWidth}>
          <FeedList />
        </box>
        <box flexGrow={1}>
          <ArticleList />
        </box>
      </box>
    );
  }

  // Narrow: single panel at a time
  if (activePanel === 'feeds') {
    return (
      <box flexGrow={1}>
        <FeedList />
      </box>
    );
  }
  if (activePanel === 'reader') {
    return (
      <box flexGrow={1}>
        <ArticleReader />
      </box>
    );
  }
  return (
    <box flexGrow={1}>
      <ArticleList />
    </box>
  );
}

function AppInner() {
  const { width } = useTerminalDimensions();
  const lifecycle = useTuiLifecycle();
  const [state, dispatch] = useReducer(appReducer, initialState);

  // Update layout mode on resize
  useEffect(() => {
    dispatch({ type: 'SET_LAYOUT_MODE', mode: getLayoutMode(width) });
  }, [width]);

  // Load data from DB
  useDataLoader(state, dispatch);

  // Auto-fetch on open if the latest fetch is stale
  useAutoFetch(state, dispatch);

  // Check for a newer version on launch (skipped under tests / via env var)
  useEffect(() => {
    if (process.env.RSS_SKIP_UPDATE_CHECK === '1' || process.env.NODE_ENV === 'test') return;
    let cancelled = false;
    void lifecycle
      .run((signal) =>
        checkForUpdate({
          currentVersion: packageJson.version,
          packageName: packageJson.name,
          signal,
        }),
      )
      .then((result) => {
        if (cancelled || !lifecycle.isAcceptingWork() || !result.shouldPrompt || !result.latestVersion) return;
        dispatch({
          type: 'SET_UPDATE_INFO',
          info: {
            packageName: packageJson.name,
            currentVersion: result.currentVersion,
            latestVersion: result.latestVersion,
          },
        });
      })
      .catch((error) => {
        if (isTuiShutdownError(error)) return;
        // Network failures are non-fatal — the user just won't be prompted.
      });
    return () => {
      cancelled = true;
    };
  }, [lifecycle]);

  const panelWidths = computePanelWidths(width, state.layoutMode, state.feedPanelWidth, state.articlePanelWidth);
  const { feedWidth, articleWidth, minFeedWidth, maxFeedWidth, minArticleWidth, maxArticleWidth } = panelWidths;
  const contextState = useMemo(
    () => ({ ...state, feedPanelWidth: feedWidth, articlePanelWidth: articleWidth }),
    [state, feedWidth, articleWidth],
  );

  // Panel navigation order
  const panelOrder = useMemo<Panel[]>(
    () => (state.layoutMode === 'wide' ? ['feeds', 'articles', 'reader'] : ['feeds', 'articles']),
    [state.layoutMode],
  );

  const nextPanel = useCallback(() => {
    if (state.overlay !== 'none') return;
    const idx = panelOrder.indexOf(state.activePanel);
    const next = panelOrder[(idx + 1) % panelOrder.length] as Panel;
    dispatch({ type: 'SET_PANEL', panel: next });
  }, [state.activePanel, state.overlay, panelOrder]);

  const prevPanel = useCallback(() => {
    if (state.overlay !== 'none') return;
    const idx = panelOrder.indexOf(state.activePanel);
    const prev = panelOrder[(idx - 1 + panelOrder.length) % panelOrder.length] as Panel;
    dispatch({ type: 'SET_PANEL', panel: prev });
  }, [state.activePanel, state.overlay, panelOrder]);

  const handlePanelClick = useCallback(
    (panel: Panel) => {
      if (state.overlay !== 'none') return;
      if (panel !== state.activePanel) {
        dispatch({ type: 'SET_PANEL', panel });
      }
    },
    [state.activePanel, state.overlay],
  );

  // Unified drag handler for pane resizing
  const handlePaneDrag = useCallback(
    (target: DragTarget, x: number) => {
      if (target === 'feed') {
        const newWidth = Math.max(minFeedWidth, Math.min(maxFeedWidth, x));
        dispatch({ type: 'SET_FEED_PANEL_WIDTH', width: newWidth });
      } else if (target === 'article') {
        const newWidth = Math.max(minArticleWidth, Math.min(maxArticleWidth, x - feedWidth));
        dispatch({ type: 'SET_ARTICLE_PANEL_WIDTH', width: newWidth });
      }
    },
    [minFeedWidth, maxFeedWidth, feedWidth, minArticleWidth, maxArticleWidth],
  );

  // Global keyboard handler
  useKeyboard((key) => {
    // Overlays consume all input
    if (state.overlay !== 'none') return;

    if (key.name === 'q' && !key.ctrl) {
      dispatch({
        type: 'SET_CONFIRM',
        action: {
          message: 'Quit rss-ai?',
          onConfirm: () => lifecycle.shutdown(),
        },
      });
      return;
    }

    if (key.name === 'tab' && !key.shift) {
      nextPanel();
      return;
    }

    if (key.name === 'tab' && key.shift) {
      prevPanel();
      return;
    }

    // ? -> help overlay
    if (key.name === '?' || (key.shift && key.name === '/')) {
      dispatch({ type: 'SET_OVERLAY', overlay: 'help' });
      return;
    }

    // / -> search
    if (key.name === '/' && !key.shift) {
      dispatch({ type: 'SET_OVERLAY', overlay: 'search' });
      return;
    }

    // [ -> shrink sidebar
    if (key.name === '[') {
      const newWidth = Math.max(minFeedWidth, feedWidth - RESIZE_STEP);
      dispatch({ type: 'SET_FEED_PANEL_WIDTH', width: newWidth });
      return;
    }

    // ] -> grow sidebar
    if (key.name === ']') {
      const newWidth = Math.min(maxFeedWidth, feedWidth + RESIZE_STEP);
      dispatch({ type: 'SET_FEED_PANEL_WIDTH', width: newWidth });
      return;
    }

    // Layout mode switching
    if (key.name === '1') {
      dispatch({ type: 'SET_LAYOUT_MODE', mode: 'wide' });
      return;
    }
    if (key.name === '2') {
      dispatch({ type: 'SET_LAYOUT_MODE', mode: 'medium' });
      return;
    }
    if (key.name === '3') {
      dispatch({ type: 'SET_LAYOUT_MODE', mode: 'narrow' });
      return;
    }
  });

  return (
    <AppStateContext.Provider value={contextState}>
      <AppDispatchContext.Provider value={dispatch}>
        <box flexDirection="column" width="100%" height="100%" backgroundColor={colors.bg}>
          <Header />

          <MainContent
            layoutMode={state.layoutMode}
            activePanel={state.activePanel}
            feedWidth={feedWidth}
            articleWidth={articleWidth}
            onDrag={handlePaneDrag}
            onPanelClick={handlePanelClick}
          />

          <StatusBar />

          {/* Overlays */}
          {state.overlay === 'help' && <HelpOverlay />}
          {state.overlay === 'search' && <SearchBar />}
          {state.overlay === 'addFeed' && <AddFeedOverlay />}
          {state.overlay === 'editFeed' && <EditFeedOverlay key={state.editingFeed?.id} />}
          {state.overlay === 'confirm' && <ConfirmDialog />}
          {state.overlay === 'update' && <UpdateOverlay />}
        </box>
      </AppDispatchContext.Provider>
    </AppStateContext.Provider>
  );
}

export function App({ lifecycle: suppliedLifecycle }: { lifecycle?: TuiLifecycle } = {}) {
  const renderer = useRenderer();
  const fallbackLifecycleRef = useRef<TuiLifecycle | null>(null);
  if (!fallbackLifecycleRef.current) {
    fallbackLifecycleRef.current = createTuiLifecycle({
      destroyRenderer: () => renderer.destroy(),
      closeDatabase: closeDb,
    });
  }

  return (
    <TuiLifecycleContext.Provider value={suppliedLifecycle ?? fallbackLifecycleRef.current}>
      <AppInner />
    </TuiLifecycleContext.Provider>
  );
}
