import type { MouseEvent } from '@opentui/core';
import { useKeyboard, useTerminalDimensions } from '@opentui/react';
import { useCallback, useMemo, useRef } from 'react';
import { sanitizeTerminalText } from '../../utils/terminal';
import { useListCursor, useScrollEchoGuard } from '../hooks/useListCursor';
import { useTuiActions } from '../hooks/useTuiActions';
import { cleanArticleDescription, formatArticleAge, getArticleWindow, truncateText } from '../models/article-list';
import { useAppDispatch, useAppState } from '../state/context';
import { terminalCellWidth, truncateTerminalText } from '../text';
import { colors, getFeedColor, icons } from '../theme';

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

  const feedW = state.feedPanelWidth ?? Math.floor(termWidth * 0.2);
  const articleW = state.articlePanelWidth ?? Math.floor(termWidth * 0.35);
  const contentWidth = useMemo(() => {
    if (state.layoutMode === 'wide') {
      return articleW - 4;
    }
    if (state.layoutMode === 'medium') {
      return termWidth - feedW - 1 - 4;
    }
    return termWidth - 4;
  }, [state.layoutMode, termWidth, feedW, articleW]);

  const safeWidth = Math.max(0, contentWidth);
  const currentArticle = state.articles[state.articleListIndex] ?? null;
  const articleIdxRef = useRef(state.articleListIndex);
  articleIdxRef.current = state.articleListIndex;
  const { armEcho, shouldIgnore: shouldIgnoreEcho } = useScrollEchoGuard();

  useListCursor({
    isActive: state.activePanel === 'articles' && state.overlay === 'none',
    length: state.articles.length,
    currentIndex: state.articleListIndex,
    setIndex: useCallback((index: number) => dispatch({ type: 'SET_ARTICLE_LIST_INDEX', index }), [dispatch]),
    shouldIgnore: shouldIgnoreEcho,
  });

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

    if ((key.name === 'enter' || key.name === 'return') && currentArticle) {
      openArticle(currentArticle);
      return;
    }

    if (key.name === 's' && currentArticle) {
      toggleArticleStar(currentArticle);
      return;
    }

    if (key.name === 'r' && !key.shift && currentArticle) {
      toggleArticleRead(currentArticle);
      return;
    }

    if (key.name === 'u') {
      dispatch({ type: 'TOGGLE_UNREAD_FILTER' });
      return;
    }

    if (key.name === 't') {
      dispatch({ type: 'CYCLE_SORT_ORDER' });
      return;
    }

    if (key.name === 'r' && key.shift) {
      if (state.selectedFeedType === 'starred') return;
      void markAllReadForFeed(state.selectedFeedId);
      return;
    }

    if (key.name === 'f' && !key.shift && state.selectedFeedId) {
      const feed = state.feeds.find((stateFeed) => stateFeed.id === state.selectedFeedId);
      if (feed) void fetchOneFeed(feed);
      return;
    }

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

    if (key.name === 'h' || key.name === 'left' || key.name === 'escape') {
      dispatch({ type: 'SET_PANEL', panel: 'feeds' });
      return;
    }

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

  const filterLabel = state.showUnreadOnly ? ' [unread]' : '';
  const sortLabel = ` [${state.sortOrder}]`;
  const articleWindow = useMemo(
    () => getArticleWindow(state.articles, state.articleListIndex, termHeight),
    [state.articles, state.articleListIndex, termHeight],
  );
  const visibleRows = articleWindow.rows;
  const showScrollUp = articleWindow.showScrollUp;
  const showScrollDown = articleWindow.showScrollDown;

  const handleMouseScroll = useCallback(
    (event: MouseEvent) => {
      if (state.activePanel !== 'articles' || state.overlay !== 'none') return;
      event.stopPropagation();
      if (state.articles.length === 0) return;
      const dir = event.scroll?.direction;
      if (dir === 'down' || dir === 'up') {
        armEcho(dir);
      }
      const cur = articleIdxRef.current;
      let newIdx: number | undefined;
      if (dir === 'down') {
        newIdx = Math.min(cur + 1, state.articles.length - 1);
      } else if (dir === 'up') {
        newIdx = Math.max(cur - 1, 0);
      }
      if (newIdx === undefined) return;
      articleIdxRef.current = newIdx;
      dispatch({ type: 'SET_ARTICLE_LIST_INDEX', index: newIdx });
      const article = state.articles[newIdx];
      if (article) {
        dispatch({ type: 'SET_SELECTED_ARTICLE', article });
      }
    },
    [armEcho, state.activePanel, state.overlay, state.articles, dispatch],
  );

  return (
    <box
      flexDirection="column"
      border
      borderStyle="rounded"
      borderColor={state.activePanel === 'articles' ? colors.borderActive : colors.border}
      title={` Articles${filterLabel}${sortLabel} `}
      titleAlignment="left"
      backgroundColor={colors.bgPanel}
      height="100%"
      onMouseScroll={handleMouseScroll}
    >
      {state.articles.length === 0 ? (
        <box padding={2}>
          <text fg={colors.textSecondary}>
            {state.loading ? 'Loading...' : state.searchQuery ? 'No matching articles' : 'No articles'}
          </text>
        </box>
      ) : (
        <box flexDirection="column" flexGrow={1}>
          {showScrollUp ? (
            <box height={1} paddingLeft={1}>
              <text fg={colors.textMuted}>{'▲ more'}</text>
            </box>
          ) : null}
          {visibleRows.map((row) => {
            if (row.type === 'header') {
              const label = truncateTerminalText(row.headerText ?? '', Math.max(0, safeWidth - 1));
              const lineLen = Math.max(0, safeWidth - terminalCellWidth(label) - 1);
              return (
                <box key={row.key} height={1} paddingLeft={1}>
                  <text>
                    <span fg={colors.textSecondary}>{label}</span>
                    <span fg={colors.textMuted}> {icons.separator.repeat(lineLen)}</span>
                  </text>
                </box>
              );
            }

            const article = row.article;
            if (!article) return null;

            const isSelected = row.articleIndex === state.articleListIndex;
            const readIcon = article.isRead ? icons.read : icons.unread;
            const age = formatArticleAge(article.publishedAt);
            const starStr = article.isStarred ? ` ${icons.starred}` : '';
            const suffix = truncateTerminalText(`${starStr}  ${age}`, Math.max(0, safeWidth - 2));
            const titleBudget = Math.max(0, safeWidth - 2 - terminalCellWidth(suffix));
            const title = truncateText(sanitizeTerminalText(article.title ?? 'Untitled'), titleBudget);

            const feedName = truncateTerminalText(
              sanitizeTerminalText(article.feed?.title ?? ''),
              Math.max(0, safeWidth - 2),
            );
            const desc = cleanArticleDescription(article.description);
            const descBudget = Math.max(0, safeWidth - 2 - terminalCellWidth(feedName) - 3);
            const descTrunc = truncateText(desc, descBudget);
            const feedColor = article.feed ? getFeedColor(article.feed.id) : colors.textSecondary;

            return (
              <box
                key={row.key}
                height={2}
                backgroundColor={isSelected ? colors.bgHighlight : undefined}
                paddingLeft={1}
                paddingRight={1}
              >
                <text>
                  <span fg={article.isRead ? colors.textMuted : colors.accentGreen}>{readIcon}</span>
                  <span fg={article.isRead ? colors.textSecondary : colors.textPrimary}> {title}</span>
                  {article.isStarred && suffix ? (
                    <span fg={colors.accentYellow}>{suffix.startsWith(starStr) ? starStr : ''}</span>
                  ) : null}
                  <span fg={colors.textMuted}>
                    {suffix.startsWith(starStr) ? suffix.slice(starStr.length) : suffix}
                  </span>
                  <br />
                  <span fg={feedColor}>
                    {'  '}
                    {feedName}
                  </span>
                  {descTrunc ? (
                    <span fg={colors.textMuted}>
                      {' · '}
                      {descTrunc}
                    </span>
                  ) : null}
                </text>
              </box>
            );
          })}
          {showScrollDown ? (
            <box height={1} paddingLeft={1}>
              <text fg={colors.textMuted}>{'▼ more'}</text>
            </box>
          ) : null}
        </box>
      )}
    </box>
  );
}
