// RSVPFlow — multi-step questionnaire matching the reference screens.
//
// FIRST-TIME RSVP (no link):
//   0a. Name lookup — also has "Already RSVP'd? Update your response" link
//   0b. Picker (only if multiple households match the name)
//   0c. Household checklist — checkboxes per guest, plus email field.
//       Unchecked = that guest is NOT attending. The dedicated Accept/Decline
//       step is gone; attendance is fully implicit from the checklist.
//   2.  Dietary per attending (checked) guest
//   3.  Song request
//   4.  Confirm + send
//   5.  Summary
//
// UPDATE RSVP via email link (`?rsvp=open&token=<base64-email>` from the
// confirmation email — App.jsx parses the URL and passes `updateToken` as a
// prop):
//   - On mount, decode the token, hit `GET sheetEndpoint?email=…`, and the
//     backend returns the household + previous selections + previous song.
//   - Skips name lookup AND the email-entry sub-step; lands directly on the
//     household checklist (step 0c) with checks pre-filled and email pre-set.
//
// MANUAL UPDATE (returning user, no link):
//   - Click "Already RSVP'd? Update your response" on step 0a.
//   - Step 0u: email-only entry. Submit → same backend lookup → land on step 0c.
//
// Submission posts to a Google Apps Script web app endpoint that writes
// straight into the master Guest list spreadsheet and emails a confirmation.

const { useState: useStateRSVP } = React;

// "Add to Calendar" link for step 5 — opens Google Calendar's TEMPLATE
// URL with the wedding event prefilled. Google Calendar handles timezone
// conversion for guests in any region. Date is the fixed ceremony start
// (12:30pm AEST, 29 Aug 2026) through reception end (~11:30pm AEST).
const CALENDAR_URL = `https://calendar.google.com/calendar/render?` +
  new URLSearchParams({
    action: 'TEMPLATE',
    text: "Audrey & Nicholas's Wedding",
    dates: '20260829T023000Z/20260829T133000Z', // 12:30 AEST → 23:30 AEST in UTC
    details: "Ceremony 12:30pm at Scots' Church, 77 Russell Street, Melbourne. Cocktails 6:30pm and reception 7:30pm at State Library Victoria, 328 Swanston Street, Melbourne. Black tie.",
    location: "Scots' Church, 77 Russell Street, Melbourne VIC 3000",
  }).toString();

// Loaded once at module load — the real household lookup.
let _GROUPS = null;
let _GROUPS_PROMISE = null;
function loadGroups() {
  if (_GROUPS) return Promise.resolve(_GROUPS);
  if (_GROUPS_PROMISE) return _GROUPS_PROMISE;
  _GROUPS_PROMISE = fetch('guest-groups.json')
    .then(r => r.json())
    .then(data => { _GROUPS = data; return data; })
    .catch(err => { console.error('Could not load guest-groups.json', err); return []; });
  return _GROUPS_PROMISE;
}
loadGroups(); // warm cache

// Find the household that contains the given full name (or first name if unique).
// Returns { party: [{ name }] } when matched, { ambiguous: [...] } when multiple
// candidates, null when nothing matches.
function findHousehold(query, groups) {
  const q = String(query || '').toLowerCase().trim();
  if (!q) return null;

  const norm = s => String(s || '').toLowerCase().trim();
  const exactFull = groups.filter(g => g.members.some(m => norm(m) === q));
  if (exactFull.length === 1) return { party: exactFull[0].members.map(name => ({ name })) };
  if (exactFull.length > 1) return { ambiguous: exactFull };

  // Try first name only — only if unambiguous
  const tokens = q.split(/\s+/);
  if (tokens.length === 1) {
    const firstMatches = groups.filter(g =>
      g.members.some(m => norm(m).split(/\s+/)[0] === q)
    );
    if (firstMatches.length === 1) return { party: firstMatches[0].members.map(name => ({ name })) };
    if (firstMatches.length > 1) return { ambiguous: firstMatches };
  }

  // Substring fallback (e.g., user typed partial "Diaz" and there are multiple)
  const sub = groups.filter(g => g.members.some(m => norm(m).includes(q)));
  if (sub.length === 1) return { party: sub[0].members.map(name => ({ name })) };
  if (sub.length > 1) return { ambiguous: sub };

  return null;
}

