/* Attendance.jsx -- CR #279.

   Public-facing absence form for parents. Three steps:
     1. Pick the camp.
     2. Type the child's first + last name (matches CR #276 last-name rule).
     3. Tick the weekdays the child will be absent on a 6-week calendar
        rendered from the camp's start_date / end_date window.

   Submits to /api/attendance (POST). Each (camp_id, child_name_lower,
   absent_date) is idempotent server-side, so a parent who submits the
   form twice for the same week never creates duplicates.

   No auth: this is parent-facing. The matching admin read panel lives
   in the dashboard. */

function ParentAttendancePage() {
  const [camps, setCamps] = React.useState([]);
  const [loadingCamps, setLoadingCamps] = React.useState(true);
  const [campsError, setCampsError] = React.useState("");
  const [step, setStep] = React.useState(1);
  const [campId, setCampId] = React.useState("");
  const [childName, setChildName] = React.useState("");
  const [parentEmail, setParentEmail] = React.useState("");
  const [absentDates, setAbsentDates] = React.useState({}); // { "YYYY-MM-DD": true }
  const [submitting, setSubmitting] = React.useState(false);
  const [submitted, setSubmitted] = React.useState(null);
  const [error, setError] = React.useState("");

  React.useEffect(() => {
    let cancelled = false;
    fetch("/api/camps")
      .then(r => r.json())
      .then(j => {
        if (cancelled) return;
        setCamps(j.rows || []);
        setLoadingCamps(false);
      })
      .catch(() => {
        if (cancelled) return;
        setCampsError("Couldn't load the camp list. Refresh to try again or email Joseph.J.Pospiech@phila.gov.");
        setLoadingCamps(false);
      });
    return () => { cancelled = true; };
  }, []);

  const selectedCamp = React.useMemo(
    () => camps.find(c => String(c.id) === String(campId)) || null,
    [camps, campId]
  );

  const weekDays = React.useMemo(() => buildWeekdayGrid(selectedCamp), [selectedCamp]);

  const nameTokens = childName.trim().split(/\s+/).filter(Boolean);
  const canStep1 = !!campId;
  const canStep2 = nameTokens.length >= 2;
  const selectedDateCount = Object.values(absentDates).filter(Boolean).length;
  const canSubmit = canStep1 && canStep2 && selectedDateCount > 0 && !submitting;

  const toggleDate = (iso) => setAbsentDates(d => ({ ...d, [iso]: !d[iso] }));

  const submit = async () => {
    setError("");
    setSubmitting(true);
    try {
      const dates = Object.entries(absentDates).filter(([, v]) => v).map(([k]) => k);
      const res = await fetch("/api/attendance", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          camp_id: Number(campId),
          child_name: childName.trim(),
          parent_email: parentEmail.trim() || undefined,
          absent_dates: dates,
        }),
      });
      const json = await res.json().catch(() => ({}));
      if (!res.ok || !json.ok) {
        throw new Error(json.error || `error_${res.status}`);
      }
      setSubmitted({ inserted: json.inserted, total: dates.length, camp: selectedCamp });
    } catch (err) {
      setError(`Couldn't submit: ${err.message}. Try again, or email Joseph.J.Pospiech@phila.gov.`);
    } finally {
      setSubmitting(false);
    }
  };

  const reset = () => {
    setSubmitted(null);
    setStep(1);
    setCampId("");
    setChildName("");
    setParentEmail("");
    setAbsentDates({});
    setError("");
  };

  if (submitted) {
    return (
      <window.ConfirmationView
        title="Got it. Thank you."
        body={(
          <span>
            We recorded {submitted.inserted} absence{submitted.inserted === 1 ? "" : "s"} for {childName.trim()} at {submitted.camp?.name || "the selected camp"}.
            {submitted.inserted !== submitted.total && (
              <> {submitted.total - submitted.inserted} date{(submitted.total - submitted.inserted) === 1 ? " was" : "s were"} already on file.</>
            )}
          </span>
        )}
        ctaLabel="Submit another absence"
        onCta={reset}
        secondary={{ label: "Back to home", onClick: () => { window.location.hash = "#home"; } }}
      />
    );
  }

  return (
    <div className="page">
      <window.FormHero
        eyebrow="Camp attendance"
        title="Letting us know about an absence"
        body="If your child will miss a day of camp, please let us know in advance. Pick the camp, give us your child's first and last name, then tick the days they won't be there. The coordinator gets a heads-up so the day's roster is accurate."
        bg="var(--c-cream)"
      />

      <div className="container" style={{ paddingTop: 28, paddingBottom: 56 }}>
        <window.FormSection number={step} title={STEP_TITLES[step - 1]}>
          {step === 1 && (
            <CampPicker
              camps={camps}
              loading={loadingCamps}
              error={campsError}
              value={campId}
              onChange={setCampId}
            />
          )}

          {step === 2 && (
            <div style={{ display: "grid", gap: 16 }}>
              <window.Field
                label="Camper's first and last name"
                value={childName}
                onChange={setChildName}
                required
                pattern="^\s*\S+\s+\S.*$"
                title="Please enter both a first and last name."
              />
              <window.Field
                label="Parent / guardian email (optional)"
                type="email"
                value={parentEmail}
                onChange={setParentEmail}
                placeholder="So we can reach out if anything looks off"
              />
            </div>
          )}

          {step === 3 && selectedCamp && (
            <AttendanceCalendar
              camp={selectedCamp}
              weekDays={weekDays}
              absentDates={absentDates}
              onToggle={toggleDate}
              selectedCount={selectedDateCount}
            />
          )}

          {step === 3 && !selectedCamp && (
            <p style={{ color: "var(--c-ink-soft)", fontSize: 14 }}>
              Pick a camp on step 1 first.
            </p>
          )}
        </window.FormSection>

        {error && (
          <div style={{
            marginTop: 16, padding: 14,
            border: "2px solid var(--c-primary)", borderRadius: 12,
            background: "var(--c-cream-deep)", color: "var(--c-ink)",
            fontSize: 14, lineHeight: 1.5,
          }}>
            {error}
          </div>
        )}

        <div style={{
          marginTop: 24, display: "flex", justifyContent: "space-between",
          gap: 12, flexWrap: "wrap",
        }}>
          {step > 1 ? (
            <button className="btn secondary small" type="button" onClick={() => setStep(step - 1)} disabled={submitting}>
              ← Back
            </button>
          ) : <span />}

          {step < 3 ? (
            <button
              className="btn small"
              type="button"
              onClick={() => setStep(step + 1)}
              disabled={(step === 1 && !canStep1) || (step === 2 && !canStep2)}
            >
              Continue →
            </button>
          ) : (
            <button
              className="btn small"
              type="button"
              onClick={submit}
              disabled={!canSubmit}
            >
              {submitting ? "Submitting…" : `Submit absence${selectedDateCount === 1 ? "" : "s"} ✓`}
            </button>
          )}
        </div>
      </div>
    </div>
  );
}

