/**
 * Crossword Puzzle Page
 *
 * Two roles:
 *   - Teacher/Admin/Principal: list games, "New game" wizard (clue/answer pairs),
 *     preview grid (read-only), publish + delete.
 *   - Student: list published games, click to play (CSS grid of input cells,
 *     debounced save 300ms via store.updateSessionState), submit shows
 *     per-clue correctness.
 *
 * No canvas / heavyweight libs - just CSS grid (gridTemplateColumns: repeat(cols, minmax(2rem, 1fr))).
 */

import { useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { useAuthStore } from '../../store/auth';
import {
  useCrosswordStore,
  CrosswordGame,
  CrosswordSession,
  CrosswordDraftClueInput,
  SubmitSessionResult,
} from '../../store/crossword';
import {
  Plus, Trash2, Send, Play, CheckCircle2, XCircle, Eye,
  Sparkles, ChevronRight, BookOpen, Trophy, Clock, Save, X,
} from 'lucide-react';

// ---------- helpers ----------

const cellKey = (row: number, col: number) => `${row}-${col}`;

function statusColor(status: CrosswordGame['status']) {
  switch (status) {
    case 'ready':
      return 'bg-emerald-50 text-emerald-600 border-emerald-100 dark:bg-emerald-950/20 dark:text-emerald-400 dark:border-emerald-900/30';
    case 'archived':
      return 'bg-muted text-text-muted/60 border-border';
    default:
      return 'bg-amber-50 text-amber-600 border-amber-100 dark:bg-amber-950/20 dark:text-amber-400 dark:border-amber-900/30';
  }
}

// ---------- main page ----------

export function CrosswordPage() {
  const { user } = useAuthStore();
  const isStaff = useMemo(
    () => user?.role !== 'student',
    [user?.role],
  );
  const { games, fetchGames, isLoading, error } = useCrosswordStore();
  const [showWizard, setShowWizard] = useState(false);
  const [activeGameId, setActiveGameId] = useState<number | null>(null);

  useEffect(() => {
    fetchGames();
  }, [fetchGames]);

  return (
    <div className="space-y-8 animate-in fade-in duration-700 pb-10">
      {/* Header */}
      <div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-6">
        <div>
          <h1 className="text-3xl font-black text-text tracking-tight uppercase">
            Crossword Workshop
          </h1>
          <nav className="flex text-[0.65rem] font-black uppercase tracking-[2px] text-text-muted gap-2 items-center mt-1">
            <span>{isStaff ? 'Teacher Portal' : 'Student Portal'}</span>
            <ChevronRight size={12} className="text-text-muted/30" />
            <span className="text-primary">Puzzle Studio</span>
          </nav>
        </div>
        {isStaff && (
          <button
            onClick={() => setShowWizard(true)}
            className="flex items-center gap-2 px-6 py-3 bg-primary text-white rounded-2xl font-black text-[0.7rem] uppercase tracking-widest hover:bg-primary/90 transition-all shadow-lg shadow-blue-900/10 active:scale-95"
          >
            <Plus size={18} /> New Puzzle
          </button>
        )}
      </div>

      {/* Error */}
      {error && (
        <div className="bg-rose-50 border border-rose-100 rounded-2xl p-5 flex items-center gap-4">
          <XCircle className="text-rose-600" size={24} />
          <span className="text-rose-700 font-bold text-sm">{error}</span>
        </div>
      )}

      {/* Loading */}
      {isLoading && (
        <div className="flex justify-center py-12">
          <div className="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin" />
        </div>
      )}

      {/* Game list */}
      {!isLoading && (
        <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
          {games.map((g) => (
            <GameCard key={g.id} game={g} isStaff={isStaff} onPlay={() => setActiveGameId(g.id)} />
          ))}
          {games.length === 0 && (
            <div className="col-span-full text-center py-20 bg-card border-2 border-dashed border-border rounded-[2rem]">
              <BookOpen size={48} className="mx-auto text-text-muted/30 mb-4" />
              <p className="font-black uppercase tracking-widest text-[0.7rem] text-text-muted">
                {isStaff ? 'No puzzles yet — create one!' : 'No published puzzles yet'}
              </p>
            </div>
          )}
        </div>
      )}

      {/* New game wizard */}
      {showWizard && <NewGameWizard onClose={() => setShowWizard(false)} />}

      {/* Play/Preview modal rendering outside card overflow-hidden boundary */}
      {activeGameId !== null && (
        <GamePlayModal
          gameId={activeGameId}
          onClose={() => setActiveGameId(null)}
        />
      )}
    </div>
  );
}

// ---------- game card ----------

function GameCard({ game, isStaff, onPlay }: { game: CrosswordGame; isStaff: boolean; onPlay: () => void }) {
  const { publishGame, deleteGame } = useCrosswordStore();

  return (
    <>
      <div className="bg-card border border-border rounded-[2rem] p-6 hover:shadow-xl hover:shadow-blue-900/5 transition-all group relative overflow-hidden">
        <div className="absolute top-0 right-0 w-40 h-40 bg-blue-500/5 rounded-bl-full -mr-12 -mt-12 group-hover:bg-blue-500/10 transition-colors" />
        <div className="flex items-start gap-4 relative z-10">
          <div className="w-14 h-14 rounded-2xl bg-blue-50 text-blue-600 dark:bg-blue-950/20 dark:text-blue-400 flex items-center justify-center border border-blue-100 dark:border-blue-900/30 shadow-sm shrink-0">
            <BookOpen size={28} />
          </div>
          <div className="flex-1 min-w-0">
            <h3 className="text-lg font-black text-text truncate">{game.title}</h3>
            {game.description && (
              <p className="text-[0.75rem] text-text-muted mt-1 line-clamp-2">{game.description}</p>
            )}
            <div className="flex flex-wrap items-center gap-3 mt-4">
              <span className={`px-2.5 py-1 rounded-lg text-[0.6rem] font-black uppercase tracking-widest border ${statusColor(game.status)}`}>
                {game.status}
              </span>
              <span className="px-2.5 py-1 rounded-lg text-[0.6rem] font-black uppercase tracking-widest border bg-muted text-text-secondary border-border">
                {game.difficulty}
              </span>
              <span className="flex items-center gap-1.5 text-[0.7rem] font-bold text-text-muted">
                <Trophy size={14} className="text-amber-500" /> {game.reward_points} pts
              </span>
              <span className="flex items-center gap-1.5 text-[0.7rem] font-bold text-text-muted">
                <Sparkles size={14} className="text-text-muted/50" /> {game.word_count} words
              </span>
              {game.is_published ? (
                <span className="px-2.5 py-1 rounded-lg text-[0.6rem] font-black uppercase tracking-widest border bg-emerald-50 text-emerald-600 border-emerald-100 dark:bg-emerald-950/20 dark:text-emerald-400 dark:border-emerald-900/30">
                  Published
                </span>
              ) : (
                <span className="px-2.5 py-1 rounded-lg text-[0.6rem] font-black uppercase tracking-widest border bg-muted text-text-muted/60 border-border">
                  Draft
                </span>
              )}
            </div>
          </div>
        </div>
        <div className="flex gap-2 mt-6 relative z-10">
          {isStaff ? (
            <>
              <button
                onClick={onPlay}
                className="flex-1 flex items-center justify-center gap-2 px-4 py-2.5 bg-primary text-white rounded-xl font-black text-[0.65rem] uppercase tracking-widest hover:bg-primary/90 transition-all active:scale-95"
              >
                <Eye size={14} /> Preview
              </button>
              {game.status === 'ready' && !game.is_published && (
                <button
                  onClick={async () => {
                    if (confirm(`Publish "${game.title}" so students can play?`)) {
                      await publishGame(game.id);
                    }
                  }}
                  className="flex items-center gap-2 px-4 py-2.5 bg-emerald-600 text-white rounded-xl font-black text-[0.65rem] uppercase tracking-widest hover:bg-emerald-700 transition-all active:scale-95"
                >
                  <Send size={14} /> Publish
                </button>
              )}
              <button
                onClick={async () => {
                  if (confirm(`Delete "${game.title}"? This cannot be undone.`)) {
                    await deleteGame(game.id);
                  }
                }}
                className="p-2.5 bg-card border border-border text-text-muted hover:text-rose-500 hover:border-rose-100 rounded-xl transition-all"
                title="Delete"
              >
                <Trash2 size={14} />
              </button>
            </>
          ) : (
            game.is_published === 1 && (
              <button
                onClick={onPlay}
                className="flex-1 flex items-center justify-center gap-2 px-4 py-2.5 bg-primary text-white rounded-xl font-black text-[0.65rem] uppercase tracking-widest hover:bg-primary/90 transition-all active:scale-95"
              >
                <Play size={14} /> Play Puzzle
              </button>
            )
          )}
        </div>
      </div>
    </>
  );
}

// ---------- new game wizard ----------

function NewGameWizard({ onClose }: { onClose: () => void }) {
  const { createGame } = useCrosswordStore();
  const [step, setStep] = useState<1 | 2>(1);
  const [meta, setMeta] = useState({
    title: '',
    description: '',
    difficulty: 'medium' as 'easy' | 'medium' | 'hard',
    reward_points: 10,
  });
  const [clues, setClues] = useState<CrosswordDraftClueInput[]>([
    { clue_text: '', answer: '', direction: 'across' },
  ]);
  const [submitting, setSubmitting] = useState(false);
  const [errMsg, setErrMsg] = useState<string | null>(null);

  const addClue = () =>
    setClues([...clues, { clue_text: '', answer: '', direction: 'across' }]);

  const removeClue = (i: number) =>
    setClues(clues.filter((_, idx) => idx !== i));

  const updateClue = (i: number, field: keyof CrosswordDraftClueInput, value: string) => {
    const next = [...clues];
    next[i] = { ...next[i], [field]: value };
    setClues(next);
  };

  const handleSubmit = async () => {
    setErrMsg(null);
    const trimmed = clues
      .map((c) => ({
        clue_text: c.clue_text.trim(),
        answer: c.answer.trim().toUpperCase().replace(/\s+/g, ''),
        direction: c.direction || 'across',
        hint: c.hint?.trim() || undefined,
      }))
      .filter((c) => c.clue_text && c.answer);

    if (trimmed.length === 0) {
      setErrMsg('Please add at least one valid clue/answer pair.');
      return;
    }
    setSubmitting(true);
    try {
      await createGame({
        title: meta.title.trim(),
        description: meta.description.trim() || undefined,
        reward_points: meta.reward_points,
        clues: trimmed,
      });
      onClose();
    } catch (e: any) {
      setErrMsg(e.response?.data?.error || e.message || 'Failed to create game');
    } finally {
      setSubmitting(false);
    }
  };

  const valid = meta.title.trim() && clues.some((c) => c.clue_text.trim() && c.answer.trim());

  return createPortal(
    <div className="fixed inset-0 bg-[#002147]/60 backdrop-blur-sm z-[150] flex items-center justify-center p-4" onClick={onClose}>
      <div className="bg-card border border-border rounded-[2rem] w-full max-w-2xl max-h-[90vh] overflow-hidden flex flex-col shadow-2xl" onClick={(e) => e.stopPropagation()}>
        <div className="px-8 py-6 bg-muted/50 border-b border-border flex items-center justify-between shrink-0">
          <div>
            <h2 className="text-xl font-black text-text uppercase tracking-tight">New Crossword Puzzle</h2>
            <p className="text-xs text-text-muted uppercase tracking-widest mt-1 font-bold">
              {step === 1 ? 'Configure Metadata' : 'Define Clues & Answers'}
            </p>
          </div>
          <button onClick={onClose} className="w-9 h-9 flex items-center justify-center rounded-xl bg-card border border-border text-text-muted hover:text-rose-500">
            <X size={18} />
          </button>
        </div>

        <div className="p-8 overflow-y-auto flex-1">
          {errMsg && (
            <div className="mb-4 bg-rose-50 border border-rose-100 rounded-xl p-4 text-rose-700 text-sm font-bold">
              {errMsg}
            </div>
          )}

          {step === 1 && (
            <div className="space-y-5">
              <div>
                <label className="text-[0.6rem] font-black uppercase tracking-[2px] text-text-muted px-1 mb-1.5 block">
                  Puzzle Title *
                </label>
                <input
                  type="text"
                  className="form-input"
                  value={meta.title}
                  onChange={(e) => setMeta({ ...meta, title: e.target.value })}
                  placeholder="e.g., Africa Capitals — Grade 5"
                />
              </div>
              <div>
                <label className="text-[0.6rem] font-black uppercase tracking-[2px] text-text-muted px-1 mb-1.5 block">
                  Description
                </label>
                <textarea
                  rows={2}
                  className="form-input"
                  value={meta.description}
                  onChange={(e) => setMeta({ ...meta, description: e.target.value })}
                  placeholder="Optional context for students"
                />
              </div>
              <div className="grid grid-cols-2 gap-4">
                <div>
                  <label className="text-[0.6rem] font-black uppercase tracking-[2px] text-text-muted px-1 mb-1.5 block">
                    Difficulty
                  </label>
                  <select
                    className="form-input appearance-none cursor-pointer"
                    value={meta.difficulty}
                    onChange={(e) => setMeta({ ...meta, difficulty: e.target.value as any })}
                  >
                    <option value="easy">Easy</option>
                    <option value="medium">Medium</option>
                    <option value="hard">Hard</option>
                  </select>
                </div>
                <div>
                  <label className="text-[0.6rem] font-black uppercase tracking-[2px] text-text-muted px-1 mb-1.5 block">
                    Reward Points
                  </label>
                  <input
                    type="number"
                    className="form-input"
                    value={meta.reward_points}
                    onChange={(e) => setMeta({ ...meta, reward_points: Number(e.target.value) })}
                    min={1}
                  />
                </div>
              </div>
              <div className="flex justify-end pt-4">
                <button
                  onClick={() => setStep(2)}
                  disabled={!meta.title.trim()}
                  className="px-8 py-3 bg-primary text-white rounded-xl font-black text-[0.7rem] uppercase tracking-widest hover:bg-primary/90 transition-all shadow-lg active:scale-95 disabled:opacity-50"
                >
                  Next: Clues
                </button>
              </div>
            </div>
          )}

          {step === 2 && (
            <div className="space-y-5">
              <div className="flex items-center justify-between">
                <p className="text-[0.7rem] font-black uppercase tracking-widest text-text-muted">
                  {clues.length} clue{clues.length === 1 ? '' : 's'}
                </p>
                <button
                  onClick={addClue}
                  className="flex items-center gap-2 px-4 py-2 bg-blue-50 text-primary border border-blue-100 rounded-xl font-black text-[0.6rem] uppercase tracking-widest"
                >
                  <Plus size={14} /> Add Clue
                </button>
              </div>

              {clues.map((c, i) => (
                <div key={i} className="bg-muted/30 border border-border rounded-2xl p-4 space-y-3 relative group">
                  <div className="flex items-center justify-between">
                    <span className="text-[0.65rem] font-black uppercase tracking-widest text-text-secondary">
                      Clue #{i + 1}
                    </span>
                    {clues.length > 1 && (
                      <button
                        onClick={() => removeClue(i)}
                        className="text-rose-500 hover:bg-rose-500/10 p-1.5 rounded-lg opacity-0 group-hover:opacity-100 transition-all"
                      >
                        <Trash2 size={14} />
                      </button>
                    )}
                  </div>
                  <div>
                    <label className="text-[0.6rem] font-black uppercase tracking-[2px] text-text-muted px-1 mb-1 block">
                      Direction
                    </label>
                    <select
                      className="form-input appearance-none cursor-pointer"
                      value={c.direction}
                      onChange={(e) => updateClue(i, 'direction', e.target.value)}
                    >
                      <option value="across">Across</option>
                      <option value="down">Down</option>
                    </select>
                  </div>
                  <div>
                    <label className="text-[0.6rem] font-black uppercase tracking-[2px] text-text-muted px-1 mb-1 block">
                      Clue Text
                    </label>
                    <input
                      type="text"
                      className="form-input"
                      value={c.clue_text}
                      onChange={(e) => updateClue(i, 'clue_text', e.target.value)}
                      placeholder="e.g., Capital of Zimbabwe"
                    />
                  </div>
                  <div>
                    <label className="text-[0.6rem] font-black uppercase tracking-[2px] text-text-muted px-1 mb-1 block">
                      Answer (single word, A–Z)
                    </label>
                    <input
                      type="text"
                      className="form-input uppercase tracking-widest"
                      value={c.answer}
                      onChange={(e) => updateClue(i, 'answer', e.target.value)}
                      placeholder="e.g., HARARE"
                      maxLength={32}
                    />
                  </div>
                  <div>
                    <label className="text-[0.6rem] font-black uppercase tracking-[2px] text-text-muted px-1 mb-1 block">
                      Hint (optional)
                    </label>
                    <input
                      type="text"
                      className="form-input"
                      value={c.hint || ''}
                      onChange={(e) => updateClue(i, 'hint', e.target.value)}
                      placeholder="e.g., starts with H"
                    />
                  </div>
                </div>
              ))}

              <div className="flex justify-between gap-3 pt-4">
                <button
                  onClick={() => setStep(1)}
                  className="px-6 py-3 bg-card border border-border text-text-muted rounded-xl font-black text-[0.7rem] uppercase tracking-widest hover:bg-muted"
                >
                  Back
                </button>
                <button
                  onClick={handleSubmit}
                  disabled={!valid || submitting}
                  className="flex items-center gap-2 px-8 py-3 bg-primary text-white rounded-xl font-black text-[0.7rem] uppercase tracking-widest hover:bg-primary/90 transition-all shadow-lg active:scale-95 disabled:opacity-50"
                >
                  {submitting ? (
                    <div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
                  ) : (
                    <CheckCircle2 size={14} />
                  )}
                  {submitting ? 'Generating…' : 'Generate Puzzle'}
                </button>
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  ,
    document.body
  );
}

// ---------- play modal ----------

function GamePlayModal({ gameId, onClose }: { gameId: number; onClose: () => void }) {
  const { user } = useAuthStore();
  const isStaff = user?.role !== 'student';
  const {
    currentGame,
    currentSession,
    isLoading,
    isSaving,
    error,
    fetchGame,
    startSession,
    updateSessionState,
    submitSession,
  } = useCrosswordStore();

  const [grid, setGrid] = useState<Record<string, string>>({});
  const [submitResult, setSubmitResult] = useState<SubmitSessionResult | null>(null);
  const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  useEffect(() => {
    fetchGame(gameId);
    return () => {
      // reset on unmount
      useCrosswordStore.getState().reset();
    };
  }, [gameId, fetchGame]);

  // Auto-start session for students when game loads
  useEffect(() => {
    if (!isStaff && currentGame && !currentSession && !submitResult) {
      startSession(gameId).catch(() => {});
    }
  }, [isStaff, currentGame, currentSession, gameId, submitResult, startSession]);

  // Hydrate grid from session's saved state (object or JSON string)
  useEffect(() => {
    if (!currentSession) return;
    const raw = currentSession.current_state;
    if (!raw) return;
    try {
      const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
      if (parsed && typeof parsed === 'object') {
        setGrid(parsed);
      }
    } catch (e) {
      // ignore - server will accept the final submit anyway
    }
  }, [currentSession?.id]);

  const layout = currentGame?.grid_layout;
  const cells = useMemo(() => layout?.cells || [], [layout]);
  const placements = useMemo(() => layout?.placements || [], [layout]);
  const rows = layout?.rows || 0;
  const cols = layout?.cols || 0;

  const handleCellChange = (row: number, col: number, value: string) => {
    const letter = (value || '').toUpperCase().slice(-1).replace(/[^A-Z]/g, '');
    const next = { ...grid, [cellKey(row, col)]: letter };
    setGrid(next);
    if (currentSession && !currentSession.completed_at) {
      if (debounceRef.current) clearTimeout(debounceRef.current);
      debounceRef.current = setTimeout(() => {
        updateSessionState(currentSession.id, next);
      }, 300);
    }
  };

  const handleSubmit = async () => {
    if (!currentSession) return;
    if (debounceRef.current) clearTimeout(debounceRef.current);
    try {
      const result = await submitSession(currentSession.id, grid);
      setSubmitResult(result);
    } catch (e) {
      // error set in store
    }
  };

  if (isLoading || !currentGame) {
    return (
      <ModalShell onClose={onClose} title="Loading puzzle…">
        <div className="space-y-4 py-10 flex flex-col items-center justify-center">
          {error ? (
            <div className="bg-rose-50 dark:bg-rose-950/20 border border-rose-100 dark:border-rose-900/30 rounded-2xl p-5 text-rose-700 dark:text-rose-400 font-bold text-sm max-w-md w-full flex items-center gap-3">
              <XCircle size={20} className="shrink-0" />
              <span>{error}</span>
            </div>
          ) : (
            <div className="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin" />
          )}
        </div>
      </ModalShell>
    );
  }

  const canSeeAnswers = currentGame._permissions?.canSeeAnswers ?? false;

  return (
    <ModalShell
      onClose={onClose}
      title={currentGame.title}
      subtitle={
        currentGame.is_published
          ? 'Published puzzle'
          : 'Draft — preview only'
      }
    >
      <div className="space-y-5">
        {error && (
          <div className="bg-rose-50 dark:bg-rose-950/20 border border-rose-100 dark:border-rose-900/30 rounded-2xl p-4 text-rose-700 dark:text-rose-400 text-sm font-bold flex items-center gap-3">
            <XCircle size={18} /> {error}
          </div>
        )}

        {/* Status row */}
        <div className="flex flex-wrap items-center gap-3">
          <span className={`px-3 py-1.5 rounded-xl text-[0.6rem] font-black uppercase tracking-widest border ${statusColor(currentGame.status)}`}>
            {currentGame.status}
          </span>
          <span className="flex items-center gap-1.5 text-[0.7rem] font-bold text-text-muted">
            <Trophy size={14} className="text-amber-500" /> {currentGame.reward_points} pts
          </span>
          <span className="flex items-center gap-1.5 text-[0.7rem] font-bold text-text-muted">
            <Sparkles size={14} className="text-text-muted/50" /> {currentGame.word_count} words
          </span>
          {currentSession && !currentSession.completed_at && (
            <span className="ml-auto flex items-center gap-2 px-3 py-1.5 rounded-xl text-[0.6rem] font-black uppercase tracking-widest border bg-blue-50 text-blue-600 border-blue-100 dark:bg-blue-950/20 dark:text-blue-400 dark:border-blue-900/30">
              {isSaving ? (
                <>
                  <div className="w-3 h-3 border-2 border-blue-600 border-t-transparent rounded-full animate-spin" /> Saving…
                </>
              ) : (
                <>
                  <Save size={12} /> Auto-saved
                </>
              )}
            </span>
          )}
        </div>

        {/* Grid */}
        {rows > 0 && cols > 0 && (
          <div className="flex justify-center">
            <div
              className="gap-0.5"
              style={{
                display: 'grid',
                gridTemplateColumns: `repeat(${cols}, minmax(2rem, 1fr))`,
              }}
            >
              {Array.from({ length: rows }).flatMap((_, r) =>
                Array.from({ length: cols }).map((_, c) => {
                  const cell = cells.find(
                    (x) => x.row === r && x.col === c && !x.is_block,
                  );
                  const isBlock = !cell;
                  const clueId = cell?.clue_ids?.[0];
                  const placement = clueId
                    ? placements.find((p) => p.clue_id === clueId)
                    : undefined;
                  const answer = placement?.answer?.[
                    r === placement?.start_row
                      ? c - (placement?.start_col ?? 0)
                      : r - (placement?.start_row ?? 0)
                  ];
                  const value = grid[cellKey(r, c)] || '';
                  const showAsCorrect =
                    submitResult?.per_clue?.find((p) => p.clue_id === clueId)?.correct;
                  const editable =
                    !isStaff &&
                    !submitResult &&
                    currentSession &&
                    !currentSession.completed_at;
                  return (
                    <div
                      key={`${r}-${c}`}
                      className={[
                        'relative aspect-square flex items-center justify-center',
                        'border border-border text-center font-black text-sm uppercase',
                        isBlock ? 'bg-muted/30' : 'bg-card',
                        showAsCorrect === true ? '!bg-emerald-50 dark:!bg-emerald-950/30' : '',
                        showAsCorrect === false ? '!bg-rose-50 dark:!bg-rose-950/30' : '',
                      ].join(' ')}
                    >
                      {!isBlock && placements.find((p) => p.start_row === r && p.start_col === c) && (
                        <span className="absolute top-0.5 left-1 text-[0.55rem] font-black text-primary">
                          {placements.find((p) => p.start_row === r && p.start_col === c)?.clue_number}
                        </span>
                      )}
                      {!isBlock && editable && (
                        <input
                          type="text"
                          maxLength={1}
                          value={value}
                          onChange={(e) => handleCellChange(r, c, e.target.value)}
                          className="w-full h-full bg-transparent text-text text-center font-black uppercase focus:outline-none focus:bg-primary/5"
                          aria-label={`Cell row ${r + 1} col ${c + 1}`}
                        />
                      )}
                      {!isBlock && !editable && (
                        <span
                          className={[
                            'uppercase',
                            submitResult && value === answer && canSeeAnswers ? 'text-emerald-700 dark:text-emerald-400' : '',
                            submitResult && value !== answer && canSeeAnswers ? 'text-rose-700 dark:text-rose-400' : '',
                          ].join(' ')}
                        >
                          {canSeeAnswers ? value || answer || '' : value}
                        </span>
                      )}
                    </div>
                  );
                }),
              )}
            </div>
          </div>
        )}

        {/* Clues */}
        <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
          <ClueList title="Across" placements={placements} direction="across" submitResult={submitResult} />
          <ClueList title="Down" placements={placements} direction="down" submitResult={submitResult} />
        </div>

        {/* Actions */}
        <div className="flex items-center justify-between pt-4 border-t border-border">
          <button
            onClick={onClose}
            className="px-6 py-2.5 bg-card border border-border text-text-muted rounded-xl font-black text-[0.65rem] uppercase tracking-widest hover:bg-muted"
          >
            Close
          </button>
          {!isStaff && currentSession && !currentSession.completed_at && !submitResult && (
            <button
              onClick={handleSubmit}
              disabled={isSaving}
              className="flex items-center gap-2 px-6 py-2.5 bg-emerald-600 hover:bg-emerald-700 text-white rounded-xl font-black text-[0.65rem] uppercase tracking-widest shadow-lg active:scale-95 disabled:opacity-50"
            >
              <Send size={14} /> Submit Answers
            </button>
          )}
          {submitResult && (
            <div className="text-right">
              <p className="text-[0.7rem] font-black uppercase tracking-widest text-text-muted">
                Score
              </p>
              <p className="text-2xl font-black text-primary">
                {submitResult.correct_count}/{submitResult.total_count}
              </p>
              <p className="text-[0.6rem] font-bold text-text-muted">
                {submitResult.points_earned} points earned
              </p>
            </div>
          )}
        </div>
      </div>
    </ModalShell>
  );
}

function ClueList({
  title,
  placements,
  direction,
  submitResult,
}: {
  title: string;
  placements: any[];
  direction: 'across' | 'down';
  submitResult: SubmitSessionResult | null;
}) {
  const list = placements.filter((p) => p.direction === direction);
  if (list.length === 0) return null;
  return (
    <div className="bg-muted/30 border border-border rounded-2xl p-4">
      <h4 className="text-[0.65rem] font-black uppercase tracking-[2px] text-text-muted mb-3">
        {title}
      </h4>
      <ol className="space-y-2">
        {list
          .sort((a, b) => a.clue_number - b.clue_number)
          .map((p) => {
            const result = submitResult?.per_clue?.find((pc) => pc.clue_id === p.clue_id);
            return (
              <li key={p.clue_id} className="flex items-start gap-2 text-[0.75rem]">
                <span className="font-black text-primary shrink-0 w-6">{p.clue_number}.</span>
                <span className="flex-1 text-text-secondary">{p.clue_text}</span>
                {submitResult && result && (
                  result.correct
                    ? <CheckCircle2 size={14} className="text-emerald-600 shrink-0" />
                    : <XCircle size={14} className="text-rose-600 shrink-0" />
                )}
              </li>
            );
          })}
      </ol>
    </div>
  );
}

// ---------- modal shell ----------

function ModalShell({
  onClose,
  title,
  subtitle,
  children,
}: {
  onClose: () => void;
  title: string;
  subtitle?: string;
  children: React.ReactNode;
}) {
  return createPortal(
    <div className="fixed inset-0 bg-[#002147]/80 backdrop-blur-md z-[200] flex items-center justify-center p-4" onClick={onClose}>
      <div className="bg-card border border-border rounded-[2rem] w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col shadow-2xl" onClick={(e) => e.stopPropagation()}>
        <div className="px-8 py-5 bg-muted/50 border-b border-border flex items-center justify-between shrink-0">
          <div>
            <h2 className="text-xl font-black text-text uppercase tracking-tight">{title}</h2>
            {subtitle && (
              <p className="text-xs text-text-muted uppercase tracking-widest mt-1 font-bold">
                {subtitle}
              </p>
            )}
          </div>
          <button
            onClick={onClose}
            className="w-9 h-9 flex items-center justify-center rounded-xl bg-card border border-border text-text-muted hover:text-rose-500"
          >
            <X size={18} />
          </button>
        </div>
        <div className="p-8 overflow-y-auto flex-1 custom-scrollbar">{children}</div>
      </div>
    </div>
  ,
    document.body
  );
}
