import React, { useEffect, useState } from 'react';
import { BookOpen, Plus, X, Edit2, Check, ChevronLeft, ChevronRight } from 'lucide-react';

// -----------------------------------------------------------------------------
// Security + Date utilities
// - Keep DD/MM/YYYY everywhere, Monday week start
// - Add strong input validation/sanitization and hardened deserialization
// -----------------------------------------------------------------------------

// ---- Security helpers ----
const MAX_TITLE_LEN = 200;
const MAX_AUTHOR_LEN = 200;
const MAX_PAGES = 100000;

/**
 * Minimal, dependency-free text sanitizer for UI strings.
 * - Removes control chars and angle brackets to defang HTML tags.
 * - Normalizes whitespace and clamps length.
 * NOTE: React escapes by default; this is defense-in-depth and protects
 *   future changes or accidental innerHTML usage.
 */
const sanitizeText = (s: unknown, maxLen: number): string => {
  if (typeof s !== 'string') return '';
  let out = s
    .replace(/[\u0000-\u001f\u007f]/g, '') // control chars
    .replace(/[<>]/g, '') // strip tag brackets
    .replace(/\s+/g, ' ') // collapse whitespace
    .trim();
  if (out.length > maxLen) out = out.slice(0, maxLen);
  return out;
};

const clampInt = (n: unknown, min: number, max: number, fallback: number): number => {
  const v = typeof n === 'number' ? n : parseInt(String(n ?? ''), 10);
  if (!Number.isFinite(v)) return fallback;
  return Math.min(max, Math.max(min, v));
};

const isValidDateKey = (key: unknown): key is string => {
  if (typeof key !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(key)) return false;
  const [y, m, d] = key.split('-').map(Number);
  const dt = new Date(y, m - 1, d);
  return dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d;
};

const coerceDateKey = (key: unknown, fallback: string): string => (isValidDateKey(key) ? String(key) : fallback);