const STEP_TITLES = ["Pick the camp", "Who's the camper?", "Pick the days they'll miss"];

function CampPicker({ camps, loading, error, value, onChange }) {
  if (loading) {
    return <p style={{ color: "var(--c-ink-soft)", fontSize: 14 }}>Loading camps…</p>;
  }
  if (error) {
    return <p style={{ color: "var(--c-primary-deep)", fontSize: 14 }}>{error}</p>;
  }
  if (!camps.length) {
    return <p style={{ color: "var(--c-ink-soft)", fontSize: 14 }}>No active camps right now.</p>;
  }
  return (
    <div style={{ display: "grid", gap: 10 }}>
      {camps.map(c => {
        const isSelected = String(c.id) === String(value);
        return (
          <button
            key={c.id}
            type="button"
            onClick={() => onChange(String(c.id))}
            style={{
              appearance: "none",
              textAlign: "left",
              padding: "16px 18px",
              borderRadius: 14,
              background: isSelected ? "var(--c-ink)" : "var(--c-paper)",
              color: isSelected ? "var(--c-paper)" : "var(--c-ink)",
              border: "2px solid var(--c-ink)",
              cursor: "pointer",
              boxShadow: isSelected ? "3px 3px 0 var(--c-ink)" : "none",
              transition: "transform 120ms ease, box-shadow 120ms ease",
              font: "inherit",
            }}
          >
            <div style={{ fontFamily: "var(--f-display)", fontWeight: 700, fontSize: 18 }}>{c.name}</div>
            <div style={{ fontFamily: "var(--f-mono)", fontSize: 11, letterSpacing: "0.08em", textTransform: "uppercase", marginTop: 4, opacity: 0.85 }}>
              {c.region} &middot; {formatDateRange(c.start_date, c.end_date)}
            </div>
          </button>
        );
      })}
    </div>
  );
}

