import type { PasteEvent } from '@opentui/core';
import { useKeyboard, useRenderer, useTerminalDimensions } from '@opentui/react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { addFeed } from '../../fetchers/feed';
import { sanitizeTerminalText } from '../../utils/terminal';
import { validateUrl } from '../../utils/validation';
import { getArticleLoadState, isSameArticleLoadState, refreshVisibleData } from '../hooks/useDataLoader';
import { computeOverlayRect } from '../layout';
import { isTuiShutdownError, useTuiLifecycle } from '../lifecycle';
import { useAppDispatch, useAppState } from '../state/context';
import { limitGraphemes, removeLastGrapheme, terminalCellWidth, truncateTerminalText } from '../text';
import { colors } from '../theme';

type Field = 'url' | 'category';
const MAX_URL_GRAPHEMES = 4_096;
const MAX_CATEGORY_GRAPHEMES = 256;

export interface AddFeedOverlayProps {
  add?: typeof addFeed;
  refresh?: typeof refreshVisibleData;
}

export function getInitialFetchWarning(
  status: Awaited<ReturnType<typeof addFeed>>['initialFetchStatus'],
  error?: string,
): string | null {
  switch (status) {
    case 'success':
    case 'not_modified':
      return null;
    case 'error':
    case 'skipped':
      return error ?? `initial fetch was ${status}`;
    case undefined:
      return 'initial fetch returned no outcome';
  }
}

