import { useState, useCallback, useEffect } from 'preact/hooks';
import './Recom.css';
import { route } from 'preact-router';
import { PipelineProgress } from '../components/PipelineProgress.js';
import type { StageGroup } from '../components/PipelineProgress.js';
import { RecommendationCard } from '../components/RecommendationCard.js';
import { Sidebar } from '../components/Sidebar.js';
import { NewRecommendationModal } from '../components/NewRecommendationModal.js';
import { useRecommendationsContext } from '../context/RecommendationsContext.js';
import { useSSE } from '../hooks/useSSE.js';
import { getRecommendation } from '../api/client.js';
import type { Recommendation, SSEEvent, StageMap, StageStatus } from '../types/index.js';

interface RecomProps {
  id: string;
  path?: string;
}

function buildDefaultStages(rec: Recommendation | null): StageMap {
  const map: StageMap = {
    interpreter: 'pending',
    retrieval: 'pending',
    validator: 'pending',
    ranking: 'pending',
    curator: 'pending',
  };
  if (rec?.self_expansive && rec.expansive_passes > 0) {
    for (let i = 0; i < rec.expansive_passes; i++) {
      const p = i + 2;
      map[`pass${p}:retrieval`] = 'pending';
      if (rec.use_validator) map[`pass${p}:validator`] = 'pending';
      map[`pass${p}:ranking`] = 'pending';
      map[`pass${p}:curator`] = 'pending';
    }
  }
  return map;
}

function buildStageGroups(rec: Recommendation | null): StageGroup[] {
  const baseStages = [
    { key: 'interpreter', label: 'Interpreting Preferences' },
    { key: 'retrieval', label: 'Generating Candidates' },
    { key: 'validator', label: 'Validating Candidates' },
    { key: 'ranking', label: 'Ranking Candidates' },
    { key: 'curator', label: 'Curating Results' },
  ];
  const groups: StageGroup[] = [{ label: '', stages: baseStages }];
  if (rec?.self_expansive && rec.expansive_passes > 0) {
    for (let i = 0; i < rec.expansive_passes; i++) {
      const p = i + 2;
      groups.push({
        label: `Pass ${p}`,
        stages: [
          { key: `pass${p}:retrieval`, label: 'Generating Candidates' },
          ...(rec.use_validator ? [{ key: `pass${p}:validator`, label: 'Validating Candidates' }] : []),
          { key: `pass${p}:ranking`, label: 'Ranking Candidates' },
          { key: `pass${p}:curator`, label: 'Curating Results' },
        ],
      });
    }
  }
  return groups;
}