function AttendanceCalendar({ camp, weekDays, absentDates, onToggle, selectedCount }) {
  if (!weekDays.length) {
    return <p style={{ color: "var(--c-ink-soft)", fontSize: 14 }}>This camp doesn't have a date window we can render. Email the coordinator.</p>;
  }
  // CR #289: group by Monday boundary, not by index. The old "slice every 5"
  // approach laid the first day of camp into column 1 regardless of its
  // actual day-of-week, so YPTC (starts Tue June 30) showed June 30 in the
  // "Mon" column and pushed the following Monday into the "Fri" column.
  // Now: a new row starts whenever we see a Monday (or at the very first
  // day if camp starts mid-week). Each cell uses gridColumnStart on its own
  // dow to land in the right Mon-Fri column.
  const weeks = [];
  let curRow = [];
  for (const d of weekDays) {
    if (d.dow === 1 && curRow.length > 0) {
      weeks.push(curRow);
      curRow = [];
    }
    curRow.push(d);
  }
  if (curRow.length) weeks.push(curRow);

  // CR #284 + #285: tint cells whose iso matches a special date on the camp
  // (show days, off-site trips, early dismissals). JSONB column from the
  // camps table comes back as either a parsed array or, defensively, a JSON
  // string -- handle both.
  const specialByIso = React.useMemo(() => {
    let list = camp?.special_dates;
    if (typeof list === "string") {
      try { list = JSON.parse(list); } catch { list = []; }
    }
    const out = {};
    for (const s of (Array.isArray(list) ? list : [])) {
      if (s && s.date) out[s.date] = s;
    }
    return out;
  }, [camp]);

  return (
    <div>
      <p style={{ color: "var(--c-ink-soft)", fontSize: 14, lineHeight: 1.55, margin: 0, marginBottom: 14 }}>
        {camp.name} runs Mon&ndash;Fri from <strong>{formatDay(camp.start_date)}</strong> through <strong>{formatDay(camp.end_date)}</strong>.
        Tap each weekday your child will be out. You can come back and submit more later.
      </p>

      <div className="attend-calendar">
        <div className="attend-header">
          <span>Mon</span><span>Tue</span><span>Wed</span><span>Thu</span><span>Fri</span>
        </div>
        {weeks.map((week, wi) => (
          <div className="attend-row" key={wi}>
            {week.map(d => {
              const sel = !!absentDates[d.iso];
              const special = specialByIso[d.iso];
              const classes = "attend-cell"
                + (sel ? " is-selected" : "")
                + (special ? " is-special" : "");
              return (
                <button
                  key={d.iso}
                  type="button"
                  className={classes}
                  style={{ gridColumnStart: d.dow }}
                  onClick={() => onToggle(d.iso)}
                  aria-pressed={sel}
                  aria-label={special ? `${d.dayNum} ${d.shortMo} — ${special.label}` : `${d.dayNum} ${d.shortMo}`}
                  title={special ? special.label : undefined}
                >
                  <span className="attend-day-num">{d.dayNum}</span>
                  <span className="attend-day-mo">{d.shortMo}</span>
                  {special && (
                    <span className="attend-special-label">{special.label}</span>
                  )}
                  {sel && <span className="attend-mark" aria-hidden="true">&#10003;</span>}
                </button>
              );
            })}
          </div>
        ))}
      </div>

      <p style={{ marginTop: 14, fontFamily: "var(--f-mono)", fontSize: 11.5, letterSpacing: "0.08em", textTransform: "uppercase", color: "var(--c-ink-soft)" }}>
        {selectedCount === 0 ? "No days selected yet" : `${selectedCount} day${selectedCount === 1 ? "" : "s"} selected`}
      </p>

      <style>{`
        .attend-calendar {
          border: 2px solid var(--c-ink);
          border-radius: 14px;
          overflow: hidden;
          background: var(--c-paper);
        }
        .attend-header {
          display: grid;
          grid-template-columns: repeat(5, 1fr);
          background: var(--c-cream-deep);
          font-family: var(--f-mono);
          font-size: 11px;
          letter-spacing: 0.1em;
          text-transform: uppercase;
          color: var(--c-ink-soft);
          border-bottom: 2px solid var(--c-ink);
        }
        .attend-header span {
          padding: 10px 8px;
          text-align: center;
        }
        .attend-row {
          display: grid;
          grid-template-columns: repeat(5, 1fr);
          border-top: 1.5px solid var(--c-line, #d8d2c8);
        }
        .attend-row:first-child { border-top: none; }
        .attend-cell {
          position: relative;
          appearance: none;
          background: var(--c-paper);
          border: none;
          border-right: 1.5px solid var(--c-line, #d8d2c8);
          cursor: pointer;
          padding: 12px 6px;
          font: inherit;
          color: var(--c-ink);
          display: flex; flex-direction: column; align-items: center; gap: 2px;
          min-height: 84px;
          transition: background 120ms ease;
        }
        .attend-cell:last-child { border-right: none; }
        .attend-cell:hover { background: var(--c-cream-deep); }
        .attend-cell.is-special {
          background: #fde4c8; /* light orange — JJ's pick for show/trip/early-dismissal days */
        }
        .attend-cell.is-special:hover { background: #fbd1a4; }
        .attend-cell.is-selected {
          background: var(--c-ink);
          color: var(--c-paper);
        }
        .attend-cell.is-special.is-selected {
          /* When a special-day cell is also marked absent, keep the dark selection
             background so the check mark is legible, but show a thin orange edge
             so the special status is still readable at a glance. */
          background: var(--c-ink);
          color: var(--c-paper);
          box-shadow: inset 0 0 0 3px #fde4c8;
        }
        .attend-special-label {
          font-family: var(--f-mono);
          font-size: 9px;
          line-height: 1.25;
          letter-spacing: 0.04em;
          text-transform: uppercase;
          color: #8b4513;
          margin-top: 4px;
          padding: 0 4px;
          text-align: center;
          max-height: 32px;
          overflow: hidden;
        }
        .attend-cell.is-selected .attend-special-label {
          color: #f5c89e;
        }
        .attend-day-num {
          font-family: var(--f-display);
          font-weight: 700;
          font-size: 22px;
          line-height: 1;
        }
        .attend-day-mo {
          font-family: var(--f-mono);
          font-size: 10.5px;
          letter-spacing: 0.08em;
          text-transform: uppercase;
          opacity: 0.85;
        }
        .attend-mark {
          position: absolute;
          top: 6px; right: 8px;
          font-size: 14px;
          font-weight: 800;
        }
        @media (max-width: 640px) {
          .attend-cell { min-height: 64px; padding: 10px 4px; }
          .attend-day-num { font-size: 18px; }
        }
      `}</style>
    </div>
  );
}

// Builds the list of weekday slots between camp.start_date and camp.end_date,
// capped at 6 weeks (30 weekdays) per the CR. Weekends are skipped because
// camps are Mon-Fri.
//
// CR #283: parse camp dates as LOCAL dates (date-only, ignore the UTC
// midnight time suffix Postgres returns). The previous version used
// `new Date(camp.start_date)`, which in US time zones shifted UTC
// midnight back into the previous evening -- e.g. "2026-06-30T00:00:00Z"
// became June 29 8pm Eastern, so the loop started a day early and JJ
// saw June 29 in the YPTC calendar even though the camp starts June 30.
function buildWeekdayGrid(camp) {
  if (!camp || !camp.start_date || !camp.end_date) return [];
  const start = parseLocalDate(camp.start_date);
  const end = parseLocalDate(camp.end_date);
  if (!start || !end || end < start) return [];
  // 7 weeks * 5 weekdays = 35. Camps that start mid-week (e.g. YPTC June 30
  // is a Tuesday) need the extra headroom so the partial-first-week + 6
  // full weeks fits without truncation (CR #289).
  const cap = 35;
  const out = [];
  const cur = new Date(start);
  while (cur <= end && out.length < cap) {
    const dow = cur.getDay(); // 0=Sun..6=Sat
    if (dow >= 1 && dow <= 5) {
      out.push({
        iso: isoLocal(cur),
        dayNum: cur.getDate(),
        shortMo: cur.toLocaleString("en-US", { month: "short" }),
        dow, // CR #289: needed so the cell can land in the correct Mon-Fri column
      });
    }
    cur.setDate(cur.getDate() + 1);
  }
  return out;
}

// Parse a Postgres DATE / timestamp string as the local-date it represents.
// Accepts "YYYY-MM-DD", "YYYY-MM-DDTHH:MM:SS.sssZ", or anything starting
// with YYYY-MM-DD. Returns a Date pinned to that local calendar day at
// midnight local time.
function parseLocalDate(s) {
  if (!s) return null;
  const m = String(s).match(/^(\d{4})-(\d{2})-(\d{2})/);
  if (!m) return null;
  const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
  return isNaN(d) ? null : d;
}

function isoLocal(d) {
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  const day = String(d.getDate()).padStart(2, "0");
  return `${y}-${m}-${day}`;
}

function formatDateRange(a, b) {
  return `${formatDay(a)} – ${formatDay(b)}`;
}

function formatDay(s) {
  if (!s) return "";
  // CR #283: render as the local calendar day Postgres meant -- new Date(s)
  // on a "YYYY-MM-DDT00:00:00Z" string shifts back a day in US time zones.
  const d = parseLocalDate(s);
  if (!d) return "";
  return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
}

Object.assign(window, { ParentAttendancePage });