// ---- Date helpers (DD/MM/YYYY + Monday start) ----
const toLocalDateKey = (d: Date = new Date()): string => {
  const year = d.getFullYear();
  const month = String(d.getMonth() + 1).padStart(2, '0');
  const day = String(d.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
};

const fromLocalDateKey = (key: string): Date => {
  const safeKey = coerceDateKey(key, toLocalDateKey());
  const [y, m, d] = safeKey.split('-').map(Number);
  return new Date(y, m - 1, d, 0, 0, 0, 0);
};

const formatDDMMYYYY = (keyOrDate: string | Date): string => {
  const d = typeof keyOrDate === 'string' ? fromLocalDateKey(keyOrDate) : keyOrDate;
  const dd = String(d.getDate()).padStart(2, '0');
  const mm = String(d.getMonth() + 1).padStart(2, '0');
  const yyyy = d.getFullYear();
  return `${dd}/${mm}/${yyyy}`;
};

const formatDateLong = (key: string): string => {
  const date = fromLocalDateKey(key);
  const dayName = date.toLocaleDateString('en-GB', { weekday: 'long' });
  return `${dayName}, ${formatDDMMYYYY(date)}`;
};

const formatMonthYear = (key: string): string => {
  const date = fromLocalDateKey(key);
  const month = date.toLocaleDateString('en-GB', { month: 'long' });
  return `${month} ${date.getFullYear()}`;
};

// Monday-start helpers (Sunday=0, Monday=1 in JS)
const getMondayStartPadding = (firstDay: Date): number => {
  const js = firstDay.getDay(); // 0..6 (Sun..Sat)
  const pad = js - 1; // shift so Mon=0
  return pad === -1 ? 6 : pad; // if Sunday
};

const getDaysInMonth = (year: number, monthIndex0: number): number => {
  return new Date(year, monthIndex0 + 1, 0).getDate();
};

// Pure util for tests and calendar grid
const computeMonthGridDays = (year: number, monthIndex0: number): (Date | null)[] => {
  const firstDay = new Date(year, monthIndex0, 1);
  const lastDate = getDaysInMonth(year, monthIndex0);
  const padding = getMondayStartPadding(firstDay);
  const days: (Date | null)[] = [];
  for (let i = 0; i < padding; i++) days.push(null);
  for (let d = 1; d <= lastDate; d++) days.push(new Date(year, monthIndex0, d));
  return days;
};

// -----------------------------------------------------------------------------
// Persistence helpers (LocalStorage) — hardened against polluted/invalid data
// -----------------------------------------------------------------------------
const STORAGE_KEY = 'reading-goal-app.v1';

interface ReadingHistory {
  [dateKey: string]: number; // YYYY-MM-DD (local)
}

interface BookItem {
  id: number;
  title: string;
  author?: string;
  totalPages: number;
  currentPage: number;
  startDate: string; // YYYY-MM-DD local
  targetDate: string; // YYYY-MM-DD local
  readingHistory: ReadingHistory;
  createdAt: string; // ISO timestamp
}

const serializeBooks = (arr: BookItem[]): string => JSON.stringify(arr);

const deserializeBooks = (raw: string): BookItem[] => {
  let parsed: unknown;
  try {
    parsed = JSON.parse(raw);
  } catch {
    return [];
  }
  if (!Array.isArray(parsed)) return [];

  return parsed
    .filter((b) => b && typeof b === 'object' && !Array.isArray(b))
    .map((b: any): BookItem => {
      // readingHistory using a null-prototype object to avoid prototype pollution
      const readingHistory: ReadingHistory = Object.create(null);
      if (b && typeof b.readingHistory === 'object' && b.readingHistory && !Array.isArray(b.readingHistory)) {
        for (const k of Object.keys(b.readingHistory)) {
          if (!Object.prototype.hasOwnProperty.call(b.readingHistory, k)) continue;
          if (!isValidDateKey(k)) continue;
          const v = clampInt(b.readingHistory[k], 0, MAX_PAGES, 0);
          if (v > 0) (readingHistory as any)[k] = v;
        }
      }

      const totalPages = clampInt(b?.totalPages, 1, MAX_PAGES, 1);
      const currentPageRaw = clampInt(b?.currentPage, 0, MAX_PAGES, 0);
      const currentPage = Math.min(currentPageRaw, totalPages);

      const startDate = coerceDateKey(b?.startDate, toLocalDateKey());
      const targetDate = coerceDateKey(b?.targetDate, toLocalDateKey());

      const title = sanitizeText(b?.title ?? 'Untitled', MAX_TITLE_LEN) || 'Untitled';
      const author = sanitizeText(b?.author ?? 'Unknown Author', MAX_AUTHOR_LEN) || 'Unknown Author';

      const idCandidate = Number(b?.id);
      const id = Number.isSafeInteger(idCandidate) ? idCandidate : Math.floor(Date.now() + Math.random() * 1000);

      const createdAt = typeof b?.createdAt === 'string' ? b.createdAt : new Date().toISOString();

      return {
        id,
        title,
        author,
        totalPages,
        currentPage,
        startDate,
        targetDate,
        readingHistory,
        createdAt,
      } as BookItem;
    });
};

// -----------------------------------------------------------------------------
// App component
// -----------------------------------------------------------------------------
const ReadingGoalApp = () => {
  const [books, setBooks] = useState<BookItem[]>([]);
  const [activeView, setActiveView] = useState<'dashboard' | 'detail'>('dashboard');
  const [selectedBook, setSelectedBook] = useState<BookItem | null>(null);
  const [showAddBook, setShowAddBook] = useState(false);
  const [selectedDate, setSelectedDate] = useState<string>(toLocalDateKey()); // calendar month anchor
  const [confirmDelete, setConfirmDelete] = useState<{ id: number; title: string } | null>(null);

  // Close confirm with Escape
  useEffect(() => {
    if (!confirmDelete) return;
    const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') setConfirmDelete(null); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [confirmDelete]);

  // Load from LocalStorage (client-only)
  useEffect(() => {
    if (typeof window === 'undefined') return;
    try {
      const raw = window.localStorage.getItem(STORAGE_KEY);
      if (raw) {
        const restored = deserializeBooks(raw);
        setBooks(restored);
      }
    } catch (e) {
      // eslint-disable-next-line no-console
      console.warn('Failed to restore books from storage:', e);
    }
  }, []);

  // Persist to LocalStorage when books change (fallback safety)
  useEffect(() => {
    if (typeof window === 'undefined') return;
    try {
      if (books.length === 0) {
        window.localStorage.removeItem(STORAGE_KEY);
      } else {
        window.localStorage.setItem(STORAGE_KEY, serializeBooks(books));
      }
    } catch (e) {
      // eslint-disable-next-line no-console
      console.warn('Failed to persist books to storage:', e);
    }
  }, [books]);

  // Centralized state + storage apply (ensures delete clears storage when empty)
  const applyBooks = (next: BookItem[]) => {
    setBooks(next);
    if (typeof window !== 'undefined') {
      try {
        if (next.length === 0) {
          window.localStorage.removeItem(STORAGE_KEY);
        } else {
          window.localStorage.setItem(STORAGE_KEY, serializeBooks(next));
        }
      } catch (e) {
        // eslint-disable-next-line no-console
        console.warn('Storage update failed:', e);
      }
    }
  };

  // --- Goal math
  const calculateDailyGoal = (book: BookItem) => {
    const today = fromLocalDateKey(toLocalDateKey());
    const target = fromLocalDateKey(book.targetDate);
    const msInDay = 1000 * 60 * 60 * 24;
    const daysRemaining = Math.ceil((target.getTime() - today.getTime()) / msInDay);
    const pagesRemaining = Math.max(0, book.totalPages - book.currentPage);
    if (daysRemaining <= 0) return { pages: 0, daysRemaining: 0, pagesRemaining, isOverdue: pagesRemaining > 0 };
    return {
      pages: Math.ceil(pagesRemaining / daysRemaining),
      daysRemaining,
      pagesRemaining,
      isOverdue: false,
    };
  };

  // --- CRUD helpers
  const deleteBook = (bookId: number) => {
    const next = books.filter(b => b.id !== bookId);
    applyBooks(next);
    if (selectedBook && selectedBook.id === bookId) {
      setSelectedBook(null);
      setActiveView('dashboard');
    }
  };

  // --- Progress updates
  const updateProgress = (bookId: number, dateKey: string, pagesRead: number) => {
    const safePages = clampInt(pagesRead, 0, MAX_PAGES, 0);
    const next = books.map(book => {
      if (book.id !== bookId) return book;
      const history = { ...(book.readingHistory || Object.create(null)) } as ReadingHistory;
      if (safePages > 0) (history as any)[dateKey] = safePages; else delete (history as any)[dateKey];
      const totalPagesRead = Object.values(history).reduce((s, n) => s + n, 0);
      return { ...book, readingHistory: history, currentPage: Math.min(totalPagesRead, book.totalPages) };
    });
    applyBooks(next);
    if (selectedBook && selectedBook.id === bookId) {
      const b = next.find(b => b.id === bookId)!;
      setSelectedBook(b);
    }
  };

  const updateTargetDate = (bookId: number, newDateKey: string) => {
    const key = coerceDateKey(newDateKey, toLocalDateKey());
    const next = books.map(b => (b.id === bookId ? { ...b, targetDate: key } : b));
    applyBooks(next);
  };

  const updateBookMeta = (bookId: number, data: { title?: string; author?: string; totalPages?: number }) => {
    const next = books.map(book => {
      if (book.id !== bookId) return book;
      const newTotalPages = data.totalPages !== undefined ? clampInt(data.totalPages, 1, MAX_PAGES, book.totalPages) : book.totalPages;
      const totalPagesRead = Object.values(book.readingHistory).reduce((s, n) => s + n, 0);
      const nextBook: BookItem = {
        ...book,
        title: data.title !== undefined ? (sanitizeText(data.title, MAX_TITLE_LEN) || book.title) : book.title,
        author: data.author !== undefined ? (sanitizeText(data.author, MAX_AUTHOR_LEN) || 'Unknown Author') : (book.author || 'Unknown Author'),
        totalPages: newTotalPages,
        currentPage: Math.min(totalPagesRead, newTotalPages),
      };
      return nextBook;
    });
    applyBooks(next);
    if (selectedBook && data) {
      const b = next.find(b => b.id === selectedBook.id);
      if (b) setSelectedBook(b);
    }
  };

  const getPreviousDateKey = (dateKey: string): string => {
    const d = fromLocalDateKey(dateKey);
    d.setDate(d.getDate() - 1);
    return toLocalDateKey(d);
  };

  // ---------------------------------------------------------------------------
  // Add Book Form
  // ---------------------------------------------------------------------------
  const AddBookForm = () => {
    const [title, setTitle] = useState('');
    const [author, setAuthor] = useState('');
    const [totalPages, setTotalPages] = useState<string>('');
    const [targetDate, setTargetDate] = useState<string>(''); // YYYY-MM-DD

    const minDateKey = (() => {
      const d = new Date();
      d.setDate(d.getDate() + 1);
      return toLocalDateKey(d);
    })();

    const canSubmit = title.trim() && Number(totalPages) > 0 && !!targetDate;

    return (
      <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
        <div className="bg-white rounded-lg p-6 w-full max-w-md">
          <h2 className="text-xl font-bold mb-4">Add New Book</h2>
          <div className="space-y-4">
            <div>
              <label className="block text-sm font-medium mb-1">Book Title</label>
              <input
                type="text"
                className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
                value={title}
                onChange={(e) => setTitle(e.target.value)}
                placeholder="Enter book title"
                autoComplete="off"
              />
            </div>
            <div>
              <label className="block text-sm font-medium mb-1">Author</label>
              <input
                type="text"
                className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
                value={author}
                onChange={(e) => setAuthor(e.target.value)}
                placeholder="Enter author name"
                autoComplete="off"
              />
            </div>
            <div>
              <label className="block text-sm font-medium mb-1">Total Pages</label>
              <input
                type="number"
                min={1}
                max={MAX_PAGES}
                className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
                value={totalPages}
                onChange={(e) => setTotalPages(e.target.value)}
                placeholder="Enter total pages"
                inputMode="numeric"
              />
            </div>
            <div>
              <label className="block text-sm font-medium mb-1">Target Completion Date (DD/MM/YYYY)</label>
              <input
                lang="en-GB"
                type="date"
                className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
                min={minDateKey}
                value={targetDate}
                onChange={(e) => setTargetDate(e.target.value)}
              />
              <p className="text-xs text-gray-500 mt-1">Shown and stored in local time; week starts on Monday.</p>
            </div>
          </div>
          <div className="flex gap-3 mt-6">
            <button
              onClick={() => setShowAddBook(false)}
              className="flex-1 px-4 py-2 border rounded-lg hover:bg-gray-50"
            >
              Cancel
            </button>
            <button
              onClick={() => {
                if (!canSubmit) return;
                const newBook: BookItem = {
                  id: Date.now(),
                  title: sanitizeText(title, MAX_TITLE_LEN) || 'Untitled',
                  author: sanitizeText(author, MAX_AUTHOR_LEN) || 'Unknown Author',
                  totalPages: clampInt(totalPages, 1, MAX_PAGES, 1),
                  currentPage: 0,
                  startDate: toLocalDateKey(),
                  targetDate: coerceDateKey(targetDate, toLocalDateKey()),
                  readingHistory: Object.create(null),
                  createdAt: new Date().toISOString(),
                };
                applyBooks([...books, newBook]);
                setShowAddBook(false);
              }}
              className="flex-1 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50"
              disabled={!canSubmit}
            >
              Add Book
            </button>
          </div>
        </div>
      </div>
    );
  };

  // ---------------------------------------------------------------------------
  // Book Card
  // ---------------------------------------------------------------------------
  const BookCard = ({ book, onClick, onDelete }: { book: BookItem; onClick: () => void; onDelete: () => void }) => {
    const goal = calculateDailyGoal(book);
    const progressPercent = (book.currentPage / book.totalPages) * 100;
    const todayKey = toLocalDateKey();
    const todayPages = book.readingHistory[todayKey] || 0;

    const safeTitle = sanitizeText(book.title, MAX_TITLE_LEN) || 'Untitled';
    const safeAuthor = sanitizeText(book.author || 'Unknown Author', MAX_AUTHOR_LEN) || 'Unknown Author';

    return (
      <div onClick={onClick} className="relative bg-white rounded-lg shadow-md p-4 hover:shadow-lg transition-shadow cursor-pointer">
        {/* Per-card delete button */}
        <button
          type="button"
          onMouseDown={(e) => e.stopPropagation()}
          onClick={(e) => {
            e.preventDefault();
            e.stopPropagation();
            // Stop React's synthetic event bubbling thoroughly
            // @ts-ignore
            if (e.nativeEvent && typeof e.nativeEvent.stopImmediatePropagation === 'function') {
              // @ts-ignore
              e.nativeEvent.stopImmediatePropagation();
            }
            onDelete();
          }}
          className="absolute top-2 right-2 z-10 p-1 rounded hover:bg-red-50 text-red-600"
          aria-label={`Delete ${safeTitle}`}
          title="Delete book"
        >
          <X size={16} />
        </button>

        <div className="flex justify-between items-start mb-3 pr-6">
          <div className="flex-1">
            <h3 className="font-semibold text-lg line-clamp-1">{safeTitle}</h3>
            <p className="text-sm text-gray-600">{safeAuthor}</p>
          </div>
          <div
            className={`px-2 py-1 rounded text-xs font-medium ${
              progressPercent >= 100 ? 'bg-green-100 text-green-700' : 'bg-blue-100 text-blue-700'
            }`}
          >
            {progressPercent >= 100 ? 'Completed' : `${goal.daysRemaining} days left`}
          </div>
        </div>

        <div className="mb-3">
          <div className="flex justify-between text-sm mb-1">
            <span className="text-gray-600">Progress</span>
            <span className="font-medium">{book.currentPage} / {book.totalPages} pages</span>
          </div>
          <div className="w-full bg-gray-200 rounded-full h-2">
            <div className="bg-blue-500 h-2 rounded-full transition-all" style={{ width: `${Math.min(progressPercent, 100)}%` }} />
          </div>
          <div className="text-xs text-gray-500 mt-1">Target: {formatDDMMYYYY(book.targetDate)}</div>
        </div>

        <div className="flex items-center justify-between">
          <div className="text-center">
            <p className="text-2xl font-bold text-blue-600">{goal.pages}</p>
            <p className="text-xs text-gray-600">pages/day</p>
          </div>
          <div className="text-right">
            <p className="text-sm font-medium">{todayPages} pages today</p>
            <p className="text-xs text-gray-600">{goal.pages > 0 ? `${Math.max(0, goal.pages - todayPages)} remaining` : 'Done!'}</p>
          </div>
        </div>
      </div>
    );
  };

  // ---------------------------------------------------------------------------
  // Book Detail
  // ---------------------------------------------------------------------------
  const BookDetailView = ({ book }: { book: BookItem }) => {
    const [editingProgress, setEditingProgress] = useState(false);
    const [progressInput, setProgressInput] = useState<string>('');
    const [editingDate, setEditingDate] = useState(false);
    const [newTargetDate, setNewTargetDate] = useState<string>(book.targetDate);
    const [editDateKey, setEditDateKey] = useState<string>('');
    const [clickTimer, setClickTimer] = useState<number | null>(null);
    const [editingMeta, setEditingMeta] = useState(false);
    const [metaTitle, setMetaTitle] = useState<string>(book.title);
    const [metaAuthor, setMetaAuthor] = useState<string>(book.author || '');
    const [metaTotalPages, setMetaTotalPages] = useState<string>(String(book.totalPages));

    const goal = calculateDailyGoal(book);
    const progressPercent = (book.currentPage / book.totalPages) * 100;

    // Generate calendar days for the current month (Monday start)
    const generateCalendarDays = () => {
      const anchor = fromLocalDateKey(selectedDate);
      const year = anchor.getFullYear();
      const month = anchor.getMonth();
      return computeMonthGridDays(year, month);
    };

    const calendarDays = generateCalendarDays();

    const handleDateInteraction = (dateKey: string, isDouble: boolean) => {
      if (isDouble) {
        if (clickTimer) {
          window.clearTimeout(clickTimer);
          setClickTimer(null);
        }
        const prevKey = getPreviousDateKey(dateKey);
        setEditDateKey(prevKey);
        setProgressInput(String(book.readingHistory[prevKey] || 0));
        setEditingProgress(true);
      } else {
        if (clickTimer) window.clearTimeout(clickTimer);
        const timer = window.setTimeout(() => {
          setEditDateKey(dateKey);
          setProgressInput(String(book.readingHistory[dateKey] || 0));
          setEditingProgress(true);
        }, 250);
        setClickTimer(timer);
      }
    };

    const handleSaveProgress = () => {
      const pages = Math.max(0, parseInt(progressInput || '0', 10));
      if (editDateKey) updateProgress(book.id, editDateKey, pages);
      setEditingProgress(false);
      setProgressInput('');
      setEditDateKey('');
    };

    const todayKey = toLocalDateKey();
    const todayStart = fromLocalDateKey(todayKey);

    const safeTitle = sanitizeText(book.title, MAX_TITLE_LEN) || 'Untitled';
    const safeAuthor = sanitizeText(book.author || 'Unknown Author', MAX_AUTHOR_LEN) || 'Unknown Author';

    return (
      <div className="max-w-4xl mx-auto p-4">
        <div className="mb-4 flex items-center justify-between">
          <button onClick={() => { setSelectedBook(null); setActiveView('dashboard'); }} className="flex items-center gap-2 text-gray-600 hover:text-gray-900">
            <ChevronLeft size={20} />
            Back to Dashboard
          </button>
          <button
            type="button"
            onClick={() => { setConfirmDelete({ id: book.id, title: safeTitle }); }}
            className="text-red-500 hover:text-red-700"
            aria-label="Delete book"
          >
            <X size={20} />
          </button>
        </div>

        <div className="bg-white rounded-lg shadow-lg p-6 mb-6">
          <div className="flex items-start justify-between mb-2">
            <div>
              <h1 className="text-2xl font-bold">{safeTitle}</h1>
              <p className="text-gray-600">{safeAuthor}</p>
            </div>
            <button
              onClick={() => { setMetaTitle(book.title); setMetaAuthor(book.author || ''); setMetaTotalPages(String(book.totalPages)); setEditingMeta(true); }}
              className="flex items-center gap-2 px-3 py-2 border rounded-lg hover:bg-gray-50"
              aria-label="Edit book details"
            >
              <Edit2 size={16} />
              <span className="text-sm">Edit details</span>
            </button>
          </div>

          <div className="grid md:grid-cols-2 gap-6">
            <div>
              <h3 className="font-semibold mb-3">Progress Overview</h3>
              <div className="space-y-3">
                <div className="flex justify-between"><span className="text-gray-600">Current Progress</span><span className="font-medium">{book.currentPage} / {book.totalPages} pages</span></div>
                <div className="w-full bg-gray-200 rounded-full h-3"><div className="bg-blue-500 h-3 rounded-full transition-all" style={{ width: `${Math.min(progressPercent, 100)}%` }} /></div>
                <div className="flex justify-between"><span className="text-gray-600">Completion</span><span className="font-medium">{progressPercent.toFixed(1)}%</span></div>
              </div>
            </div>
            <div>
              <h3 className="font-semibold mb-3">Daily Reading Goal</h3>
              <div className="bg-blue-50 rounded-lg p-4 text-center">
                <p className="text-3xl font-bold text-blue-600">{goal.pages}</p>
                <p className="text-sm text-gray-600">pages per day</p>
                <p className="text-xs text-gray-500 mt-2">{goal.pagesRemaining} pages remaining • {goal.daysRemaining} days left</p>
              </div>
            </div>
          </div>

          <div className="mt-6">
            <div className="flex items-center justify-between mb-2">
              <h3 className="font-semibold">Target Completion Date</h3>
              <button onClick={() => setEditingDate(!editingDate)} className="text-blue-500 hover:text-blue-700" aria-label="Edit target date">
                <Edit2 size={16} />
              </button>
            </div>
            {editingDate ? (
              <div>
                <p className="text-xs text-gray-500 mb-2">Format: DD/MM/YYYY</p>
                <div className="flex gap-2">
                  <input
                    lang="en-GB"
                    type="date"
                    className="flex-1 px-3 py-2 border rounded-lg"
                    value={newTargetDate}
                    min={toLocalDateKey()}
                    onChange={(e) => setNewTargetDate(e.target.value)}
                  />
                  <button onClick={() => { updateTargetDate(book.id, newTargetDate); setEditingDate(false); }} className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600" aria-label="Save target date">
                    <Check size={16} />
                  </button>
                  <button onClick={() => { setNewTargetDate(book.targetDate); setEditingDate(false); }} className="px-4 py-2 border rounded-lg hover:bg-gray-50" aria-label="Cancel editing date">
                    <X size={16} />
                  </button>
                </div>
              </div>
            ) : (
              <p className="text-lg">{formatDateLong(book.targetDate)}</p>
            )}
          </div>
        </div>

        {editingMeta && (
          <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
            <div className="bg-white rounded-lg p-6 w-full max-w-md">
              <h2 className="text-xl font-bold mb-4">Edit Book Details</h2>
              <div className="space-y-4">
                <div>
                  <label className="block text-sm font-medium mb-1">Title</label>
                  <input
                    type="text"
                    className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
                    value={metaTitle}
                    onChange={(e) => setMetaTitle(e.target.value)}
                    placeholder="Enter book title"
                    autoComplete="off"
                  />
                </div>
                <div>
                  <label className="block text-sm font-medium mb-1">Author</label>
                  <input
                    type="text"
                    className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
                    value={metaAuthor}
                    onChange={(e) => setMetaAuthor(e.target.value)}
                    placeholder="Enter author name"
                    autoComplete="off"
                  />
                </div>
                <div>
                  <label className="block text-sm font-medium mb-1">Total Pages</label>
                  <input
                    type="number"
                    min={1}
                    max={MAX_PAGES}
                    className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
                    value={metaTotalPages}
                    onChange={(e) => setMetaTotalPages(e.target.value)}
                    placeholder="Enter total pages"
                    inputMode="numeric"
                  />
                  <p className="text-xs text-gray-500 mt-1">Changing total pages won't alter your reading history; progress caps at total pages.</p>
                </div>
              </div>
              <div className="flex gap-3 mt-6">
                <button
                  onClick={() => setEditingMeta(false)}
                  className="flex-1 px-4 py-2 border rounded-lg hover:bg-gray-50"
                >
                  Cancel
                </button>
                <button
                  onClick={() => { const tp = clampInt(metaTotalPages, 1, MAX_PAGES, 1); updateBookMeta(book.id, { title: metaTitle, author: metaAuthor, totalPages: tp }); setEditingMeta(false); }}
                  className="flex-1 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50"
                  disabled={!metaTitle.trim() || (clampInt(metaTotalPages, 1, MAX_PAGES, 1) < 1)}
                >
                  Save
                </button>
              </div>
            </div>
          </div>
        )}

        {/* Reading Calendar */}
        <div className="bg-white rounded-lg shadow-lg p-6">
          <h3 className="font-semibold mb-4">Reading History</h3>
          <p className="text-sm text-gray-600 mb-4">Single-click to edit that date • Double-click to edit the previous day</p>

          {/* Month Navigation */}
          <div className="flex items-center justify-between mb-4">
            <button
              onClick={() => {
                const d = fromLocalDateKey(selectedDate);
                d.setMonth(d.getMonth() - 1);
                setSelectedDate(toLocalDateKey(d));
              }}
              className="p-2 hover:bg-gray-100 rounded"
              aria-label="Previous month"
            >
              <ChevronLeft size={20} />
            </button>
            <h4 className="font-medium">{formatMonthYear(selectedDate)}</h4>
            <button
              onClick={() => {
                const d = fromLocalDateKey(selectedDate);
                d.setMonth(d.getMonth() + 1);
                setSelectedDate(toLocalDateKey(d));
              }}
              className="p-2 hover:bg-gray-100 rounded"
              aria-label="Next month"
            >
              <ChevronRight size={20} />
            </button>
          </div>

          {/* Calendar Grid (Monday start) */}
          <div className="grid grid-cols-7 gap-1 mb-4">
            {['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].map((day) => (
              <div key={day} className="text-center text-xs font-medium text-gray-600 py-2">
                {day}
              </div>
            ))}
            {calendarDays.map((day, index) => {
              if (!day) return <div key={index} />;
              const dateKey = toLocalDateKey(day);
              const pagesRead = book.readingHistory[dateKey] || 0;
              const isToday = dateKey === todayKey;
              const isPast = day < todayStart; // start-of-day compare
              return (
                <button
                  key={index}
                  onClick={() => handleDateInteraction(dateKey, false)}
                  onDoubleClick={() => handleDateInteraction(dateKey, true)}
                  className={`p-2 rounded-lg text-center hover:bg-gray-100 relative ${
                    isToday ? 'ring-2 ring-blue-500' : ''
                  } ${pagesRead > 0 ? 'bg-green-50' : isPast ? 'bg-gray-50' : ''}`}
                >
                  <div className="text-sm">{day.getDate()}</div>
                  {pagesRead > 0 && <div className="text-xs font-medium text-green-600">{pagesRead}p</div>}
                </button>
              );
            })}
          </div>

          {/* Edit Progress */}
          {editingProgress && (
            <div className="border-t pt-4">
              <h4 className="font-medium mb-2">Edit progress for {formatDDMMYYYY(editDateKey)}</h4>
              <div className="flex gap-2">
                <input
                  type="number"
                  className="flex-1 px-3 py-2 border rounded-lg"
                  value={progressInput}
                  onChange={(e) => setProgressInput(e.target.value)}
                  placeholder="Pages read"
                  min={0}
                  inputMode="numeric"
                />
                <button onClick={handleSaveProgress} className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600">Save</button>
                <button onClick={() => { setEditingProgress(false); setProgressInput(''); setEditDateKey(''); }} className="px-4 py-2 border rounded-lg hover:bg-gray-50">Cancel</button>
              </div>
            </div>
          )}
        </div>
      </div>
    );
  };

  // ---------------------------------------------------------------------------
  // Dashboard
  // ---------------------------------------------------------------------------
  const DashboardView = () => {
    const todayKey = toLocalDateKey();
    const activeBooks = books.filter((b) => b.currentPage < b.totalPages);
    const completedBooks = books.filter((b) => b.currentPage >= b.totalPages);

    const todaysTotalGoal = activeBooks.reduce((sum, b) => sum + calculateDailyGoal(b).pages, 0);
    const todaysTotalRead = activeBooks.reduce((sum, b) => sum + (b.readingHistory[todayKey] || 0), 0);

    return (
      <div className="max-w-6xl mx-auto p-4">
        <div className="mb-6">
          <h1 className="text-3xl font-bold mb-2">Reading Dashboard</h1>
          <p className="text-gray-600">Track your daily reading goals</p>
        </div>

        {activeBooks.length > 0 && (
          <div className="bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-lg p-6 mb-6">
            <h2 className="text-xl font-semibold mb-4">Today's Reading Goal ({formatDDMMYYYY(fromLocalDateKey(todayKey))})</h2>
            <div className="grid grid-cols-3 gap-4">
              <div><p className="text-3xl font-bold">{todaysTotalGoal}</p><p className="text-blue-100">Total pages to read</p></div>
              <div><p className="text-3xl font-bold">{todaysTotalRead}</p><p className="text-blue-100">Pages completed</p></div>
              <div><p className="text-3xl font-bold">{Math.max(0, todaysTotalGoal - todaysTotalRead)}</p><p className="text-blue-100">Pages remaining</p></div>
            </div>
          </div>
        )}

        <div className="mb-6">
          <div className="flex justify-between items-center mb-4">
            <h2 className="text-xl font-semibold">Currently Reading</h2>
            <button onClick={() => setShowAddBook(true)} className="flex items-center gap-2 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600">
              <Plus size={16} />
              Add Book
            </button>
          </div>

          {activeBooks.length > 0 ? (
            <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
              {activeBooks.map((book) => (
                <BookCard
                  key={book.id}
                  book={book}
                  onClick={() => { setSelectedBook(book); setActiveView('detail'); }}
                  onDelete={() => setConfirmDelete({ id: book.id, title: sanitizeText(book.title, MAX_TITLE_LEN) || 'Untitled' })}
                />
              ))}
            </div>
          ) : (
            <div className="bg-gray-50 rounded-lg p-8 text-center">
              <BookOpen size={48} className="mx-auto text-gray-400 mb-3" />
              <p className="text-gray-600 mb-4">No active books yet</p>
              <button onClick={() => setShowAddBook(true)} className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600">Add Your First Book</button>
            </div>
          )}
        </div>

        {completedBooks.length > 0 && (
          <div>
            <h2 className="text-xl font-semibold mb-4">Completed</h2>
            <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
              {completedBooks.map((book) => (
                <BookCard
                  key={book.id}
                  book={book}
                  onClick={() => { setSelectedBook(book); setActiveView('detail'); }}
                  onDelete={() => setConfirmDelete({ id: book.id, title: sanitizeText(book.title, MAX_TITLE_LEN) || 'Untitled' })}
                />
              ))}
            </div>
          </div>
        )}
      </div>
    );
  };

  return (
    <div className="min-h-screen bg-gray-100" lang="en-GB">{/* ensures Monday-start and DD/MM/YYYY in native date pickers */}
      {activeView === 'dashboard' && <DashboardView />}
      {activeView === 'detail' && selectedBook && <BookDetailView book={selectedBook} />}
      {showAddBook && <AddBookForm />}

      {/* Delete confirmation modal */}
      {confirmDelete && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
          <div role="dialog" aria-modal="true" aria-labelledby="delete-title" className="bg-white rounded-lg w-full max-w-md p-6 shadow-xl">
            <h2 id="delete-title" className="text-xl font-bold mb-2">Delete "{confirmDelete.title}"?</h2>
            <p className="text-sm text-gray-600 mb-4">This will permanently remove the book and its reading history from this device. This action cannot be undone.</p>
            <div className="flex gap-3 justify-end">
              <button onClick={() => setConfirmDelete(null)} className="px-4 py-2 border rounded-lg hover:bg-gray-50">Cancel</button>
              <button onClick={() => { deleteBook(confirmDelete.id); setConfirmDelete(null); }} className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700">Delete</button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
};

// -----------------------------------------------------------------------------
// Dev-time tests (non-breaking). These run once in dev to validate date logic.
// They do NOT modify UI; they only log to the console.
// -----------------------------------------------------------------------------
const __runDevTests = () => {
  try {
    // Roundtrip local key
    const k = '2025-09-01';
    console.assert(toLocalDateKey(fromLocalDateKey(k)) === k, 'Roundtrip failed');

    // Format DD/MM/YYYY
    console.assert(formatDDMMYYYY('2025-09-01') === '01/09/2025', 'DD/MM/YYYY format failed');

    // Monday-start padding checks
    const sept2025 = computeMonthGridDays(2025, 8); // Sept 2025 starts Monday
    const aug2025 = computeMonthGridDays(2025, 7);  // Aug 2025 starts Friday
    const sept2024 = computeMonthGridDays(2024, 8); // Sept 2024 starts Sunday -> padding 6
    // Count leading nulls
    const countPad = (arr: (Date | null)[]) => arr.findIndex(x => x !== null);
    console.assert(countPad(sept2025) === 0, 'September 2025 should have 0 padding (Mon start)');
    console.assert(countPad(aug2025) === 4, 'August 2025 should have 4 padding (Fri start -> 4)');
    console.assert(countPad(sept2024) === 6, 'September 2024 should have 6 padding (Sun start -> 6)');

    // Grid size sanity: padding + days-in-month
    const sizeSept = sept2025.length;
    console.assert(sizeSept === 0 + 30, 'September 2025 grid size mismatch');

    // Leap year February check
    const feb2024 = computeMonthGridDays(2024, 1); // Feb 2024
    const expectedPadFeb2024 = getMondayStartPadding(new Date(2024, 1, 1));
    console.assert(feb2024.length === expectedPadFeb2024 + 29, 'February 2024 should have 29 days + padding');

    // Long format includes correct weekday
    console.assert(formatDateLong('2025-09-01').startsWith('Monday'), 'Long format weekday incorrect for 2025-09-01');
    console.assert(formatDateLong('2024-02-29').startsWith('Thursday'), 'Long format weekday incorrect for 2024-02-29');

    // formatMonthYear
    console.assert(formatMonthYear('2025-09-01').toLowerCase().includes('september'), 'Month label should include September');

    // Persistence roundtrip (pure functions)
    const sample: BookItem = {
      id: 1,
      title: 'Sample',
      author: 'Auth',
      totalPages: 100,
      currentPage: 10,
      startDate: '2025-09-01',
      targetDate: '2025-09-30',
      readingHistory: { '2025-09-01': 10 },
      createdAt: '2025-09-01T00:00:00.000Z',
    };
    const json = serializeBooks([sample]);
    const restored = deserializeBooks(json);
    console.assert(Array.isArray(restored) && restored.length === 1, 'Deserialize array size mismatch');
    console.assert(restored[0].readingHistory['2025-09-01'] === 10, 'Reading history did not survive roundtrip');
    console.assert(formatDDMMYYYY(restored[0].targetDate) === '30/09/2025', 'Target date format unexpected after roundtrip');

    // Deletion storage policy
    const afterDeleteEmpty: BookItem[] = [];
    console.assert(Array.isArray(afterDeleteEmpty) && afterDeleteEmpty.length === 0, 'Deletion result should be empty array when last item removed');

    // Non-array JSON should deserialize to an empty list
    console.assert(deserializeBooks('{"not":"array"}').length === 0, 'Non-array JSON should return empty array');

    // --- Security tests ---
    // 1) Sanitizer removes angle brackets
    const evil = '<img src=x onerror=alert(1)>'; // no angle brackets should remain
    console.assert(!/[<>]/.test(sanitizeText(evil, 200)), 'Sanitizer should strip angle brackets');

    // 2) Invalid date keys ignored
    const poisoned = serializeBooks([{ id: 1, title: '<b>X</b>', author: '<i>Y</i>', totalPages: 10, currentPage: 0, startDate: '2024-13-99', targetDate: '2024-00-00', readingHistory: { '__proto__': 999, '2024-02-30': 5 }, createdAt: '2025-01-01T00:00:00.000Z' } as any]);
    const restored2 = deserializeBooks(poisoned);
    console.assert(restored2[0].title === 'bX/b' || restored2[0].title === 'bX/b'.slice(0), 'Title should be sanitized (tags removed)');
    console.assert(Object.keys(restored2[0].readingHistory).length === 0, 'Invalid reading history keys should be dropped');
  } catch (err) {
    // Never throw - only log
    // eslint-disable-next-line no-console
    console.warn('Dev tests encountered an error:', err);
  }
};

// Only attempt to read process in environments where it exists (Node/bundlers)
if (
  typeof window !== 'undefined' &&
  typeof process !== 'undefined' &&
  process && process.env && process.env.NODE_ENV !== 'production'
) {
  // Run after a tick to avoid blocking render in some setups
  setTimeout(__runDevTests, 0);
}

export default ReadingGoalApp;