function RSVPFlow({ onFinished, sheetEndpoint, updateToken }) {
  const [step, setStep] = React.useState(0);
  const [updateMode, setUpdateMode] = React.useState(false); // true = update flow (email-anchored)
  const [fullName, setFullName] = React.useState("");
  const [found, setFound] = React.useState(null); // { party: [{ pid, name, position }] }
  const [candidates, setCandidates] = React.useState([]); // matched households when ambiguous
  const [householdConfirmed, setHouseholdConfirmed] = React.useState(false); // anchored by email
  const [lookupError, setLookupError] = React.useState("");
  const [lookupBusy, setLookupBusy] = React.useState(false);
  const [attending, setAttending] = React.useState({}); // pid -> boolean. Checked = attending.
  const [diet, setDiet] = React.useState({});
  const [dietIndex, setDietIndex] = React.useState(0);
  const [email, setEmail] = React.useState("");
  const [sendConfirm, setSendConfirm] = React.useState(true);
  const [emailTouched, setEmailTouched] = React.useState(false);
  const [submitting, setSubmitting] = React.useState(false);
  const [submitted, setSubmitted] = React.useState(false);

  const emailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim());

  // If we arrived via the email update link (App.jsx parsed the URL and
  // passed `updateToken`), decode the email and run the backend lookup
  // immediately. The user lands directly on the checklist with their
  // previous selections pre-filled.
  React.useEffect(() => {
    if (!updateToken) return;
    let decoded = updateToken;
    try { decoded = atob(updateToken); } catch {}
    if (!decoded || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(decoded)) return;
    setEmail(decoded);
    setUpdateMode(true);
    runUpdateLookup(decoded);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [updateToken]);

  // Resolve the chosen household into our internal `party` shape.
  // Each member gets a stable per-position `pid` so people with identical
  // names (e.g. two Oscar Diaz in one household) stay independently
  // selectable. The pid also tells Code.gs which sheet row to write to when
  // there are duplicate-name rows.
  // `prevResponse` (optional) is an array aligned by index with the same
  // members, carrying { attendance, dietary } from the spreadsheet — used to
  // pre-fill the checklist on the update flow. When omitted, every guest
  // defaults to attending (checked).
  function pickHousehold(members, prevResponse = null) {
    const party = members.map((m, i) => {
      const name = typeof m === 'string' ? m : (m.name || m);
      return { pid: 'p' + i, name, position: i };
    });
    setFound({ party });
    const initAttending = {};
    const initDiet = {};
    party.forEach((p, i) => {
      const prev = prevResponse && prevResponse[i];
      if (prev) {
        const att = String(prev.attendance || '').toLowerCase();
        // Treat 'accept' / 'yes' as checked. Anything else (decline, no, blank)
        // = unchecked.
        initAttending[p.pid] = att === 'accept' || att === 'yes';
        initDiet[p.pid] = prev.dietary || '';
      } else {
        initAttending[p.pid] = true; // first-timer default
        initDiet[p.pid] = '';
      }
    });
    setAttending(initAttending);
    setDiet(initDiet);
    setCandidates([]);
    setLookupError("");
    setHouseholdConfirmed(false);
  }

  // Email-anchored lookup against the Apps Script web app. Returns the
  // household members + previous attendance / dietary / song.
  //
  // DEV-MODE FALLBACK: when `sheetEndpoint` is unset or still the placeholder
  // (i.e., the Apps Script web app hasn't been deployed and pasted into
  // App.jsx yet), we fake the lookup locally using guest-groups.json so the
  // update UI can be exercised on localhost. The fake matches the email's
  // local part against household members — typing `oscar@anything` returns
  // any household with "Oscar" in it. All members come back as `accept` with
  // no dietary, mimicking a fresh RSVP. Remove this branch (or it auto-falls
  // through) once the real endpoint is configured.
  async function runUpdateLookup(emailToFind) {
    if (!emailToFind) return;
    setLookupBusy(true);
    setLookupError("");

    const isPlaceholderEndpoint =
      !sheetEndpoint ||
      typeof sheetEndpoint !== 'string' ||
      sheetEndpoint.indexOf('PASTE_APPS_SCRIPT') >= 0 ||
      !/^https?:\/\//.test(sheetEndpoint);

    if (isPlaceholderEndpoint) {
      try {
        const groups = await loadGroups();
        const localPart = String(emailToFind).split('@')[0] || '';
        const result = findHousehold(localPart, groups || []);
        let chosen = null;
        if (result && result.party) chosen = result.party.map(p => p.name);
        else if (result && result.ambiguous && result.ambiguous.length) chosen = result.ambiguous[0].members;
        if (!chosen || !chosen.length) {
          setLookupError(`(dev mock) No household matched "${localPart}". Try an email whose local part matches a guest's first name — e.g., oscar@anything.`);
          setLookupBusy(false);
          return;
        }
        const mockParty = chosen.map(name => ({ name, attendance: 'accept', dietary: '' }));
        console.info('[RSVPFlow] sheetEndpoint not configured — returning DEV-MOCK household for', emailToFind, mockParty);
        pickHousehold(mockParty, mockParty);
      } catch (e) {
        console.warn('RSVP dev-mock lookup failed:', e);
        setLookupError("Dev mock lookup failed. Check console.");
      } finally {
        setLookupBusy(false);
      }
      return;
    }

    try {
      const url = `${sheetEndpoint}?email=${encodeURIComponent(emailToFind.trim())}`;
      const r = await fetch(url);
      const data = await r.json();
      if (!data.ok || !data.party || data.party.length === 0) {
        setLookupError("We couldn't find an existing RSVP for that email. Double-check the address, or use the back arrow to start a fresh RSVP under your name.");
        return;
      }
      pickHousehold(data.party, data.party);
    } catch (e) {
      console.warn('RSVP update lookup failed:', e);
      setLookupError("We couldn't reach the server. Please try again in a moment.");
    } finally {
      setLookupBusy(false);
    }
  }

  // Called from the household checklist once the email is valid. The
  // attendance state is already complete (every checkbox has a boolean), so
  // we skip the now-redundant Accept/Decline screen and jump straight to
  // dietary (or to the confirm screen, if nobody is attending).
  function confirmHousehold() {
    if (!emailValid) { setEmailTouched(true); return; }
    setHouseholdConfirmed(true);
    const accepting = (found?.party || []).filter(p => attending[p.pid]);
    if (accepting.length > 0) {
      setDietIndex(0);
      setStep(2);
    } else {
      setStep(4);
    }
  }

  // Real party lookup — hits the loaded guest-groups.json
  async function findInvitation() {
    if (!fullName.trim()) return;
    setLookupBusy(true);
    setLookupError("");
    setCandidates([]);
    const groups = await loadGroups();
    const result = findHousehold(fullName, groups || []);
    setLookupBusy(false);

    if (!result) {
      setLookupError("We can't find that name. Try your first name, your surname, or your full name as it appears on the invitation.");
      return;
    }
    if (result.ambiguous) {
      setCandidates(result.ambiguous);
      return;
    }
    pickHousehold(result.party);
  }

  async function submit() {
    setSubmitting(true);
    const payload = {
      submitted_at: new Date().toISOString(),
      primary_name: fullName,
      party: (found?.party || []).map(p => ({
        name: p.name,
        position: p.position,                       // 0-based slot in the household
        attendance: attending[p.pid] ? 'accept' : 'decline',
        dietary: attending[p.pid] ? (diet[p.pid] || '') : '',
      })),
      email,
      send_confirm: sendConfirm,
    };
    try {
      // POST to the configured sheet endpoint. Use no-cors so Apps Script accepts the form.
      if (sheetEndpoint) {
        await fetch(sheetEndpoint, {
          method: 'POST',
          mode: 'no-cors',
          headers: { 'Content-Type': 'text/plain;charset=utf-8' },
          body: JSON.stringify(payload),
        });
      } else {
        console.warn('RSVPFlow: sheetEndpoint not configured — payload only logged locally', payload);
      }
    } catch (e) {
      console.warn('RSVP submission failed:', e);
    }
    // Mirror to localStorage so the session remembers
    try { localStorage.setItem('rsvp_submission', JSON.stringify(payload)); } catch {}
    setSubmitting(false);
    setSubmitted(true);
    setStep(5);
  }

  const totalSteps = 5;
  const progress = Math.min(1, step / totalSteps);
  const acceptedParty = (found?.party || []).filter(p => attending[p.pid]);
  const dietGuestList = acceptedParty;
  const currentDietGuest = dietGuestList[dietIndex];

  // Back button — moves the guest one step back through the flow.
  // Hidden on the very first screen (name input, with no update mode and no
  // candidates and no found household) and on the post-submit summary screen.
  const canGoBack = !(step === 0 && candidates.length === 0 && !found && !updateMode) && step !== 5;

  function goBack() {
    if (step === 0 && updateMode && !found) {
      // Update email entry → name lookup
      setUpdateMode(false);
      setEmail("");
      setEmailTouched(false);
      setLookupError("");
      return;
    }
    if (step === 0 && found && !householdConfirmed) {
      // Checklist → either update email entry (if we got here via update
      // flow) or back to picker / name input (first-timer flow).
      setFound(null);
      setAttending({});
      setDiet({});
      if (updateMode) {
        setEmail("");
        setEmailTouched(false);
      }
      // candidates still set means we'll land back on the picker
      return;
    }
    if (step === 0 && candidates.length > 0) {
      // Picker → name input
      setCandidates([]);
      setLookupError("");
      return;
    }
    if (step === 2) {
      // Dietary → previous diet guest, or back to checklist if at first
      if (dietIndex > 0) setDietIndex(i => i - 1);
      else { setHouseholdConfirmed(false); setStep(0); }
      return;
    }
    if (step === 4) {
      // Confirm/send → last diet guest (if any) or checklist
      if (dietGuestList.length > 0) {
        setDietIndex(dietGuestList.length - 1);
        setStep(2);
      } else {
        setHouseholdConfirmed(false);
        setStep(0);
      }
      return;
    }
  }

  return (
    <div className="rsvp-sheet">
      <div className="rsvp-bar">
        {canGoBack && (
          <button className="rsvp-back" onClick={goBack} aria-label="Back">←</button>
        )}
        <div className="rsvp-bar-label">RSVP</div>
        <button className="rsvp-close" onClick={onFinished} aria-label="Close">✕</button>
        <div className="rsvp-progress-wrap">
          <div className="rsvp-progress" style={{ width: `${progress * 100}%` }} />
        </div>
      </div>

      <div className="rsvp-body">
        {/* Step 0a — Name lookup (first-timer entry) */}
        {step === 0 && !updateMode && candidates.length === 0 && !found && (
          <StepFade>
            <h2 className="rsvp-h">Audrey &amp; Nicholas's Wedding</h2>
            <p className="rsvp-p">
              If you're responding for you and a guest (or your family),
              you'll be able to RSVP for your entire group.
            </p>
            <label className="rsvp-field">
              <input
                className={`rsvp-input ${lookupError ? 'error' : ''}`}
                placeholder="Your name (or surname)"
                value={fullName}
                onChange={e => { setFullName(e.target.value); setLookupError(""); }}
                onKeyDown={e => e.key === 'Enter' && findInvitation()}
                autoFocus
              />
            </label>
            {lookupError && <div className="rsvp-err">{lookupError}</div>}
            <button
              className={`rsvp-btn ${fullName.trim() && !lookupBusy ? 'primary' : 'muted'}`}
              onClick={findInvitation}
              disabled={!fullName.trim() || lookupBusy}
            >{lookupBusy ? 'Looking…' : 'Find your invitation'}</button>
            <button
              type="button"
              className="rsvp-link"
              onClick={() => { setUpdateMode(true); setLookupError(""); }}
            >Already RSVP'd? Update your response</button>
          </StepFade>
        )}

        {/* Step 0u — Email-only entry (manual update flow) */}
        {step === 0 && updateMode && !found && (
          <StepFade>
            <h2 className="rsvp-h">Update your response</h2>
            <p className="rsvp-p">
              Enter the email you used when you RSVP'd. We'll bring up your party
              so you can edit who's attending.
            </p>
            <input
              className={`rsvp-input ${(emailTouched && !emailValid) || lookupError ? 'error' : ''}`}
              placeholder="Your email"
              value={email}
              onChange={e => { setEmail(e.target.value); setLookupError(""); }}
              onBlur={() => setEmailTouched(true)}
              onKeyDown={e => { if (e.key === 'Enter' && emailValid) runUpdateLookup(email); }}
              type="email"
              autoFocus
            />
            {emailTouched && !emailValid && <div className="rsvp-err">Please enter a valid email.</div>}
            {lookupError && <div className="rsvp-err">{lookupError}</div>}
            <button
              className={`rsvp-btn ${emailValid && !lookupBusy ? 'primary' : 'muted'}`}
              onClick={() => { setEmailTouched(true); if (emailValid) runUpdateLookup(email); }}
              disabled={!emailValid || lookupBusy}
            >{lookupBusy ? 'Looking…' : 'Find my RSVP'}</button>
          </StepFade>
        )}

        {/* Step 0b — Multi-household picker (first-timer ambiguous match) */}
        {step === 0 && !updateMode && candidates.length > 0 && !found && (
          <StepFade>
            <h2 className="rsvp-h">Which is your household?</h2>
            <p className="rsvp-p">
              We found {candidates.length} invitations that match "{fullName}".
              Please choose the one you're in.
            </p>
            <div className="rsvp-candidate-list">
              {candidates.map(g => (
                <button
                  key={g.id}
                  className="rsvp-candidate"
                  onClick={() => pickHousehold(g.members)}
                >
                  <span className="rsvp-candidate-count">{g.members.length}</span>
                  <span className="rsvp-candidate-label">{g.label}</span>
                </button>
              ))}
            </div>
            <button
              className="rsvp-btn primary outlined"
              onClick={() => { setCandidates([]); setLookupError(""); }}
            >Try a different name</button>
          </StepFade>
        )}

        {/* Step 0c — Household checklist + email. Replaces both the old
            "Confirm your household" screen AND the separate Accept/Decline
            step. Unchecked = that guest is declining. */}
        {step === 0 && found && !householdConfirmed && (
          <StepFade>
            <h2 className="rsvp-h">{updateMode ? 'Update your response' : 'Confirm your household'}</h2>
            <p className="rsvp-p">
              {updateMode
                ? "Here's your party. Uncheck anyone who can no longer attend, then continue."
                : "We found everyone in your party. Uncheck anyone who can't attend, then add your email to continue."}
            </p>
            <ul className="rsvp-checklist">
              {found.party.map(p => (
                <li key={p.pid}>
                  <label className="rsvp-check-row">
                    <input
                      type="checkbox"
                      checked={!!attending[p.pid]}
                      onChange={e => setAttending(a => ({ ...a, [p.pid]: e.target.checked }))}
                    />
                    <span className="rsvp-check-mark" aria-hidden="true" />
                    <span className="rsvp-check-name">{p.name}</span>
                  </label>
                </li>
              ))}
            </ul>
            <input
              className={`rsvp-input ${emailTouched && !emailValid ? 'error' : ''}`}
              placeholder="Your email"
              value={email}
              onChange={e => setEmail(e.target.value)}
              onBlur={() => setEmailTouched(true)}
              onKeyDown={e => e.key === 'Enter' && confirmHousehold()}
              type="email"
              autoFocus={!updateMode && !email}
            />
            {emailTouched && !emailValid && <div className="rsvp-err">Please enter a valid email so we can connect this RSVP to you.</div>}
            <button
              className={`rsvp-btn ${emailValid ? 'primary' : 'muted'}`}
              onClick={confirmHousehold}
              disabled={!emailValid}
            >Continue</button>
            {!updateMode && (
              <button
                className="rsvp-btn primary outlined"
                onClick={() => {
                  // Not their household — go back to picker (or name input)
                  setFound(null);
                  setAttending({});
                  setDiet({});
                  setHouseholdConfirmed(false);
                }}
              >That's not my household</button>
            )}
          </StepFade>
        )}

        {/* Step 2 — Dietary per attending guest */}
        {step === 2 && currentDietGuest && (
          <StepFade key={`diet-${currentDietGuest.pid}`}>
            <h2 className="rsvp-h">Do you have any dietary restrictions?</h2>
            <div className="rsvp-diet-label">{currentDietGuest.name}:</div>
            <textarea
              className="rsvp-textarea"
              value={diet[currentDietGuest.pid] || ''}
              onChange={e => setDiet(d => ({ ...d, [currentDietGuest.pid]: e.target.value }))}
              placeholder=""
            />
            <button className="rsvp-btn primary" onClick={() => {
              if (dietIndex < dietGuestList.length - 1) setDietIndex(i => i + 1);
              else setStep(4);
            }}>Continue</button>
            <button className="rsvp-btn primary outlined" onClick={() => {
              if (dietIndex < dietGuestList.length - 1) setDietIndex(i => i + 1);
              else setStep(4);
            }}>Skip</button>
          </StepFade>
        )}

        {/* Step 4 — Confirm and send */}
        {step === 4 && (
          <StepFade>
            <h2 className="rsvp-h">Ready to send?</h2>
            <p className="rsvp-p">
              We'll send your confirmation to <strong>{email}</strong>.
              You can come back and update your response anytime using the link in that email.
            </p>
            <label className="rsvp-check">
              <input
                type="checkbox"
                checked={sendConfirm}
                onChange={e => setSendConfirm(e.target.checked)}
              />
              <span className="rsvp-check-box" />
              <span>Send me an RSVP confirmation by email</span>
            </label>
            <button
              className={`rsvp-btn ${emailValid && !submitting ? 'primary' : 'muted'}`}
              onClick={() => { setEmailTouched(true); if (emailValid) submit(); }}
              disabled={submitting || !emailValid}
            >{submitting ? 'Sending…' : 'Send RSVP'}</button>
            <button
              className="rsvp-btn primary outlined"
              onClick={() => { setHouseholdConfirmed(false); setStep(0); }}
            >Change email</button>
          </StepFade>
        )}

        {/* Step 5 — Confirmation summary */}
        {step === 5 && (
          <StepFade>
            <h2 className="rsvp-h">All set! Here's what we sent Audrey &amp; Nicholas.</h2>
            <div className="rsvp-summary">
              <div className="rsvp-summary-row">
                <div className="rsvp-summary-label">Your RSVP response</div>
                <button
                  className="rsvp-summary-link"
                  onClick={() => {
                    // Send the guest back to the household checklist with their
                    // existing answers preserved (including the email anchor).
                    // On resubmit, the spreadsheet rows are overwritten with the
                    // new values.
                    setSubmitted(false);
                    setDietIndex(0);
                    setHouseholdConfirmed(false);
                    setStep(0);
                  }}
                >Update response</button>
              </div>
              <div className="rsvp-summary-title">Wedding Day <a className="rsvp-summary-cal" href={CALENDAR_URL} target="_blank" rel="noopener noreferrer">＋ Add to Calendar</a></div>
              <ul className="rsvp-summary-list">
                {(found?.party || []).map(p => (
                  <li key={p.pid} className={!attending[p.pid] ? 'declined' : ''}>
                    <span className={`rsvp-summary-dot ${attending[p.pid] ? 'accept' : 'decline'}`}>{attending[p.pid] ? '✓' : '✕'}</span>
                    {p.name}
                  </li>
                ))}
              </ul>
            </div>
            <button className="rsvp-btn primary" onClick={() => {
              // After RSVP, take the guest straight to the Details page
              // (timing, parking, gifts, attire summary).
              if (onFinished) onFinished('details');
            }}>Continue to Details</button>
          </StepFade>
        )}
      </div>

      {/* Watermark logo at the bottom of the modal — fills the empty space
          under shorter steps and acts as a brand anchor on every step. */}
      <div className="rsvp-mark">
        <img src="assets/Logos/A%26N-logo-small.png" alt="A & N" />
      </div>
    </div>
  );
}

function StepFade({ children }) {
  return <div className="rsvp-step-fade">{children}</div>;
}

window.RSVPFlow = RSVPFlow;