export function Recom({ id }: RecomProps) {
  const { list, feedback, submitFeedback, rerank, updateStatus, updateTitle, refreshList, createNew, deleteRec } = useRecommendationsContext();

  const [rec, setRec] = useState<Recommendation | null>(null);
  const [stages, setStages] = useState<StageMap>(buildDefaultStages(null));
  // sseKey drives the SSE connection. null = inactive; a number = active.
  // Using a timestamp nonce ensures the URL is always unique on (re)connect,
  // so useSSE's useEffect always re-runs even if the base path hasn't changed.
  const [sseKey, setSseKey] = useState<number | null>(null);
  const [showModal, setShowModal] = useState(false);
  const [infoExpanded, setInfoExpanded] = useState(true);

  // Derive the actual URL from the key; query param is ignored by the server
  // but makes the string unique so React/Preact state always treats it as changed.
  const sseUrl = sseKey !== null ? `/api/recommendations/${id}/stream?_k=${sseKey}` : null;

  useEffect(() => {
    setRec(null);
    setStages(buildDefaultStages(null));
    setSseKey(null);
    getRecommendation(id)
      .then((data) => {
        setRec(data);
        if (data.status === 'running' || data.status === 'pending') {
          setStages(buildDefaultStages(data));
          setSseKey(Date.now());
        }
      })
      .catch(() => route('/'));
  }, [id]);

  const handleSSEEvent = useCallback(
    (event: SSEEvent) => {
      if (event.stage !== 'complete') {
        const stageKey = event.stage as string;
        setStages((prev) => {
          if (!(stageKey in prev)) return prev;
          const eventData = event.data as { skipped?: boolean } | undefined;
          let newStatus: StageStatus;
          if (event.status === 'start') {
            newStatus = 'running';
          } else if (event.status === 'done') {
            newStatus = eventData?.skipped ? 'skipped' : 'done';
          } else {
            newStatus = 'error';
          }
          return { ...prev, [stageKey]: newStatus };
        });
      }

      if (event.stage === 'complete' && event.status === 'done') {
        const incoming = event.data as { title?: string } | undefined;
        if (incoming?.title) {
          updateTitle(id, incoming.title);
          setRec((prev) => (prev ? { ...prev, title: incoming.title! } : prev));
        }
        setSseKey(null);
        updateStatus(id, 'done');
        void getRecommendation(id)
          .then(setRec)
          .catch(() => {
            // Fetch failed after completion — poll once more via handleSSEClose logic
            updateStatus(id, 'done');
          });
        void refreshList();
      }

      if (event.status === 'error') {
        setSseKey(null);
        updateStatus(id, 'error');
        setRec((prev) => (prev ? { ...prev, status: 'error' as const } : null));
        const stageKey = event.stage as string;
        if (stageKey !== 'complete') {
          setStages((prev) => (stageKey in prev ? { ...prev, [stageKey]: 'error' } : prev));
        }
      }
    },
    [id, updateStatus, updateTitle, refreshList],
  );

  const handleSSEClose = useCallback(() => {
    void getRecommendation(id).then((data) => {
      if (data.status === 'done') {
        setSseKey(null);
        setRec(data);
        const allDone = Object.fromEntries(
          Object.keys(buildDefaultStages(data)).map((k) => [k, 'done' as StageStatus])
        );
        setStages(allDone);
        updateStatus(id, 'done');
        void refreshList();
      } else if (data.status === 'error') {
        setSseKey(null);
        setRec(data);
        updateStatus(id, 'error');
      } else {
        // Pipeline still running — reconnect with a new unique key so that
        // useSSE's useEffect always re-fires even if the base URL is the same.
        setSseKey(Date.now());
      }
    });
  }, [id, updateStatus, refreshList]);

  useSSE(sseUrl, handleSSEEvent, handleSSEClose);

  const handleRetry = async () => {
    await rerank(id);
    setStages(buildDefaultStages(rec));
    setSseKey(Date.now());
    setRec((prev) => (prev ? { ...prev, status: 'pending' as const } : null));
  };

  const handleRerank = async () => {
    await rerank(id);
    setStages(buildDefaultStages(rec));
    setSseKey(Date.now());
    setRec((prev) => (prev ? { ...prev, status: 'pending' as const } : null));
  };

  const handleCreateNew = async (body: {
    main_prompt: string;
    liked_series: string;
    disliked_series: string;
    themes: string;
    brainstorm_count?: number;
    media_type: import('../types/index.js').MediaType;
    use_web_search?: boolean;
    use_validator?: boolean;
    hard_requirements?: boolean;
    self_expansive?: boolean;
    expansive_passes?: number;
    expansive_mode?: 'soft' | 'extreme';
    generation_mode?: 'brainstorm' | 'continuous';
    total_count?: number;
    validate_results?: boolean;
  }) => {
    const newId = await createNew(body);
    if (newId) {
      route(`/recom/${newId}`);
    }
  };

  const isRunning = rec?.status === 'running' || rec?.status === 'pending' || sseKey !== null;
  const feedbackMap = new Map(feedback.map((f) => [f.item_name, f]));
  const stageGroups = buildStageGroups(rec);

  return (
    <div class="layout">
      <Sidebar
        list={list}
        selectedId={id}
        onSelect={(sid) => route(`/recom/${sid}`)}
        onNewClick={() => setShowModal(true)}
      />

      <main class="main-content">
        <div class="recom-content">
          {rec && (
            <div class="content-area">
              <div class="rec-info-panel">
                <button class="rec-info-toggle" onClick={() => setInfoExpanded((e) => !e)}>
                  <span class="rec-info-title">{rec.title}</span>
                  <span class="rec-info-chevron">{infoExpanded ? '▲' : '▼'}</span>
                </button>
                {infoExpanded && (
                  <div class="rec-info-body">
                    {rec.main_prompt && (
                      <div class="rec-info-row">
                        <span class="rec-info-label">Description</span>
                        <span class="rec-info-value">{rec.main_prompt}</span>
                      </div>
                    )}
                    {rec.liked_series && (
                      <div class="rec-info-row">
                        <span class="rec-info-label">Liked</span>
                        <span class="rec-info-value">{rec.liked_series}</span>
                      </div>
                    )}
                    {rec.disliked_series && (
                      <div class="rec-info-row">
                        <span class="rec-info-label">Disliked</span>
                        <span class="rec-info-value">{rec.disliked_series}</span>
                      </div>
                    )}
                    {rec.themes && (
                      <div class="rec-info-row">
                        <span class="rec-info-label">Themes</span>
                        <span class="rec-info-value">{rec.themes}</span>
                      </div>
                    )}
                    <div class="rec-info-row">
                      <span class="rec-info-label">Media</span>
                      <span class="rec-info-value">{rec.media_type === 'tv_show' ? 'TV series' : 'Movies'}</span>
                    </div>
                    {rec.use_web_search && (
                      <div class="rec-info-row">
                        <span class="rec-info-label">Web Search</span>
                        <span class="rec-info-badge">enabled</span>
                      </div>
                    )}
                    {rec.use_validator && (
                      <div class="rec-info-row">
                        <span class="rec-info-label">Validator</span>
                        <span class="rec-info-badge">enabled</span>
                      </div>
                    )}
                    {rec.hard_requirements && (
                      <div class="rec-info-row">
                        <span class="rec-info-label">Hard Req.</span>
                        <span class="rec-info-badge">enabled</span>
                      </div>
                    )}
                    {rec.self_expansive && (
                      <div class="rec-info-row">
                        <span class="rec-info-label">Self Expansive</span>
                        <span class="rec-info-badge">{rec.expansive_passes} pass{rec.expansive_passes !== 1 ? 'es' : ''} · {rec.expansive_mode}</span>
                      </div>
                    )}
                    <div class="rec-info-row rec-info-delete-row">
                      {!isRunning && (
                        <button class="btn-rerun btn-rerank" onClick={handleRetry}>
                          Re-run Pipeline
                        </button>
                      )}
                      <button
                        class="btn-danger"
                        onClick={() => {
                          if (confirm('Delete this recommendation?')) {
                            void deleteRec(id).then(() => route('/'));
                          }
                        }}
                      >
                        Delete
                      </button>
                    </div>
                  </div>
                )}
              </div>
            </div>
          )}

          {isRunning && (
            <div class="content-area">
              <PipelineProgress stageGroups={stageGroups} stages={stages} onRetry={handleRetry} />
            </div>
          )}

          {!isRunning && rec?.status === 'done' && rec.recommendations && (
            <div class="content-area">
              <div class="cards-grid">
                {rec.recommendations.map((show) => (
                  <RecommendationCard
                    key={show.title}
                    show={show}
                    verified={rec.use_validator}
                    existingFeedback={feedbackMap.get(show.title)}
                    onFeedback={async (name, stars, comment) => {
                      await submitFeedback({ item_name: name, stars, feedback: comment });
                    }}
                  />
                ))}
              </div>
              <div class="rerank-section">
                <button
                  class="btn-rerank"
                  onClick={handleRerank}
                  disabled={feedback.length === 0}
                  title={feedback.length === 0 ? 'Rate at least one show to enable re-ranking' : 'Re-rank based on your feedback'}
                >
                  Re-rank with Feedback {feedback.length > 0 ? `(${feedback.length} rated)` : ''}
                </button>
              </div>
            </div>
          )}

          {!isRunning && rec?.status === 'error' && (
            <div class="content-area error-state">
              <h2>Something went wrong</h2>
              <p>The pipeline encountered an error. You can try again by clicking the button below.</p>
              <button class="btn-primary" onClick={handleRetry}>
                Re-run Pipeline
              </button>
            </div>
          )}
        </div>
      </main>

      {showModal && (
        <NewRecommendationModal
          onClose={() => setShowModal(false)}
          onSubmit={handleCreateNew}
        />
      )}
    </div>
  );
}
