/* ============================================================
   wird-tracker.js  (full replacement)
   ============================================================ */

(function () {
	"use strict";

	const API = "/api/wird";

	// type: "count" | "juz" | "min" | "rating" | "nafl" | "khatm"
	const DAILY_WIRD = {
		durood: { label: "Durood", type: "count", unit: "count", target: 300 },
		istighfar: { label: "Istighfar", type: "count", unit: "count", target: 200 },
		quran: { label: "Qurʾān", type: "juz", unit: "juz", target: 2 },
		muraqabah: { label: "Murāqabah", type: "min", unit: "min", target: 10 },
	    wuqoof_qalbi: { label: "Wuqūf Qalbī", type: "rating", unit: "", target: 3 },
	    Allah: { label: "Allah's Name", type: "count", unit: "count", target: 500 },
	};

	// Nafl prayers — stored in wird_entries, value=1 when done
	const NAFL_WIRD = {
		salatul_tawbah: { label: "Ṣalāt al-Tawbah", type: "nafl", unit: "", target: 1, rakats: 2, desc: "2 rakʿāt · repentance", icon: "🤲" },
		salatul_hajaat: { label: "Ṣalāt al-Ḥājah", type: "nafl", unit: "", target: 1, rakats: 2, desc: "2 rakʿāt · need & supplication", icon: "🌙" },
		tahajjud: { label: "Tahajjud", type: "nafl", unit: "", target: 1, rakats: 0, desc: "night vigil", icon: "⭐" },
	};

	const WIRD_META = {
		...DAILY_WIRD,
		...NAFL_WIRD,
		shaykh_meeting: { label: "Meeting w/ Shaykh", type: "meeting", unit: "meeting", target: 1 },
		khatm: { label: "Khatm", type: "khatm", unit: "", target: 1 },
		motalah: { label: "Mutālaʿah", type: "motalah", unit: "min", target: 30 },
	};

	const RATING_LABELS = {
		1: "distracted", 2: "scattered", 3: "present", 4: "attentive", 5: "absorbed",
	};

	const KHATM_TYPES = {
		tilawah: "Tilāwah",
		hifz: "Ḥifẓ",
		tadabbur: "Tadabbur",
	};

	const MEETING_CYCLE_DAYS = 21;
	const HEATMAP_WEEKS = 52;

	const $ = (sel, ctx = document) => ctx.querySelector(sel);
	const $$ = (sel, ctx = document) => [...ctx.querySelectorAll(sel)];
	const isoDate = (d = new Date()) => d.toISOString().slice(0, 10);

	function daysBetween(a, b) {
		return Math.round((new Date(b) - new Date(a)) / 86400000);
	}
	function addDays(iso, n) {
		const d = new Date(iso); d.setDate(d.getDate() + n); return isoDate(d);
	}
	function fmtDisplay(iso) {
		return new Date(iso + "T00:00:00").toLocaleDateString("en-GB", {
			day: "numeric", month: "short", year: "numeric",
		});
	}

	function fmtValue(type, val) {
		const meta = WIRD_META[type];
		if (!meta) return val;
		if (meta.type === "rating") return val ? `${RATING_LABELS[val] || val} (${val}/5)` : "—";
		if (meta.type === "meeting") return val >= 1 ? "✓ attended" : "✗ missed";
		if (meta.type === "nafl") return val >= 1 ? "✓ prayed" : "—";
		if (meta.type === "khatm") return val >= 1 ? "✓ completed" : "—";
		return `${Number(val).toLocaleString()} ${meta.unit}`;
	}

	function statusClass(type, val) {
		const meta = WIRD_META[type];
		if (!meta || !val || val <= 0) return "";
		if (meta.type === "rating") {
			if (val >= 4) return "over";
			if (val >= 3) return "done";
			return "miss";
		}
		if (val >= meta.target * 1.1) return "over";
		if (val >= meta.target) return "done";
		return "miss";
	}

	// ── Mutālaʿah (Study) ─────────────────────────────────────

	const MOTALAH_API = "/api/wird/motalah";
	const CALIBRE_API = "/api/calibre/books";   // see controller below
	const MOTALAH_TARGET = 30;                     // daily target in minutes

	let motalahEntries = [];
	let calibreBooks = [];

	async function loadMotalah() {
		try {
			const [mRes, bRes] = await Promise.all([
				fetch(MOTALAH_API),
				fetch(CALIBRE_API),
			]);
			if (mRes.ok) motalahEntries = await mRes.json();
			if (bRes.ok) calibreBooks = await bRes.json();
		} catch (e) {
			console.warn("Mutalaah load error:", e);
		}
	}

	function renderMotalah() {
		const today = isoDate();
		const todayM = motalahEntries.filter(e => e.date === today);
		const todayMins = todayM.reduce((s, e) => s + Number(e.durationMinutes), 0);

		// streak
		let streak = 0;
		const now = new Date();
		for (let i = 0; i < 365; i++) {
			const d = new Date(now); d.setDate(d.getDate() - i);
			const iso = isoDate(d);
			const mins = motalahEntries.filter(e => e.date === iso)
				.reduce((s, e) => s + Number(e.durationMinutes), 0);
			if (mins >= MOTALAH_TARGET) streak++;
			else if (i > 0) break;
		}

		// month total
		const monthStart = today.slice(0, 7);
		const monthMins = motalahEntries
			.filter(e => e.date.startsWith(monthStart))
			.reduce((s, e) => s + Number(e.durationMinutes), 0);
		const monthHrs = (monthMins / 60).toFixed(1);

		// unique books touched (all time)
		const allBooks = new Set(
			motalahEntries.flatMap(e => e.bookIds || [])
		);

		$("#motalah-today-mins").textContent = todayMins || "0";
		$("#motalah-streak").textContent = streak;
		$("#motalah-total-hrs").textContent = monthHrs;
		$("#motalah-books-count").textContent = allBooks.size;

		// sessions list (20 most recent)
		const wrap = $("#motalah-sessions-wrap");
		const sorted = [...motalahEntries].sort((a, b) => b.date.localeCompare(a.date)).slice(0, 20);

		if (sorted.length === 0) {
			wrap.innerHTML = `<p style="color:var(--muted);font-style:italic">No study sessions logged yet.</p>`;
			return;
		}

		wrap.innerHTML = sorted.map(e => {
			const bookNames = (e.bookIds || [])
				.map(id => {
					const b = calibreBooks.find(b => String(b.id) === String(id));
					return b ? `<span class="motalah-book-chip">${b.title}</span>` : "";
				}).join("");

			const note = e.notes
				? `<span class="motalah-session-note"> — ${e.notes}</span>`
				: "";

			return `
        <div class="motalah-session">
          <span class="motalah-session-date">${e.date}</span>
          <span class="motalah-session-dur">${e.durationMinutes} min</span>
          <span class="motalah-session-books">${bookNames || ""}${note}</span>
        </div>
      `;
		}).join("");
	}

	// Modal
	const motalahModal = $("#motalah-modal");
	const motalahForm = $("#motalah-form");
	const motalahDateEl = $("#motalah-date");
	const motalahDurEl = $("#motalah-duration");
	const motalahBooksEl = $("#motalah-books");
	const motalahNotesEl = $("#motalah-notes");

	// ── Book search UI ────────────────────────────────────────
	// selectedBookIds: Set of ids currently picked in the modal
	let selectedBookIds = new Set();

	const bookSearchInput = document.getElementById("motalah-book-search");
	const bookSearchResults = document.getElementById("book-search-results");
	const selectedChipsWrap = document.getElementById("selected-books-chips");

	function bookById(id) {
		return calibreBooks.find(b => String(b.id) === String(id));
	}

	function renderSelectedChips() {
		selectedChipsWrap.innerHTML = [...selectedBookIds].map(id => {
			const b = bookById(id);
			const label = b ? b.title : id;
			return `<span class="selected-book-chip" data-id="${id}">
        ${label}
        <button type="button" data-id="${id}" title="Remove">×</button>
      </span>`;
		}).join("");

		// Sync hidden select so the existing submit handler still works
		motalahBooksEl.innerHTML = [...selectedBookIds]
			.map(id => `<option value="${id}" selected></option>`).join("");
	}

	function renderBookResults(query) {
		const q = query.trim().toLowerCase();
		const sorted = [...calibreBooks].sort((a, b) => a.title.localeCompare(b.title));
		const filtered = q
			? sorted.filter(b =>
				b.title.toLowerCase().includes(q) ||
				(b.authors || "").toLowerCase().includes(q)
			)
			: sorted;

		if (filtered.length === 0) {
			bookSearchResults.innerHTML = `<div class="book-no-results">No books found</div>`;
		} else {
			bookSearchResults.innerHTML = filtered.slice(0, 40).map(b => {
				const sel = selectedBookIds.has(String(b.id));
				return `<div class="book-result-item${sel ? " selected" : ""}" data-id="${b.id}">
          <div class="book-result-check">${sel ? "✓" : ""}</div>
          <span class="book-result-title">${b.title}</span>
          ${b.authors ? `<span class="book-result-author">${b.authors}</span>` : ""}
        </div>`;
			}).join("");
		}
		bookSearchResults.classList.add("open");
	}

    if (bookSearchInput) {
	bookSearchInput.addEventListener("input", () => {
	    if (calibreBooks.length === 0) return;
	    renderBookResults(bookSearchInput.value);
	});
	
	bookSearchInput.addEventListener("focus", () => {
	    if (calibreBooks.length === 0) return;
	    renderBookResults(bookSearchInput.value);
	});
	
	bookSearchResults.addEventListener("mousedown", e => {
	    const item = e.target.closest(".book-result-item");
	    if (!item) return;
	    e.preventDefault(); // prevent input blur before we register click
	    const id = String(item.dataset.id);
	    if (selectedBookIds.has(id)) {
		selectedBookIds.delete(id);
	    } else {
		selectedBookIds.add(id);
	    }
	    renderSelectedChips();
	    renderBookResults(bookSearchInput.value);
	});
    }

	if (selectedChipsWrap) {
	    selectedChipsWrap.addEventListener("click", e => {
		const btn = e.target.closest("button[data-id]");
		if (!btn) return;
		selectedBookIds.delete(String(btn.dataset.id));
		renderSelectedChips();
		// Refresh dropdown if open
		if (bookSearchResults.classList.contains("open")) {
		    renderBookResults(bookSearchInput.value);
		}
	    });
	}

    // Close dropdown on outside click
    document.addEventListener("click", e => {
	if (!e.target.closest(".book-search-wrap")) {
	    bookSearchResults.classList.remove("open");
	}
    });
    
	function populateBookSelect() {
		// no-op — book search handles population dynamically
	}

	function openMotalahModal() {
		motalahForm.reset();
		motalahDateEl.value = isoDate();
		selectedBookIds = new Set();
		bookSearchInput.value = "";
		bookSearchResults.classList.remove("open");
		bookSearchResults.innerHTML = "";
		renderSelectedChips();
		motalahModal.showModal();
	}
    if (motalahForm) {
	motalahForm.addEventListener("submit", async e => {
	    e.preventDefault();
	    const selectedBooks = [...selectedBookIds];
	    const payload = {
		date: motalahDateEl.value,
		durationMinutes: parseInt(motalahDurEl.value),
		bookIds: selectedBooks,
		notes: motalahNotesEl.value.trim() || null,
	    };
	    try {
			    const r = await fetch(MOTALAH_API, {
				method: "POST",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify(payload),
			    });
		if (!r.ok) throw new Error(await r.text());
		const saved = await r.json();
		motalahEntries.unshift(saved);
		motalahModal.close();
		renderMotalah();
	    } catch (err) {
		alert("Error saving session: " + err.message);
	    }
	});
    }

    $("#motalah-cancel").addEventListener("click", () => motalahModal.close());
    $("#log-motalah-btn").addEventListener("click", openMotalahModal);

	// ── State ────────────────────────────────────────────────
	let allEntries = [];
	let todayMap = {};
	let meetingLog = [];
	let chart = null;

	// ── Boot ─────────────────────────────────────────────────
	document.addEventListener("DOMContentLoaded", async () => {
	    setTodayLabel();
	    await Promise.all([loadAll(), loadMotalah()]);
	    renderToday();
	    renderNafl();
	    renderMeetingPanel();
	    renderKhatm();
	    renderHistory();
	    renderChart();
	    renderMotalah();
	    renderSummary();
	    renderHeatmap();
	    bindControls();
	});

	function setTodayLabel() {
		$("#today-date").textContent = new Date().toLocaleDateString("en-GB", {
			weekday: "long", day: "numeric", month: "long", year: "numeric",
		});
	}

	async function loadAll() {
		try {
			const r = await fetch(`${API}/entries`);
			if (!r.ok) throw new Error(r.status);
			allEntries = await r.json();
			buildTodayMap();
			buildMeetingLog();
		} catch (e) {
			console.error("Wird API error:", e);
		}
	}

	function buildTodayMap() {
		const today = isoDate();
		todayMap = {};
		for (const e of allEntries) {
			if (e.date !== today) continue;
			if (WIRD_META[e.wirdType]?.type === "rating") {
				const existing = todayMap[e.wirdType];
				if (!existing || e.createdAt > existing.createdAt) {
					todayMap[e.wirdType] = Number(e.value);
				}
			} else {
				todayMap[e.wirdType] = (todayMap[e.wirdType] || 0) + Number(e.value);
			}
		}
	}

	function buildMeetingLog() {
		meetingLog = allEntries
			.filter(e => e.wirdType === "shaykh_meeting" && Number(e.value) >= 1)
			.sort((a, b) => b.date.localeCompare(a.date));
	}

	async function postEntry(payload) {
		const r = await fetch(`${API}/entries`, {
			method: "POST",
			headers: { "Content-Type": "application/json" },
			body: JSON.stringify(payload),
		});
		if (!r.ok) throw new Error(await r.text());
		return r.json();
	}

	async function deleteEntry(id) {
		const r = await fetch(`${API}/entries/${id}`, { method: "DELETE" });
		if (!r.ok) throw new Error(await r.text());
	}

	// ════════════════════════════════════════════════════════
	// TODAY PANEL
	// ════════════════════════════════════════════════════════

	function renderToday() {
		const wrap = $("#wird-cards");
		wrap.innerHTML = "";
		let done = 0;
		const types = Object.keys(DAILY_WIRD);

		for (const type of types) {
			const meta = DAILY_WIRD[type];
			const val = todayMap[type] || 0;
			const cls = statusClass(type, val);
			if (cls === "done" || cls === "over") done++;

			const card = document.createElement("div");
			card.className = `wird-card ${cls}`;
			card.dataset.type = type;

			if (meta.type === "rating") {
				card.innerHTML = buildRatingCard(meta, val);
			} else {
				const pct = Math.min((val / meta.target) * 100, 100);
				card.innerHTML = `
          <div class="card-name">${meta.label}</div>
          <div class="card-meta">
            ${fmtValue(type, val)}
            <span style="opacity:.5"> / ${meta.target.toLocaleString()} ${meta.unit}</span>
          </div>
          <div class="card-bar-wrap">
            <div class="card-bar-fill" style="width:${pct}%"></div>
          </div>
          <button class="card-log-btn">+ log</button>
        `;
			}

			card.querySelector(".card-log-btn").addEventListener("click", () => openModal(type));
			wrap.appendChild(card);
		}

		const pct = Math.round((done / types.length) * 100);
		$("#today-progress-fill").style.width = pct + "%";
		$("#today-progress-label").textContent = `${done} / ${types.length} complete`;
	}

	function buildRatingCard(meta, val) {
		const pips = [1, 2, 3, 4, 5].map(n => {
			const filled = val >= n;
			const isMin = n === meta.target;
			return `<span class="card-pip ${filled ? "filled" : ""} ${isMin ? "min-marker" : ""}"
                    title="${RATING_LABELS[n]}"></span>`;
		}).join("");
		const lbl = val ? `${RATING_LABELS[val]} <span style="opacity:.45">(${val}/5)</span>` : "not logged";
		return `
      <div class="card-name">${meta.label}</div>
      <div class="card-pips">${pips}</div>
      <div class="card-meta card-rating-lbl">${lbl}</div>
      <button class="card-log-btn">+ log</button>
    `;
	}

	// ════════════════════════════════════════════════════════
	// LOG MODAL  (existing — unchanged logic)
	// ════════════════════════════════════════════════════════

	const modal = $("#log-modal");
	const form = $("#log-form");
	const typeEl = $("#log-type");
	const valEl = $("#log-value");
	const dateEl = $("#log-date");
	const notesEl = $("#log-notes");
	const ratingEl = $("#log-rating-value");

	let selectedRating = 0;
	$$("#modal-pips .modal-pip").forEach(btn => {
		btn.addEventListener("click", () => {
			selectedRating = parseInt(btn.dataset.val);
			ratingEl.value = selectedRating;
			$$("#modal-pips .modal-pip").forEach(b => {
				b.classList.toggle("selected", parseInt(b.dataset.val) <= selectedRating);
			});
		});
	});

	function openModal(preType = null) {
		form.reset();
		selectedRating = 0;
		ratingEl.value = "";
		$$("#modal-pips .modal-pip").forEach(b => b.classList.remove("selected"));
		dateEl.value = isoDate();
		if (preType) typeEl.value = preType;
		updateModalFields();
		modal.showModal();
	}

	function openMeetingModal() {
		form.reset();
		dateEl.value = isoDate();
		typeEl.value = "shaykh_meeting";
		updateModalFields();
		modal.showModal();
	}

	function updateModalFields() {
		const type = typeEl.value;
		const isRating = type === "wuqoof_qalbi";
		const isMeeting = type === "shaykh_meeting";
		$("#value-group").style.display = (!isRating && !isMeeting) ? "" : "none";
		$("#rating-group").style.display = isRating ? "" : "none";
		$("#shaykh-group").style.display = isMeeting ? "" : "none";
		if (!isRating && !isMeeting) {
			const meta = DAILY_WIRD[type];
			if (meta) valEl.placeholder = `Target: ${meta.target} ${meta.unit}`;
		}
	}

    if (typeEl){
	typeEl.addEventListener("change", updateModalFields);
    }
	$("#modal-cancel").addEventListener("click", () => modal.close());

    if (form){
	form.addEventListener("submit", async (e) => {
		e.preventDefault();
		const type = typeEl.value;
		const isRating = type === "wuqoof_qalbi";
		const isMtg = type === "shaykh_meeting";

		if (isRating && !ratingEl.value) { alert("Please select a rating."); return; }

		const payload = {
			wirdType: type,
			date: dateEl.value,
			value: isRating ? Number(ratingEl.value) : isMtg ? 1 : Number(valEl.value),
			notes: notesEl.value.trim() || null,
		};

		try {
			const saved = await postEntry(payload);
			allEntries.push(saved);
			buildTodayMap();
			buildMeetingLog();
			renderToday();
			renderNafl();
			renderMeetingPanel();
			renderHistory();
			renderChart();
			renderHeatmap();
			modal.close();
		} catch (err) {
			alert("Could not save entry: " + err.message);
		}
	});
    }
	// ════════════════════════════════════════════════════════
	// NAFL PRAYERS
	// Stored in wird_entries: wirdType = "salatul_tawbah" |
	// "salatul_hajaat" | "tahajjud", value = 1 when prayed.
	// Toggle = delete existing today entry, or post new one.
	// ════════════════════════════════════════════════════════

	function renderNafl() {
		const today = isoDate();

		// Build a map of which nafl are done today and their entry id
		const naflToday = {}; // { wirdType: { id, done: true } }
		for (const e of allEntries) {
			if (e.date !== today) continue;
			if (!NAFL_WIRD[e.wirdType]) continue;
			if (Number(e.value) >= 1) naflToday[e.wirdType] = e.id;
		}

		const done = Object.keys(naflToday).length;
		const total = Object.keys(NAFL_WIRD).length;

		// Render each card
		for (const [type, meta] of Object.entries(NAFL_WIRD)) {
			const card = document.getElementById(`nafl-${type}`);
			if (!card) continue;
			const isDone = !!naflToday[type];
			card.classList.toggle("nafl-done", isDone);
			const btn = card.querySelector(".nafl-toggle");
			btn.textContent = isDone ? "✓ prayed" : "Mark as prayed";
			btn.dataset.type = type;
			btn.dataset.entryId = naflToday[type] || "";
		}

		// Progress bar — track width = 120px for 3/3
		const barPx = Math.round((done / total) * 120);
		$("#nafl-completion-bar").style.width = barPx + "px";
		$("#nafl-completion-label").textContent = `${done} / ${total} today`;
	}

	// Single delegated listener on the nafl-cards container
	$("#nafl-cards").addEventListener("click", async e => {
		const btn = e.target.closest(".nafl-toggle");
		if (!btn) return;

		const type = btn.dataset.type;
		const entryId = btn.dataset.entryId;
		const today = isoDate();

		btn.disabled = true;
		try {
			if (entryId) {
				// Toggle OFF — delete
				await deleteEntry(entryId);
				allEntries = allEntries.filter(en => String(en.id) !== String(entryId));
			} else {
				// Toggle ON — post
				const saved = await postEntry({ wirdType: type, date: today, value: 1, notes: null });
				allEntries.push(saved);
			}
			buildTodayMap();
			renderNafl();
			renderHeatmap();
		} catch (err) {
			alert("Could not update: " + err.message);
		} finally {
			btn.disabled = false;
		}
	});

	// ════════════════════════════════════════════════════════
	// MEETING PANEL  (unchanged)
	// ════════════════════════════════════════════════════════

	function renderMeetingPanel() {
		const today = isoDate();
		const last = meetingLog[0] || null;
		const lastDate = last ? last.date : null;
		const nextDate = lastDate ? addDays(lastDate, MEETING_CYCLE_DAYS) : null;
		const daysToNext = nextDate ? daysBetween(today, nextDate) : null;

		let statusHtml = "";
		if (!lastDate) {
			statusHtml = `<span class="meeting-status pending">No meetings logged yet</span>`;
		} else if (daysToNext > 0) {
			statusHtml = `<span class="meeting-status ok">Next due in <strong>${daysToNext}</strong> day${daysToNext !== 1 ? "s" : ""} &mdash; ${fmtDisplay(nextDate)}</span>`;
		} else if (daysToNext === 0) {
			statusHtml = `<span class="meeting-status due">Due today</span>`;
		} else {
			const ov = Math.abs(daysToNext);
			statusHtml = `<span class="meeting-status overdue"><strong>${ov}</strong> day${ov !== 1 ? "s" : ""} overdue &mdash; expected ${fmtDisplay(nextDate)}</span>`;
		}

		const cycles = buildCycleInsights();
		const cycleRows = cycles.map(c => `
      <tr>
        <td>${fmtDisplay(c.start)} &rarr; ${fmtDisplay(c.end)}</td>
        <td>${c.met
				? `<span class="badge done">✓ met — ${fmtDisplay(c.metOn)}</span>`
				: c.ongoing
					? `<span class="badge">ongoing</span>`
					: `<span class="badge miss">✗ missed</span>`}
        </td>
        <td style="font-family:monospace;font-size:.7rem;color:var(--muted)">${c.met ? `day ${c.dayOfCycle}` : "—"}</td>
      </tr>
    `).join("");

		$("#meeting-panel-body").innerHTML = `
      <div id="meeting-status-row">
        <div>
          <span class="meeting-meta-lbl">Last meeting</span>
          <span class="meeting-meta-val">${lastDate ? fmtDisplay(lastDate) : "—"}</span>
        </div>
        <div>
          <span class="meeting-meta-lbl">Next expected</span>
          <span class="meeting-meta-val">${nextDate ? fmtDisplay(nextDate) : "—"}</span>
        </div>
        <div id="meeting-status-msg">${statusHtml}</div>
      </div>
      <div id="meeting-cycles">
        <h3>3-week cycles</h3>
        ${cycles.length === 0
				? `<p style="color:var(--muted);font-size:.88rem">Log at least one meeting to see cycle insights.</p>`
				: `<table class="meeting-cycle-table">
              <thead><tr><th>Cycle window</th><th>Status</th><th>When</th></tr></thead>
              <tbody>${cycleRows}</tbody>
            </table>`}
      </div>
    `;
	}

	function buildCycleInsights() {
		if (meetingLog.length === 0) return [];
		const dates = [...meetingLog].reverse().map(e => e.date);
		const today = isoDate();
		const cycles = [];
		let start = dates[0];
		while (start <= today) {
			const end = addDays(start, MEETING_CYCLE_DAYS - 1);
			const hit = dates.find(d => d >= start && d <= end);
			cycles.push({
				start, end,
				met: !!hit,
				metOn: hit || null,
				dayOfCycle: hit ? daysBetween(start, hit) + 1 : null,
				ongoing: end > today,
			});
			start = addDays(end, 1);
		}
		return cycles.reverse();
	}

	// ════════════════════════════════════════════════════════
	// KHATM TRACKER
	// Stored in wird_entries: wirdType="khatm", value=1.
	// notes field carries JSON: {"type":"tilawah"} or plain text.
	// ════════════════════════════════════════════════════════

	function getKhatmEntries() {
		return [...allEntries]
			.filter(e => e.wirdType === "khatm" && Number(e.value) >= 1)
			.sort((a, b) => a.date.localeCompare(b.date)); // asc for numbering
	}

	function renderKhatm() {
		const entries = getKhatmEntries();
		const total = entries.length;

		const thisYear = entries.filter(
			e => e.date.startsWith(String(new Date().getFullYear()))
		).length;

		let avgDays = "—";
		if (entries.length >= 2) {
			let gap = 0;
			for (let i = 1; i < entries.length; i++)
				gap += daysBetween(entries[i - 1].date, entries[i].date);
			avgDays = Math.round(gap / (entries.length - 1));
		}

		const last = entries[entries.length - 1];

		$("#khatm-total").textContent = total || "0";
		$("#khatm-this-year").textContent = thisYear || "0";
		$("#khatm-avg-days").textContent = avgDays;
		$("#khatm-last").textContent = last ? fmtDisplay(last.date) : "—";

		const listEl = $("#khatm-list");
		if (entries.length === 0) {
			listEl.innerHTML = `<p style="color:var(--muted);font-style:italic">No completions logged yet.</p>`;
			return;
		}

		// Render newest first
		const display = [...entries].reverse();
		listEl.innerHTML = display.map(e => {
			const num = entries.findIndex(x => x.id === e.id) + 1;
			const milestone = num % 10 === 0;
			const prevEntry = entries[num - 2]; // ascending, so num-2 is previous
			const gapTxt = prevEntry ? `+${daysBetween(prevEntry.date, e.date)}d` : "first";

			// Parse recitation type out of notes if stored as JSON
			let recType = "", noteText = e.notes || "";
			try {
				const parsed = JSON.parse(e.notes);
				recType = parsed.type || "";
				noteText = parsed.notes || "";
			} catch (_) { /* notes is plain text */ }

			const typeBadge = recType
				? `<span class="khatm-type-badge">${KHATM_TYPES[recType] || recType}</span>`
				: "";
			const note = noteText
				? `<span class="khatm-note">${noteText}</span>`
				: "";

			return `
        <div class="khatm-item${milestone ? " milestone" : ""}">
          <div class="khatm-num" title="${milestone ? "🎉 Milestone!" : `#${num}`}">${num}</div>
          <span class="khatm-date">${fmtDisplay(e.date)}</span>
          ${typeBadge}
          <span class="khatm-gap">${gapTxt}</span>
          ${note}
        </div>
      `;
		}).join("");
	}

	// Khatm modal
	const khatmModal = $("#khatm-modal");
	const khatmForm = $("#khatm-form");
	const khatmDateEl = $("#khatm-date");
	const khatmTypeEl = $("#khatm-recitation");
	const khatmNotesEl = $("#khatm-notes");

	$("#log-khatm-btn").addEventListener("click", () => {
		khatmForm.reset();
		khatmDateEl.value = isoDate();
		khatmModal.showModal();
	});
	$("#khatm-cancel").addEventListener("click", () => khatmModal.close());

	khatmForm.addEventListener("submit", async e => {
		e.preventDefault();
		// Pack recitation type + notes into the notes field as JSON
		const recType = khatmTypeEl.value || "";
		const noteText = khatmNotesEl.value.trim();
		const notes = (recType || noteText)
			? JSON.stringify({ type: recType || undefined, notes: noteText || undefined })
			: null;

		try {
			const saved = await postEntry({
				wirdType: "khatm",
				date: khatmDateEl.value,
				value: 1,
				notes,
			});
			allEntries.push(saved);
			khatmModal.close();
			renderKhatm();
			renderHeatmap();
		} catch (err) {
			alert("Error saving khatm: " + err.message);
		}
	});

	// ════════════════════════════════════════════════════════
	// HISTORY TABLE  (filter out nafl + khatm to keep it clean,
	// or you can remove the filter if you want them shown)
	// ════════════════════════════════════════════════════════

	function renderHistory() {
		const tbody = $("#history-body");
		tbody.innerHTML = "";

		const HIDE_TYPES = new Set(["shaykh_meeting", ...Object.keys(NAFL_WIRD), "khatm"]);
		const sorted = [...allEntries]
		      .filter(e => !HIDE_TYPES.has(e.wirdType))
		      .sort((a, b) => b.date.localeCompare(a.date))
	    	      .slice(0, 20);

		if (sorted.length === 0) {
			tbody.innerHTML = `<tr><td colspan="5" class="loading-cell">No entries yet.</td></tr>`;
			return;
		}

		for (const e of sorted) {
			const meta = WIRD_META[e.wirdType] || {};
			const cls = statusClass(e.wirdType, e.value);
			const badgeTxt = cls === "over" ? (meta.type === "rating" ? "flourishing" : "above")
				: cls === "done" ? (meta.type === "rating" ? "present" : "met")
					: cls === "miss" ? "below"
						: "—";
			const tr = document.createElement("tr");
			tr.innerHTML = `
        <td style="font-family:monospace;font-size:.75rem">${e.date}</td>
        <td>${meta.label || e.wirdType}</td>
        <td style="font-family:monospace;font-size:.82rem">${fmtValue(e.wirdType, e.value)}</td>
        <td><span class="badge ${cls}">${badgeTxt}</span></td>
        <td style="color:var(--muted);font-size:.82rem">${e.notes || ""}</td>
      `;
			tbody.appendChild(tr);
		}
	}

	// ════════════════════════════════════════════════════════
	// TREND CHART  (unchanged)
	// ════════════════════════════════════════════════════════

	function renderChart() {
		const type = $("#trend-type").value;
		const days = parseInt($("#trend-range").value);
		const meta = WIRD_META[type];
		const isRating = meta.type === "rating";
		const now = new Date();
		const labels = [];
		const vals = [];

		const isMotalah = type === "motalah";
		for (let i = days - 1; i >= 0; i--) {
			const d = new Date(now);
			d.setDate(d.getDate() - i);
			const iso = isoDate(d);
			labels.push(iso.slice(5));
			if (isMotalah) {
				vals.push(motalahEntries.filter(e => e.date === iso)
					.reduce((s, e) => s + Number(e.durationMinutes), 0));
			} else {
				const dayEntries = allEntries.filter(e => e.date === iso && e.wirdType === type);
				if (isRating) {
					const last = dayEntries.sort((a, b) => a.createdAt > b.createdAt ? 1 : -1).pop();
					vals.push(last ? Number(last.value) : 0);
				} else {
					vals.push(dayEntries.reduce((s, e) => s + Number(e.value), 0));
				}
			}
		}

		const nonZero = vals.filter(v => v > 0);
		const avg = nonZero.length
			? (isRating
				? (nonZero.reduce((a, b) => a + b, 0) / nonZero.length).toFixed(1)
				: Math.round(nonZero.reduce((a, b) => a + b, 0) / nonZero.length))
			: 0;
		const max = nonZero.length ? Math.max(...nonZero) : 0;
		const streak = calcStreak(type);

		if (isRating) {
			$("#trend-stats").innerHTML = `
        <div class="stat-chip"><span class="stat-val">${avg}</span><span class="stat-lbl">avg rating</span></div>
        <div class="stat-chip"><span class="stat-val">${RATING_LABELS[max] || "—"}</span><span class="stat-lbl">best day</span></div>
        <div class="stat-chip"><span class="stat-val">${streak}d</span><span class="stat-lbl">streak ≥ ${meta.target}</span></div>
        <div class="stat-chip"><span class="stat-val">${nonZero.length}</span><span class="stat-lbl">days logged</span></div>
      `;
		} else {
			$("#trend-stats").innerHTML = `
        <div class="stat-chip"><span class="stat-val">${Number(avg).toLocaleString()}</span><span class="stat-lbl">avg / day</span></div>
        <div class="stat-chip"><span class="stat-val">${Number(max).toLocaleString()}</span><span class="stat-lbl">best day</span></div>
        <div class="stat-chip"><span class="stat-val">${streak}d</span><span class="stat-lbl">current streak</span></div>
        <div class="stat-chip"><span class="stat-val">${meta.target.toLocaleString()}</span><span class="stat-lbl">daily target</span></div>
      `;
		}

		const cs = getComputedStyle(document.documentElement);
		const accent = cs.getPropertyValue("--wird-accent").trim() || "#7c5c3a";
		const gridColor = cs.getPropertyValue("--border").trim() || "#d7d7d7";
		const tickColor = cs.getPropertyValue("--muted").trim() || "#666";
		const ctx = $("#trend-chart").getContext("2d");
		if (chart) chart.destroy();

		chart = new Chart(ctx, {
			type: "line",
			data: {
				labels,
				datasets: [
					{
						label: meta.label,
						data: vals,
						borderColor: accent,
						backgroundColor: accent + "22",
						fill: true, tension: .35, pointRadius: 3, pointHoverRadius: 5,
					},
					{
						label: "Target",
						data: vals.map(() => meta.target),
						borderColor: "rgba(74,124,89,.5)",
						borderDash: [5, 4],
						pointRadius: 0, fill: false, tension: 0,
					},
				],
			},
			options: {
				responsive: true,
				maintainAspectRatio: true,
				plugins: {
					legend: { display: false },
					tooltip: {
						callbacks: {
							label: ctx => {
								const v = ctx.parsed.y;
								if (isRating && ctx.datasetIndex === 0)
									return `${meta.label}: ${RATING_LABELS[v] || v} (${v}/5)`;
								return `${ctx.dataset.label}: ${v}`;
							},
						},
					},
				},
				scales: {
					x: {
						grid: { color: gridColor + "22" },
						ticks: { color: tickColor, font: { family: "monospace", size: 10 }, maxTicksLimit: 10 },
					},
					y: {
						grid: { color: gridColor + "22" },
						ticks: {
							color: tickColor,
							font: { family: "monospace", size: 10 },
							...(isRating ? {
								min: 0, max: 5, stepSize: 1,
								callback: v => RATING_LABELS[v] || (v === 0 ? "" : v),
							} : {}),
						},
						beginAtZero: true,
						...(isRating ? { min: 0, max: 5 } : {}),
					},
				},
			},
		});
	}

	function calcStreak(type) {
		const meta = WIRD_META[type];
		let streak = 0;
		const now = new Date();
		for (let i = 0; i < 365; i++) {
			const d = new Date(now);
			d.setDate(d.getDate() - i);
			const iso = isoDate(d);
			let total;
			if (type === "motalah") {
				total = motalahEntries.filter(e => e.date === iso)
					.reduce((s, e) => s + Number(e.durationMinutes), 0);
			} else {
				const entries = allEntries.filter(e => e.date === iso && e.wirdType === type);
				if (meta && meta.type === "rating") {
					const last = entries.sort((a, b) => a.createdAt > b.createdAt ? 1 : -1).pop();
					total = last ? Number(last.value) : 0;
				} else {
					total = entries.reduce((s, e) => s + Number(e.value), 0);
				}
			}
			const target = type === "motalah" ? MOTALAH_TARGET : (meta ? meta.target : 1);
			if (total >= target) streak++;
			else if (i > 0) break;
		}
		return streak;
	}

	// ════════════════════════════════════════════════════════
	// HEATMAP
	// Reads allEntries directly. No extra API call.
	// ════════════════════════════════════════════════════════

	function renderHeatmap() {
		const type = $("#heatmap-type").value;
		const today = new Date();
		const todayIso = isoDate(today);

		// Anchor: Sunday on or before today, then go back (HEATMAP_WEEKS-1) full weeks
		const anchor = new Date(today);
		anchor.setDate(anchor.getDate() - anchor.getDay());
		const startDate = new Date(anchor);
		startDate.setDate(startDate.getDate() - (HEATMAP_WEEKS - 1) * 7);

		const scoreMap = buildHeatmapScores(type, isoDate(startDate), todayIso);

		// ── Month labels ──────────────────────────────────────
		const monthLabelEl = $("#heatmap-month-labels");
		monthLabelEl.innerHTML = "";
		let lastMonth = null;
		// 15px per col (12px cell + 3px gap); labels are position:absolute
		for (let w = 0; w < HEATMAP_WEEKS; w++) {
			const d = new Date(startDate);
			d.setDate(d.getDate() + w * 7);
			const mo = d.toLocaleDateString("en-GB", { month: "short" });
			if (mo !== lastMonth) {
				const lbl = document.createElement("span");
				lbl.className = "hm-month-lbl";
				lbl.style.left = w * 15 + "px";
				lbl.textContent = mo;
				monthLabelEl.appendChild(lbl);
				lastMonth = mo;
			}
		}

		// ── Grid ─────────────────────────────────────────────
		const gridEl = $("#heatmap-grid");
		gridEl.innerHTML = "";

		// Day-of-week label column
		const dowCol = document.createElement("div");
		dowCol.className = "hm-dow-col";
		["", "Mon", "", "Wed", "", "Fri", ""].forEach(lbl => {
			const el = document.createElement("div");
			el.className = "hm-dow-lbl";
			el.textContent = lbl;
			dowCol.appendChild(el);
		});
		gridEl.appendChild(dowCol);

		for (let w = 0; w < HEATMAP_WEEKS; w++) {
			const col = document.createElement("div");
			col.className = "hm-col";
			for (let d = 0; d < 7; d++) {
				const cellDate = new Date(startDate);
				cellDate.setDate(cellDate.getDate() + w * 7 + d);
				const iso = isoDate(cellDate);

				const cell = document.createElement("div");
				cell.className = "hm-cell";

				if (iso > todayIso) {
					cell.classList.add("hm-empty");
				} else {
					const score = scoreMap[iso] || 0;
					cell.dataset.level = scoreToLevel(score, type);
					if (iso === todayIso) cell.classList.add("hm-today");
					cell.title = buildTooltip(iso, score, type);
				}
				col.appendChild(cell);
			}
			gridEl.appendChild(col);
		}

		// ── Stats ─────────────────────────────────────────────
		renderHeatmapStats(scoreMap, type, isoDate(startDate), todayIso);
	}

	function buildHeatmapScores(type, startIso, endIso) {
		// Returns { "2025-01-14": score } where score is a 0–1+ fraction
		// (against the relevant target) so scoreToLevel() is consistent.
		const map = {};

		if (type === "__all__") {
			// Fraction of DAILY_WIRD targets met each day
			const dayCounts = {}; // { date: { type: total } }
			for (const e of allEntries) {
				if (e.date < startIso || e.date > endIso) continue;
				if (!DAILY_WIRD[e.wirdType]) continue;
				if (!dayCounts[e.date]) dayCounts[e.date] = {};
				const meta = DAILY_WIRD[e.wirdType];
				if (meta.type === "rating") {
					dayCounts[e.date][e.wirdType] = Math.max(
						dayCounts[e.date][e.wirdType] || 0, Number(e.value)
					);
				} else {
					dayCounts[e.date][e.wirdType] = (dayCounts[e.date][e.wirdType] || 0) + Number(e.value);
				}
			}
			const total = Object.keys(DAILY_WIRD).length;
			for (const [date, typeMap] of Object.entries(dayCounts)) {
				let met = 0;
				for (const [t, val] of Object.entries(typeMap)) {
					if (val >= DAILY_WIRD[t].target) met++;
				}
				map[date] = met / total;
			}
			return map;
		}

		// Nafl: fraction of 3 prayers done
		if (type === "nafl") {
			for (const e of allEntries) {
				if (!NAFL_WIRD[e.wirdType]) continue;
				if (e.date < startIso || e.date > endIso) continue;
				if (Number(e.value) >= 1) map[e.date] = (map[e.date] || 0) + (1 / 3);
			}
			return map;
		}

		// Khatm: binary
		if (type === "khatm") {
			for (const e of allEntries) {
				if (e.wirdType !== "khatm") continue;
				if (e.date < startIso || e.date > endIso) continue;
				if (Number(e.value) >= 1) map[e.date] = 1;
			}
			return map;
		}

		// Motalah: use motalahEntries, normalised against MOTALAH_TARGET
		if (type === "motalah") {
			for (const e of motalahEntries) {
				if (e.date < startIso || e.date > endIso) continue;
				map[e.date] = (map[e.date] || 0) + Number(e.durationMinutes);
			}
			for (const d of Object.keys(map)) map[d] = map[d] / MOTALAH_TARGET;
			return map;
		}

		// Single wird type — normalised against target
		const meta = WIRD_META[type];
		for (const e of allEntries) {
			if (e.wirdType !== type) continue;
			if (e.date < startIso || e.date > endIso) continue;
			if (meta && meta.type === "rating") {
				map[e.date] = Math.max(map[e.date] || 0, Number(e.value));
			} else {
				map[e.date] = (map[e.date] || 0) + Number(e.value);
			}
		}
		if (meta && meta.target) {
			for (const d of Object.keys(map)) map[d] = map[d] / meta.target;
		}
		return map;
	}

	function scoreToLevel(score, type) {
		if (type === "khatm") return score > 0 ? 4 : 0;
		if (score <= 0) return 0;
		if (score < 0.4) return 1;
		if (score < 0.75) return 2;
		if (score < 1.0) return 3;
		return 4; // met or exceeded
	}

	function buildTooltip(iso, score, type) {
		const label = fmtDisplay(iso);
		if (score <= 0) return `${label}: nothing logged`;
		if (type === "__all__") return `${label}: ${Math.round(score * 100)}% of targets met`;
		if (type === "nafl") return `${label}: ${Math.round(score * 3)} / 3 prayers`;
		if (type === "khatm") return `${label}: khatm completed ✓`;
		if (type === "motalah") return `${label}: ${Math.round(score * MOTALAH_TARGET)} min`;
		const meta = WIRD_META[type];
		if (!meta) return label;
		if (meta.type === "rating") {
			const r = Math.round(score);
			return `${label}: ${RATING_LABELS[r] || r}/5`;
		}
		const raw = score * (meta.target || 1);
		return `${label}: ${Math.round(raw).toLocaleString()} ${meta.unit}`;
	}

    // ============================================================
    // DAILY SUMMARY TABLE
    // ============================================================

    let summaryDays = 30;
    let summaryFilter = "__all__";

    function renderSummary() {
	const wrap = document.getElementById("summary-table-wrap");
	if (!wrap) return;

	const today = isoDate();
	const cols = summaryFilter === "__all__"
	      ? Object.keys(DAILY_WIRD)
	      : [summaryFilter];

	// Build date rows newest→oldest
	const rows = [];
	for (let i = 0; i < summaryDays; i++) {
	    const d = new Date();
	    d.setDate(d.getDate() - i);
	    rows.push(isoDate(d));
	}

	// Pre-aggregate entries into { date: { type: val } }
	const agg = {};
	for (const e of allEntries) {
	    if (!cols.includes(e.wirdType)) continue;
	    if (!agg[e.date]) agg[e.date] = {};
	    const meta = DAILY_WIRD[e.wirdType];
	    if (meta && meta.type === "rating") {
		agg[e.date][e.wirdType] = Math.max(
		    agg[e.date][e.wirdType] || 0, Number(e.value)
		);
	    } else {
		agg[e.date][e.wirdType] = (agg[e.date][e.wirdType] || 0) + Number(e.value);
	    }
	}

	// Build header
	const thCols = cols.map(t => {
	    const meta = DAILY_WIRD[t];
	    const unitHint = meta.type === "rating" ? "/5" : meta.unit ? meta.unit : "";
	    return `<th title="${meta.label} · target: ${meta.target}${unitHint}">
      ${meta.label}<br>
      <span style="font-weight:400;opacity:.6">/ ${meta.target}${unitHint}</span>
    </th>`;
	}).join("");

	// Build body rows
	const bodyRows = rows.map(dateIso => {
	    const isToday = dateIso === today;
	    const dayData = agg[dateIso] || {};

	    const displayDate = new Date(dateIso + "T00:00:00").toLocaleDateString("en-GB", {
		weekday: "short", day: "numeric", month: "short"
	    });

	    const cells = cols.map(type => {
		const meta = DAILY_WIRD[type];
		const val = dayData[type];

		if (val === undefined || val === null) {
		    return `<td class="summary-cell-empty">—</td>`;
		}

		const cls = statusClass(type, val);
		const cellCls = cls === "over" ? "summary-cell-over"
		      : cls === "done" ? "summary-cell-done"
		      : cls === "miss" ? "summary-cell-miss"
		      : "";

		let display;
		if (meta.type === "rating") {
		    display = `${val}<span style="opacity:.45">/5</span>`;
		} else {
		    display = Number(val).toLocaleString();
		}

		return `<td class="${cellCls}">${display}</td>`;
	    }).join("");

	    return `<tr class="${isToday ? "summary-today" : ""}">
      <td>${displayDate}${isToday ? " ·" : ""}</td>
      ${cells}
    </tr>`;
	}).join("");

	// Footer: days target met per column
	const footCells = cols.map(type => {
	    const meta = DAILY_WIRD[type];
	    let metCount = 0;
	    for (const dateIso of rows) {
		const val = (agg[dateIso] || {})[type];
		if (val !== undefined && val >= meta.target) metCount++;
	    }
	    const pct = Math.round((metCount / summaryDays) * 100);
	    const cls = pct >= 80 ? "summary-cell-done" : pct >= 50 ? "" : "summary-cell-miss";
	    return `<td class="summary-tfoot ${cls}">${metCount}d <span style="opacity:.55">(${pct}%)</span></td>`;
	}).join("");

	wrap.innerHTML = `
    <table id="summary-table">
      <thead>
        <tr>
          <th>Date</th>
          ${thCols}
        </tr>
      </thead>
      <tbody>${bodyRows}</tbody>
      <tfoot>
        <tr>
          <td class="summary-tfoot" style="color:var(--muted)">days met</td>
          ${footCells}
        </tr>
      </tfoot>
    </table>
  `;
    }

    // Controls
    document.querySelectorAll(".summary-preset").forEach(btn => {
	btn.addEventListener("click", () => {
	    summaryDays = parseInt(btn.dataset.days);
	    document.querySelectorAll(".summary-preset").forEach(b => b.classList.remove("active"));
	    btn.classList.add("active");
	    renderSummary();
	});
    });

    const summaryFilterEl = document.getElementById("summary-filter");
    if (summaryFilterEl) {
	summaryFilterEl.addEventListener("change", () => {
	    summaryFilter = summaryFilterEl.value;
	    renderSummary();
	});
    }
    
	function renderHeatmapStats(scoreMap, type, startIso, endIso) {
		const el = $("#heatmap-stats");
		const daysLogged = Object.values(scoreMap).filter(v => v > 0).length;
		const daysTarget = Object.values(scoreMap).filter(v => v >= 1).length;

		// Longest streak of any-activity
		let longest = 0, cur = 0;
		const s = new Date(startIso), e = new Date(endIso);
		for (let d = new Date(s); d <= e; d.setDate(d.getDate() + 1)) {
			if ((scoreMap[isoDate(d)] || 0) > 0) { cur++; longest = Math.max(longest, cur); }
			else cur = 0;
		}

		const chips = [
			{ val: daysLogged, lbl: "days with activity" },
			{ val: `${longest}d`, lbl: "longest streak" },
		];
		if (type !== "khatm") {
			chips.push({ val: daysTarget, lbl: type === "nafl" ? "days all prayers done" : "days target met" });
		}

		el.innerHTML = chips.map(c =>
			`<div class="stat-chip">
        <span class="stat-val">${c.val}</span>
        <span class="stat-lbl">${c.lbl}</span>
       </div>`
		).join("");
	}

	// ════════════════════════════════════════════════════════
	// BIND CONTROLS
	// ════════════════════════════════════════════════════════

	function bindControls() {
		$("#open-log-btn").addEventListener("click", () => openModal());
		$("#log-meeting-btn").addEventListener("click", openMeetingModal);
		$("#trend-type").addEventListener("change", renderChart);
		$("#trend-range").addEventListener("change", renderChart);
		$("#heatmap-type").addEventListener("change", renderHeatmap);
	}
  /* ============================================================
  wird-export.js
  Client-side PDF export for the Wird Tracker.
  Depends on: jsPDF + jspdf-autotable (loaded via CDN).
  All data is read from allEntries / motalahEntries already
  held in memory by wird-tracker.js — no extra API calls.

   HOW TO INTEGRATE
   ─────────────────
   1. Paste this block INSIDE the existing IIFE in
   wird-tracker.js, just before the closing })();

   2. Add the HTML snippet (wird-export-html.html) to the
   template and the CDN <script> tags after chart.js.
   ============================================================ */

  // ── Export modal elements ─────────────────────────────────
  const exportModal     = document.getElementById("export-modal");
  const exportForm      = document.getElementById("export-form");
  const exportFromEl    = document.getElementById("export-from");
  const exportToEl      = document.getElementById("export-to");
  const exportBtnLabel  = document.getElementById("export-btn-label");
  const exportSubmitBtn = document.getElementById("export-submit");

  // ── Open / close ──────────────────────────────────────────
  document.getElementById("export-pdf-btn").addEventListener("click", () => {
    const today = isoDate();
    const thirtyDaysAgo = (() => {
      const d = new Date(); d.setDate(d.getDate() - 30); return isoDate(d);
    })();
    exportFromEl.value = thirtyDaysAgo;
    exportToEl.value   = today;
    exportModal.showModal();
  });

  document.getElementById("export-cancel").addEventListener("click", () => exportModal.close());

  // ── Preset buttons ────────────────────────────────────────
  document.querySelectorAll(".export-preset").forEach(btn => {
    btn.addEventListener("click", () => {
      const days = parseInt(btn.dataset.preset);
      const to   = new Date();
      const from = new Date();

      if (days === 365) {
        // "This year" = Jan 1 of current year
        from.setMonth(0, 1);
      } else {
        from.setDate(from.getDate() - (days - 1));
      }

      exportFromEl.value = isoDate(from);
      exportToEl.value   = isoDate(to);

      // Visual feedback on active preset
      document.querySelectorAll(".export-preset").forEach(b => b.classList.remove("active"));
      btn.classList.add("active");
    });
  });

  // Clear active preset when user manually changes dates
  [exportFromEl, exportToEl].forEach(el => {
    el.addEventListener("change", () => {
      document.querySelectorAll(".export-preset").forEach(b => b.classList.remove("active"));
    });
  });

  // ── Generate PDF ──────────────────────────────────────────
  exportForm.addEventListener("submit", async e => {
    e.preventDefault();

    const fromIso = exportFromEl.value;
    const toIso   = exportToEl.value;

    if (!fromIso || !toIso || fromIso > toIso) {
      alert("Please select a valid date range.");
      return;
    }

    const includeNafl    = document.getElementById("export-include-nafl").checked;
    const includeKhatm   = document.getElementById("export-include-khatm").checked;
    const includeMotalah = document.getElementById("export-include-motalah").checked;

    // Loading state
    exportSubmitBtn.disabled = true;
    exportBtnLabel.textContent = "Generating…";

    try {
      await generatePDF({ fromIso, toIso, includeNafl, includeKhatm, includeMotalah });
      exportModal.close();
    } catch (err) {
      console.error("PDF export error:", err);
      alert("Could not generate PDF: " + err.message);
    } finally {
      exportSubmitBtn.disabled = false;
      exportBtnLabel.textContent = "Download PDF";
    }
  });

  // ── Core PDF generation ───────────────────────────────────
  async function generatePDF({ fromIso, toIso, includeNafl, includeKhatm, includeMotalah }) {
    const { jsPDF } = window.jspdf;
    const doc = new jsPDF({ orientation: "portrait", unit: "mm", format: "a4" });

    const PAGE_W  = 210;
    const MARGIN  = 16;
    const COL_W   = PAGE_W - MARGIN * 2;
    const ACCENT  = [124, 92, 58];    // --wird-accent (light mode safe)
    const MUTED   = [130, 130, 130];
    const FG      = [30, 30, 30];
    const BORDER  = [220, 215, 208];

    // ── Cover / header ─────────────────────────────────────
    doc.setFillColor(...ACCENT);
    doc.rect(0, 0, PAGE_W, 28, "F");

    doc.setFont("helvetica", "bold");
    doc.setFontSize(16);
    doc.setTextColor(255, 255, 255);
    doc.text("Wird Tracker — Export", MARGIN, 16);

    doc.setFont("helvetica", "normal");
    doc.setFontSize(8);
    doc.setTextColor(255, 255, 255, 0.75);
    doc.text(
      `${fmtDisplay(fromIso)}  →  ${fmtDisplay(toIso)}   ·   generated ${fmtDisplay(isoDate())}`,
      MARGIN, 23
    );

    let cursorY = 38;

    // ── Helper: section heading ────────────────────────────
    function sectionHeading(title, y) {
      doc.setFillColor(...BORDER);
      doc.rect(MARGIN, y, COL_W, 7, "F");
      doc.setFont("helvetica", "bold");
      doc.setFontSize(8.5);
      doc.setTextColor(...ACCENT);
      doc.text(title.toUpperCase(), MARGIN + 3, y + 5);
      return y + 10;
    }

    // ── Helper: summary stat row ───────────────────────────
    function statRow(label, value, y) {
      doc.setFont("helvetica", "normal");
      doc.setFontSize(8.5);
      doc.setTextColor(...MUTED);
      doc.text(label, MARGIN + 3, y);
      doc.setFont("helvetica", "bold");
      doc.setTextColor(...FG);
      doc.text(String(value), MARGIN + 60, y);
      return y + 6;
    }

    // ── Helper: autoTable wrapper ──────────────────────────
    function addTable(columns, rows, startY) {
      doc.autoTable({
        startY,
        head: [columns.map(c => c.header)],
        body: rows,
        margin: { left: MARGIN, right: MARGIN },
        styles: {
          font:      "helvetica",
          fontSize:  8,
          cellPadding: 2.5,
          textColor: FG,
          lineColor: BORDER,
          lineWidth: 0.2,
          overflow:  "linebreak",
        },
        headStyles: {
          fillColor:  BORDER,
          textColor:  ACCENT,
          fontStyle:  "bold",
          fontSize:   7.5,
        },
        alternateRowStyles: { fillColor: [250, 248, 245] },
        columnStyles: columns.reduce((acc, c, i) => {
          if (c.width) acc[i] = { cellWidth: c.width };
          return acc;
        }, {}),
      });
      return doc.lastAutoTable.finalY + 8;
    }

    // ── 1. Daily Wirds summary ─────────────────────────────
    cursorY = sectionHeading("Daily Awrād", cursorY);

    // Build per-day totals for the range
    const dayMap = {}; // { "2025-01-14": { durood: 300, istighfar: 100, ... } }
    for (const e of allEntries) {
      if (e.date < fromIso || e.date > toIso) continue;
      if (!WIRD_META[e.wirdType]) continue;
      if (!includeNafl && NAFL_WIRD[e.wirdType]) continue;
      if (!includeKhatm && e.wirdType === "khatm") continue;
      if (!dayMap[e.date]) dayMap[e.date] = {};
      const meta = WIRD_META[e.wirdType];
      if (meta && meta.type === "rating") {
        dayMap[e.date][e.wirdType] = Math.max(
          dayMap[e.date][e.wirdType] || 0, Number(e.value)
        );
      } else {
        dayMap[e.date][e.wirdType] = (dayMap[e.date][e.wirdType] || 0) + Number(e.value);
      }
    }

    // Build summary stats
    const daysInRange = Object.keys(dayMap).length;
    const dailyTypes  = Object.keys(DAILY_WIRD);
    const daysAllMet  = Object.values(dayMap).filter(dm =>
      dailyTypes.every(t => (dm[t] || 0) >= DAILY_WIRD[t].target)
    ).length;

    cursorY = statRow("Date range",       `${fmtDisplay(fromIso)} → ${fmtDisplay(toIso)}`, cursorY);
    cursorY = statRow("Days with entries", daysInRange, cursorY);
    cursorY = statRow("Days all targets met", daysAllMet, cursorY);
    cursorY += 3;

    // Per-wird totals table
    const summaryRows = dailyTypes.map(type => {
      const meta   = DAILY_WIRD[type];
      const values = Object.values(dayMap).map(dm => dm[type] || 0).filter(v => v > 0);
      const total  = meta.type === "rating"
        ? (values.length ? (values.reduce((a,b)=>a+b,0)/values.length).toFixed(1) + " avg" : "—")
        : values.reduce((a,b)=>a+b,0).toLocaleString();
      const days   = values.length;
      const target = meta.type === "rating"
        ? `≥ ${meta.target} / 5`
        : `${meta.target.toLocaleString()} ${meta.unit}`;
      const metDays = meta.type === "rating"
        ? values.filter(v => v >= meta.target).length
        : values.filter(v => v >= meta.target).length;
      return [meta.label, total, target, `${metDays} / ${days}`, days ? `${Math.round(metDays/days*100)}%` : "—"];
    });

    cursorY = addTable(
      [
        { header: "Wird",        width: 42 },
        { header: "Total / Avg", width: 30 },
        { header: "Target",      width: 35 },
        { header: "Days Met",    width: 25 },
        { header: "Hit %",       width: 20 },
      ],
      summaryRows,
      cursorY
    );

    // ── 2. Detailed daily log ──────────────────────────────
    if (doc.lastAutoTable.finalY > 200) {
      doc.addPage();
      cursorY = 18;
    }

    cursorY = sectionHeading("Detailed Daily Log", cursorY);

    const logEntries = [...allEntries]
      .filter(e => {
      if (e.date < fromIso || e.date > toIso) return false;
      if (!WIRD_META[e.wirdType]) return false;
      if (!includeNafl && NAFL_WIRD[e.wirdType]) return false;
      if (!includeKhatm && e.wirdType === "khatm") return false;
      return true;
    })
      .sort((a, b) => b.date.localeCompare(a.date));

    const HIDE_IN_LOG = new Set();
    if (!includeNafl) Object.keys(NAFL_WIRD).forEach(k => HIDE_IN_LOG.add(k));
    if (!includeKhatm) HIDE_IN_LOG.add("khatm");

    const logRows = logEntries
      .filter(e => !HIDE_IN_LOG.has(e.wirdType))
      .map(e => {
      const meta = WIRD_META[e.wirdType];
      const cls  = statusClass(e.wirdType, Number(e.value));
      const vs   = cls === "over" ? "above" : cls === "done" ? "met" : cls === "miss" ? "below" : "—";
      return [
        e.date,
        meta ? meta.label : e.wirdType,
        fmtValue(e.wirdType, e.value),
        vs,
        e.notes ? e.notes.replace(/\n/g, " ").slice(0, 60) : "",
      ];
    });

    cursorY = addTable(
      [
        { header: "Date",       width: 22 },
        { header: "Wird",       width: 40 },
        { header: "Amount",     width: 35 },
        { header: "vs Target",  width: 22 },
        { header: "Notes",      width: null },
      ],
      logRows.length ? logRows : [["—", "No entries in range", "", "", ""]],
      cursorY
    );

    // ── 3. Nafl section ────────────────────────────────────
    if (includeNafl) {
      if (doc.lastAutoTable.finalY > 200) { doc.addPage(); cursorY = 18; }
      else { cursorY = doc.lastAutoTable.finalY + 6; }

      cursorY = sectionHeading("Nafl Prayers", cursorY);

      const naflRows = allEntries
        .filter(e => NAFL_WIRD[e.wirdType] && e.date >= fromIso && e.date <= toIso && Number(e.value) >= 1)
        .sort((a,b) => b.date.localeCompare(a.date))
        .map(e => [e.date, NAFL_WIRD[e.wirdType]?.label || e.wirdType, "✓ prayed"]);

      cursorY = addTable(
        [
          { header: "Date",   width: 28 },
          { header: "Prayer", width: 60 },
          { header: "Status", width: 30 },
        ],
        naflRows.length ? naflRows : [["—", "No nafl prayers in range", ""]],
        cursorY
      );
    }

    // ── 4. Khatm section ───────────────────────────────────
    if (includeKhatm) {
      if (doc.lastAutoTable.finalY > 200) { doc.addPage(); cursorY = 18; }
      else { cursorY = doc.lastAutoTable.finalY + 6; }

      cursorY = sectionHeading("Khatm — Qurʾān Completions", cursorY);

      const khatmEntries = allEntries
        .filter(e => e.wirdType === "khatm" && e.date >= fromIso && e.date <= toIso && Number(e.value) >= 1)
        .sort((a,b) => b.date.localeCompare(a.date));

      const khatmRows = khatmEntries.map(e => {
        let recType = "", noteText = e.notes || "";
        try {
          const parsed = JSON.parse(e.notes);
          recType  = KHATM_TYPES[parsed.type] || parsed.type || "";
          noteText = parsed.notes || "";
        } catch (_) {}
        return [e.date, recType || "—", noteText.slice(0, 60) || "—"];
      });

      cursorY = addTable(
        [
          { header: "Date",       width: 28 },
          { header: "Type",       width: 40 },
          { header: "Notes",      width: null },
        ],
        khatmRows.length ? khatmRows : [["—", "No khatms in range", ""]],
        cursorY
      );
    }

    // ── 5. Mutālaʿah section ──────────────────────────────
    if (includeMotalah) {
      if (doc.lastAutoTable.finalY > 200) { doc.addPage(); cursorY = 18; }
      else { cursorY = doc.lastAutoTable.finalY + 6; }

      cursorY = sectionHeading("Mutālaʿah — Study Sessions", cursorY);

      const mInRange = motalahEntries
        .filter(e => e.date >= fromIso && e.date <= toIso)
        .sort((a,b) => b.date.localeCompare(a.date));

      const totalMins = mInRange.reduce((s,e) => s + Number(e.durationMinutes), 0);
      cursorY = statRow("Sessions", mInRange.length, cursorY);
      cursorY = statRow("Total study time", `${totalMins} min  (${(totalMins/60).toFixed(1)} hrs)`, cursorY);
      cursorY += 3;

      const motalahRows = mInRange.map(e => {
        const bookList = (e.bookIds || [])
          .map(id => {
          const b = calibreBooks.find(b => String(b.id) === String(id));
          return b ? b.title : String(id);
        }).join(", ");
        return [
          e.date,
          `${e.durationMinutes} min`,
          bookList.slice(0, 50) || "—",
          (e.notes || "").slice(0, 50) || "—",
        ];
      });

      cursorY = addTable(
        [
          { header: "Date",      width: 22 },
          { header: "Duration",  width: 22 },
          { header: "Books",     width: 70 },
          { header: "Notes",     width: null },
        ],
        motalahRows.length ? motalahRows : [["—", "No sessions in range", "", ""]],
        cursorY
      );
    }

    // ── Footer on every page ──────────────────────────────
    const pageCount = doc.internal.getNumberOfPages();
    for (let i = 1; i <= pageCount; i++) {
      doc.setPage(i);
      doc.setFont("helvetica", "normal");
      doc.setFontSize(7);
      doc.setTextColor(...MUTED);
      doc.text(
        `Wird Tracker  ·  Page ${i} of ${pageCount}`,
        PAGE_W / 2, 292,
        { align: "center" }
      );
    }

    // ── Save ───────────────────────────────────────────────
    const filename = `wird-${fromIso}-to-${toIso}.pdf`;
    doc.save(filename);
  }
})();
