import type { MouseEvent } from '@opentui/core';
import { useKeyboard, useTerminalDimensions } from '@opentui/react';
import { useCallback, useMemo, useRef } from 'react';
import { useListCursor, useScrollEchoGuard } from '../hooks/useListCursor';
import { useTuiActions } from '../hooks/useTuiActions';
import {
  buildFeedListItems,
  type FeedListItem,
  getFeedScrollOffset,
  getFeedVisibleRows,
  getNavigableFeedIndices,
  getVisibleFeedItems,
  truncateFeedLabel,
} from '../models/feed-list';
import { useAppDispatch, useAppState } from '../state/context';
import { terminalCellWidth, truncateTerminalText } from '../text';
import { colors, getFeedColor, icons } from '../theme';

export function FeedList() {
  const state = useAppState();
  const dispatch = useAppDispatch();
  const { deleteFeed, fetchAll, fetchOneFeed, markAllReadForFeed } = useTuiActions();
  const { width: termWidth, height: termHeight } = useTerminalDimensions();

  const feedW = state.feedPanelWidth ?? Math.floor(termWidth * 0.2);
  const contentWidth = Math.max(0, feedW - 4);

  const items = useMemo(
    () => buildFeedListItems(state.categories, state.totalUnread, state.totalStarred),
    [state.categories, state.totalUnread, state.totalStarred],
  );
  const categoryByName = useMemo(() => {
    const categories = new Map<string, (typeof state.categories)[number]>();
    for (const category of state.categories) {
      if (!categories.has(category.name)) categories.set(category.name, category);
    }
    return categories;
  }, [state.categories]);
  const navigableIndices = useMemo(() => getNavigableFeedIndices(items), [items]);
  const currentNavIdx = useMemo(
    () => navigableIndices.indexOf(state.feedListIndex),
    [navigableIndices, state.feedListIndex],
  );
  const navIdxRef = useRef(currentNavIdx);
  navIdxRef.current = currentNavIdx;
  const { armEcho, shouldIgnore: shouldIgnoreEcho } = useScrollEchoGuard();

  useListCursor({
    isActive: state.activePanel === 'feeds' && state.overlay === 'none',
    length: navigableIndices.length,
    currentIndex: currentNavIdx,
    setIndex: useCallback(
      (navIdx: number) => dispatch({ type: 'SET_FEED_LIST_INDEX', index: navigableIndices[navIdx] ?? 0 }),
      [dispatch, navigableIndices],
    ),
    shouldIgnore: shouldIgnoreEcho,
  });

  const handleSelect = useCallback(
    (item: FeedListItem) => {
      if (item.type === 'categoryHeader' && item.categoryName) {
        dispatch({
          type: 'TOGGLE_CATEGORY',
          categoryName: item.categoryName,
        });
      } else if (item.feedType) {
        dispatch({
          type: 'SELECT_FEED',
          feedId: item.feedId ?? null,
          feedType: item.feedType,
        });
        dispatch({ type: 'SET_PANEL', panel: 'articles' });
      }
    },
    [dispatch],
  );

  const availableRows = getFeedVisibleRows(termHeight);
  const showScrollIndicators = availableRows >= 3;
  const visibleRows = showScrollIndicators ? availableRows - 2 : availableRows;
  const scrollOffset = useMemo(
    () => getFeedScrollOffset(state.feedListIndex, visibleRows, items.length),
    [state.feedListIndex, visibleRows, items.length],
  );
  const visibleItems = useMemo(
    () => getVisibleFeedItems(items, scrollOffset, visibleRows),
    [items, scrollOffset, visibleRows],
  );

  useKeyboard((key) => {
    if (state.activePanel !== 'feeds' || state.overlay !== 'none') return;

    if (key.name === 'enter' || key.name === 'return') {
      const item = items[state.feedListIndex];
      if (item) handleSelect(item);
      return;
    }

    if (key.name === 'a') {
      dispatch({ type: 'SET_OVERLAY', overlay: 'addFeed' });
      return;
    }

    if (key.name === 'd') {
      const item = items[state.feedListIndex];
      if (item?.type === 'feed' && item.feedId) {
        const feedId = item.feedId;
        dispatch({
          type: 'SET_CONFIRM',
          action: {
            message: `Delete feed "${item.label}"?`,
            onConfirm: async () => {
              await deleteFeed(feedId);
            },
          },
        });
      }
      return;
    }

    if (key.name === 'r' && key.shift) {
      const item = items[state.feedListIndex];
      if (!item || item.type === 'separator' || item.type === 'categoryHeader') return;
      if (item.type === 'virtual' && item.feedType === 'starred') return;

      void markAllReadForFeed(item.type === 'feed' && item.feedId ? item.feedId : null);
      return;
    }

    if (key.name === 'f' && !key.shift) {
      const item = items[state.feedListIndex];
      if (item?.type !== 'feed' || !item.feedId) return;

      const feed = state.feeds.find((stateFeed) => stateFeed.id === item.feedId);
      if (!feed) return;

      void fetchOneFeed(feed);
      return;
    }

    if (key.name === 'f' && key.shift) {
      void fetchAll();
      return;
    }

    if (key.name === 'e') {
      const item = items[state.feedListIndex];
      if (item?.type === 'feed' && item.feedId) {
        const feed = state.feeds.find((stateFeed) => stateFeed.id === item.feedId);
        if (feed) {
          dispatch({ type: 'SET_EDITING_FEED', feed });
        }
      }
      return;
    }

    if (key.name === 'l' || key.name === 'right') {
      dispatch({ type: 'SET_PANEL', panel: 'articles' });
      return;
    }
  });

  const showScrollUp = showScrollIndicators && scrollOffset > 0;
  const showScrollDown = showScrollIndicators && scrollOffset + visibleRows < items.length;

  const handleMouseScroll = useCallback(
    (event: MouseEvent) => {
      if (state.activePanel !== 'feeds' || state.overlay !== 'none') return;
      event.stopPropagation();
      const dir = event.scroll?.direction;
      if (dir === 'down' || dir === 'up') {
        armEcho(dir);
      }
      const cur = navIdxRef.current;
      let newNavIdx: number | undefined;
      if (dir === 'down') {
        newNavIdx = Math.min(cur + 1, navigableIndices.length - 1);
      } else if (dir === 'up') {
        newNavIdx = Math.max(cur - 1, 0);
      }
      if (newNavIdx === undefined) return;
      const newItemIdx = navigableIndices[newNavIdx] ?? 0;
      navIdxRef.current = newNavIdx;
      dispatch({ type: 'SET_FEED_LIST_INDEX', index: newItemIdx });
      const item = items[newItemIdx];
      if (item && item.type !== 'separator' && item.type !== 'categoryHeader' && item.feedType) {
        dispatch({
          type: 'SELECT_FEED',
          feedId: item.feedId ?? null,
          feedType: item.feedType,
        });
      }
    },
    [armEcho, navigableIndices, items, dispatch, state.activePanel, state.overlay],
  );

  return (
    <box
      flexDirection="column"
      border
      borderStyle="rounded"
      borderColor={state.activePanel === 'feeds' ? colors.borderActive : colors.border}
      title=" Feeds "
      titleAlignment="left"
      backgroundColor={colors.bgPanel}
      height="100%"
      onMouseScroll={handleMouseScroll}
    >
      {showScrollUp ? (
        <box height={1} paddingLeft={1}>
          <text fg={colors.textMuted}>{'▲ more'}</text>
        </box>
      ) : null}
      <box flexDirection="column" flexGrow={1}>
        {visibleItems.map(({ item, index }) => {
          const isSelected = index === state.feedListIndex;

          if (item.type === 'separator') {
            return (
              <box key={item.id} height={1} paddingLeft={1}>
                <text fg={colors.textMuted}>{icons.separator.repeat(Math.max(0, contentWidth))}</text>
              </box>
            );
          }

          if (item.type === 'virtual') {
            const countStr = item.unreadCount ? ` ${item.unreadCount}` : '';
            const visibleCount = truncateTerminalText(countStr, contentWidth);
            const label = truncateFeedLabel(item.label, visibleCount, 0, contentWidth);
            return (
              <box
                key={item.id}
                height={1}
                backgroundColor={isSelected ? colors.bgHighlight : undefined}
                paddingLeft={1}
                paddingRight={1}
              >
                <text>
                  <span fg={isSelected ? colors.accentBlue : colors.textPrimary}>{label}</span>
                  {visibleCount ? <span fg={colors.textSecondary}>{visibleCount}</span> : null}
                </text>
              </box>
            );
          }

          if (item.type === 'categoryHeader') {
            const category = item.categoryName ? categoryByName.get(item.categoryName) : undefined;
            const icon = category?.collapsed ? icons.categoryClosed : icons.categoryOpen;
            const countStr = item.unreadCount ? ` (${item.unreadCount})` : '';
            const available = Math.max(0, contentWidth - terminalCellWidth(`${icon} `));
            const visibleCount = truncateTerminalText(countStr, available);
            const label = truncateFeedLabel(item.label, visibleCount, 2, contentWidth);
            return (
              <box
                key={item.id}
                height={1}
                backgroundColor={isSelected ? colors.bgHighlight : undefined}
                paddingLeft={1}
                paddingRight={1}
              >
                <text>
                  <span fg={colors.textSecondary}>{icon} </span>
                  <span fg={isSelected ? colors.accentBlue : colors.textPrimary}>{label}</span>
                  {visibleCount ? <span fg={colors.textSecondary}>{visibleCount}</span> : null}
                </text>
              </box>
            );
          }

          const hasUnread = (item.unreadCount ?? 0) > 0;
          const countStr = hasUnread ? ` ${icons.unread} ${item.unreadCount}` : ` ${icons.read}`;
          const available = Math.max(0, contentWidth - 2);
          const visibleCount = truncateTerminalText(countStr, available);
          const label = truncateFeedLabel(item.label, visibleCount, 2, contentWidth);
          const color = item.feedId ? getFeedColor(item.feedId) : colors.textPrimary;
          return (
            <box
              key={item.id}
              height={1}
              backgroundColor={isSelected ? colors.bgHighlight : undefined}
              paddingLeft={3}
              paddingRight={1}
            >
              <text>
                <span fg={color}>{label}</span>
                {hasUnread ? (
                  <span fg={colors.accentGreen}>{visibleCount}</span>
                ) : (
                  <span fg={colors.textMuted}>{visibleCount}</span>
                )}
              </text>
            </box>
          );
        })}
      </box>
      {showScrollDown ? (
        <box height={1} paddingLeft={1}>
          <text fg={colors.textMuted}>{'▼ more'}</text>
        </box>
      ) : null}
    </box>
  );
}