export function AddFeedOverlay({ add = addFeed, refresh = refreshVisibleData }: AddFeedOverlayProps = {}) {
  const state = useAppState();
  const dispatch = useAppDispatch();
  const lifecycle = useTuiLifecycle();
  const renderer = useRenderer();
  const { width: terminalWidth, height: terminalHeight } = useTerminalDimensions();
  const [url, setUrl] = useState('');
  const [category, setCategory] = useState('');
  const [activeField, setActiveField] = useState<Field>('url');
  const [error, setError] = useState<string | null>(null);
  const [adding, setAdding] = useState(false);
  const [addedWithWarning, setAddedWithWarning] = useState(false);
  const addingRef = useRef(false);
  const addedWithWarningRef = useRef(false);
  const mountedRef = useRef(true);
  const operationRef = useRef(0);
  const stateRef = useRef(state);
  stateRef.current = state;
  const layout = computeOverlayRect(terminalWidth, terminalHeight, {
    widthRatio: 0.6,
    minWidth: 28,
    height: 12,
    leftRatio: 0.2,
    topRatio: 0.3,
  });
  const labelWidth = Math.min(12, layout.contentWidth);
  const rowGap = layout.contentHeight >= 5 ? 1 : 0;
  const valueWidth = Math.max(0, layout.contentWidth - labelWidth - 1);

  const appendText = useCallback(
    (rawText: string) => {
      const text = sanitizeTerminalText(rawText);
      if (!text) return;
      if (activeField === 'url') setUrl((value) => limitGraphemes(value + text, MAX_URL_GRAPHEMES));
      else setCategory((value) => limitGraphemes(value + text, MAX_CATEGORY_GRAPHEMES));
    },
    [activeField],
  );

  useEffect(() => {
    const handlePaste = (event: PasteEvent) => {
      appendText(new TextDecoder().decode(event.bytes));
      event.preventDefault();
    };
    renderer.keyInput.on('paste', handlePaste);
    return () => {
      renderer.keyInput.off('paste', handlePaste);
    };
  }, [renderer, appendText]);

  useEffect(() => {
    mountedRef.current = true;
    return () => {
      mountedRef.current = false;
      operationRef.current += 1;
    };
  }, []);

  useKeyboard((key) => {
    if (key.name === 'escape') {
      // Cancelling a network-backed add midway leaves the created feed
      // ambiguous. Keep this overlay as the owner until it completes.
      if (addingRef.current) return;
      operationRef.current += 1;
      dispatch({ type: 'SET_OVERLAY', overlay: 'none' });
      return;
    }

    // The feed already exists after a failed initial fetch. Only allow the
    // user to dismiss and retry it from the feed list, avoiding a duplicate add.
    if (addedWithWarningRef.current) return;

    if (key.name === 'tab') {
      setActiveField((f) => (f === 'url' ? 'category' : 'url'));
      return;
    }

    if ((key.name === 'enter' || key.name === 'return') && !adding) {
      if (!url.trim()) {
        setError('URL is required');
        return;
      }
      const urlResult = validateUrl(url.trim());
      if (!urlResult.valid) {
        setError(urlResult.error);
        return;
      }
      setAdding(true);
      addingRef.current = true;
      setError(null);
      const operation = operationRef.current + 1;
      operationRef.current = operation;
      const criteria = getArticleLoadState(stateRef.current);
      const isActive = () =>
        mountedRef.current &&
        operationRef.current === operation &&
        stateRef.current.overlay === 'addFeed' &&
        lifecycle.isAcceptingWork() &&
        isSameArticleLoadState(criteria, getArticleLoadState(stateRef.current));

      void lifecycle
        .run(async (signal) => {
          const result = await add(url.trim(), {
            category: category.trim() || undefined,
            fetchNow: true,
            signal,
          });
          if (!isActive()) return;
          try {
            await refresh(criteria, dispatch, { canDispatch: isActive });
          } catch (refreshError) {
            if (isActive()) {
              setError(
                sanitizeTerminalText(
                  `Feed was saved, but the list could not refresh: ${refreshError instanceof Error ? refreshError.message : String(refreshError)}. Press Esc, then fetch it from the feed list.`,
                ),
              );
              setAddedWithWarning(true);
              addedWithWarningRef.current = true;
              setAdding(false);
              addingRef.current = false;
            }
            return;
          }
          if (!isActive()) return;
          const initialFetchWarning = getInitialFetchWarning(result.initialFetchStatus, result.initialFetchError);
          if (initialFetchWarning) {
            setError(
              sanitizeTerminalText(
                `Feed was saved, but its first fetch failed: ${initialFetchWarning}. Press Esc, then retry from the feed list.`,
              ),
            );
            setAddedWithWarning(true);
            addedWithWarningRef.current = true;
            setAdding(false);
            addingRef.current = false;
            return;
          }
          if (isActive()) dispatch({ type: 'SET_OVERLAY', overlay: 'none' });
        })
        .catch((err) => {
          if (!isTuiShutdownError(err) && isActive()) {
            setError(sanitizeTerminalText(err instanceof Error ? err.message : String(err)));
            setAdding(false);
            addingRef.current = false;
          }
        });
      return;
    }

    if (key.name === 'backspace') {
      if (activeField === 'url') setUrl((value) => removeLastGrapheme(value));
      else setCategory((value) => removeLastGrapheme(value));
      return;
    }

    // Character input. Use key.sequence (not key.name) so shifted letters keep
    // their case — opentui lowercases key.name for them.
    if (!key.ctrl && !key.meta && key.name !== 'space') {
      appendText(key.sequence);
      return;
    }

    if (key.name === 'space') {
      if (activeField === 'category') setCategory((value) => limitGraphemes(`${value} `, MAX_CATEGORY_GRAPHEMES));
      return;
    }
  });

  return (
    <box
      position="absolute"
      left={layout.left}
      top={layout.top}
      width={layout.width}
      height={layout.height}
      border
      borderStyle="rounded"
      borderColor={colors.accentBlue}
      title={truncateTerminalText(' Add Feed ', Math.max(0, layout.width - 4))}
      titleAlignment="center"
      backgroundColor={colors.bg}
      padding={layout.padding}
      zIndex={100}
      flexDirection="column"
    >
      {layout.contentHeight >= 1 ? (
        <box flexDirection="row" marginBottom={rowGap}>
          <box width={labelWidth}>
            <text fg={colors.textSecondary}>{truncateTerminalText('URL:', labelWidth)}</text>
          </box>
          <text>
            <span fg={activeField === 'url' ? colors.textPrimary : colors.textSecondary}>
              {truncateTerminalText(sanitizeTerminalText(url) || '(enter feed URL)', valueWidth)}
            </span>
            {activeField === 'url' && terminalCellWidth(url || '(enter feed URL)') < valueWidth ? (
              <span fg={colors.accentBlue}>{'█'}</span>
            ) : null}
          </text>
        </box>
      ) : null}

      {layout.contentHeight >= 2 + rowGap ? (
        <box flexDirection="row" marginBottom={rowGap}>
          <box width={labelWidth}>
            <text fg={colors.textSecondary}>{truncateTerminalText('Category:', labelWidth)}</text>
          </box>
          <text>
            <span fg={activeField === 'category' ? colors.textPrimary : colors.textSecondary}>
              {truncateTerminalText(sanitizeTerminalText(category) || '(optional)', valueWidth)}
            </span>
            {activeField === 'category' && terminalCellWidth(category || '(optional)') < valueWidth ? (
              <span fg={colors.accentBlue}>{'█'}</span>
            ) : null}
          </text>
        </box>
      ) : null}

      {layout.contentHeight >= 3 + rowGap * 2 ? (
        error ? (
          <text fg={colors.accentRed}>{truncateTerminalText(sanitizeTerminalText(error), layout.contentWidth)}</text>
        ) : adding ? (
          <text fg={colors.accentGreen}>
            {truncateTerminalText('Validating and adding feed... Esc is disabled', layout.contentWidth)}
          </text>
        ) : addedWithWarning ? (
          <text fg={colors.textMuted}>{truncateTerminalText('Feed saved · Esc to close', layout.contentWidth)}</text>
        ) : (
          <text fg={colors.textMuted}>
            {truncateTerminalText('Tab to switch fields, Enter to add, Esc to cancel', layout.contentWidth)}
          </text>
        )
      ) : null}
    </box>
  );
}
